Skip to content

Commit 860bad1

Browse files
authoredFeb 11, 2024
Merge pull request #562 from python-openapi/fix/milti-types-schema-format-unmarshal-fix
Fix/milti types schema format unmarshal fix
2 parents e666357 + 97775cf commit 860bad1

File tree

5 files changed

+69
-52
lines changed

5 files changed

+69
-52
lines changed
 

‎openapi_core/unmarshalling/schemas/exceptions.py

-18
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,3 @@ class FormatterNotFoundError(UnmarshallerError):
1919

2020
def __str__(self) -> str:
2121
return f"Formatter not found for {self.type_format} format"
22-
23-
24-
@dataclass
25-
class FormatUnmarshalError(UnmarshallerError):
26-
"""Unable to unmarshal value for format"""
27-
28-
value: str
29-
type: str
30-
original_exception: Exception
31-
32-
def __str__(self) -> str:
33-
return (
34-
"Unable to unmarshal value {value} for format {type}: {exception}"
35-
).format(
36-
value=self.value,
37-
type=self.type,
38-
exception=self.original_exception,
39-
)

‎openapi_core/unmarshalling/schemas/unmarshallers.py

+27-31
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
from openapi_core.unmarshalling.schemas.datatypes import (
1616
FormatUnmarshallersDict,
1717
)
18-
from openapi_core.unmarshalling.schemas.exceptions import FormatUnmarshalError
19-
from openapi_core.unmarshalling.schemas.exceptions import UnmarshallerError
2018
from openapi_core.validation.schemas.validators import SchemaValidator
2119

2220
log = logging.getLogger(__name__)
@@ -138,34 +136,15 @@ def _unmarshal_properties(
138136

139137
class MultiTypeUnmarshaller(PrimitiveUnmarshaller):
140138
def __call__(self, value: Any) -> Any:
141-
unmarshaller = self._get_best_unmarshaller(value)
139+
primitive_type = self.schema_validator.get_primitive_type(value)
140+
unmarshaller = self.schema_unmarshaller.get_type_unmarshaller(
141+
primitive_type
142+
)
142143
return unmarshaller(value)
143144

144-
@property
145-
def type(self) -> List[str]:
146-
types = self.schema.getkey("type", ["any"])
147-
assert isinstance(types, list)
148-
return types
149-
150-
def _get_best_unmarshaller(self, value: Any) -> "PrimitiveUnmarshaller":
151-
for schema_type in self.type:
152-
result = self.schema_validator.type_validator(
153-
value, type_override=schema_type
154-
)
155-
if not result:
156-
continue
157-
result = self.schema_validator.format_validator(value)
158-
if not result:
159-
continue
160-
return self.schema_unmarshaller.get_type_unmarshaller(schema_type)
161-
162-
raise UnmarshallerError("Unmarshaller not found for type(s)")
163-
164145

165146
class AnyUnmarshaller(MultiTypeUnmarshaller):
166-
@property
167-
def type(self) -> List[str]:
168-
return self.schema_unmarshaller.types_unmarshaller.get_types()
147+
pass
169148

170149

171150
class TypesUnmarshaller:
@@ -185,7 +164,7 @@ def __init__(
185164
def get_types(self) -> List[str]:
186165
return list(self.unmarshallers.keys())
187166

188-
def get_unmarshaller(
167+
def get_unmarshaller_cls(
189168
self,
190169
schema_type: Optional[Union[Iterable[str], str]],
191170
) -> Type["PrimitiveUnmarshaller"]:
@@ -220,8 +199,8 @@ def unmarshal(self, schema_format: str, value: Any) -> Any:
220199
return value
221200
try:
222201
return format_unmarshaller(value)
223-
except (ValueError, TypeError) as exc:
224-
raise FormatUnmarshalError(value, schema_format, exc)
202+
except (AttributeError, ValueError, TypeError):
203+
return value
225204

226205
def get_unmarshaller(
227206
self, schema_format: str
@@ -279,19 +258,32 @@ def unmarshal(self, value: Any) -> Any:
279258
(isinstance(value, bytes) and schema_format in ["binary", "byte"])
280259
):
281260
return typed
282-
return self.formats_unmarshaller.unmarshal(schema_format, typed)
261+
262+
format_unmarshaller = self.get_format_unmarshaller(schema_format)
263+
if format_unmarshaller is None:
264+
return typed
265+
try:
266+
return format_unmarshaller(typed)
267+
except (AttributeError, ValueError, TypeError):
268+
return typed
283269

284270
def get_type_unmarshaller(
285271
self,
286272
schema_type: Optional[Union[Iterable[str], str]],
287273
) -> PrimitiveUnmarshaller:
288-
klass = self.types_unmarshaller.get_unmarshaller(schema_type)
274+
klass = self.types_unmarshaller.get_unmarshaller_cls(schema_type)
289275
return klass(
290276
self.schema,
291277
self.schema_validator,
292278
self,
293279
)
294280

281+
def get_format_unmarshaller(
282+
self,
283+
schema_format: str,
284+
) -> Optional[FormatUnmarshaller]:
285+
return self.formats_unmarshaller.get_unmarshaller(schema_format)
286+
295287
def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller":
296288
cls = self.__class__
297289

@@ -304,6 +296,10 @@ def evolve(self, schema: SchemaPath) -> "SchemaUnmarshaller":
304296

305297
def find_format(self, value: Any) -> Optional[str]:
306298
for schema in self.schema_validator.iter_valid_schemas(value):
299+
schema_validator = self.schema_validator.evolve(schema)
300+
primitive_type = schema_validator.get_primitive_type(value)
301+
if primitive_type != "string":
302+
continue
307303
if "format" in schema:
308304
return str(schema.getkey("format"))
309305
return None

‎openapi_core/validation/schemas/validators.py

+18
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ def format_validator_callable(self) -> FormatValidator:
7878

7979
return lambda x: True
8080

81+
def get_primitive_type(self, value: Any) -> Optional[str]:
82+
schema_types = self.schema.getkey("type")
83+
if isinstance(schema_types, str):
84+
return schema_types
85+
if schema_types is None:
86+
schema_types = sorted(self.validator.TYPE_CHECKER._type_checkers)
87+
assert isinstance(schema_types, list)
88+
for schema_type in schema_types:
89+
result = self.type_validator(value, type_override=schema_type)
90+
if not result:
91+
continue
92+
result = self.format_validator(value)
93+
if not result:
94+
continue
95+
assert isinstance(schema_type, (str, type(None)))
96+
return schema_type
97+
return None
98+
8199
def iter_valid_schemas(self, value: Any) -> Iterator[SchemaPath]:
82100
yield self.schema
83101

‎tests/integration/unmarshalling/test_unmarshallers.py

+21
Original file line numberDiff line numberDiff line change
@@ -2057,6 +2057,27 @@ def test_nultiple_types_invalid(self, unmarshallers_factory, types, value):
20572057
assert len(exc_info.value.schema_errors) == 1
20582058
assert "is not of type" in exc_info.value.schema_errors[0].message
20592059

2060+
@pytest.mark.parametrize(
2061+
"types,format,value,expected",
2062+
[
2063+
(["string", "null"], "date", None, None),
2064+
(["string", "null"], "date", "2018-12-13", date(2018, 12, 13)),
2065+
],
2066+
)
2067+
def test_multiple_types_format_valid_or_ignored(
2068+
self, unmarshallers_factory, types, format, value, expected
2069+
):
2070+
schema = {
2071+
"type": types,
2072+
"format": format,
2073+
}
2074+
spec = SchemaPath.from_dict(schema)
2075+
unmarshaller = unmarshallers_factory.create(spec)
2076+
2077+
result = unmarshaller.unmarshal(value)
2078+
2079+
assert result == expected
2080+
20602081
def test_any_null(self, unmarshallers_factory):
20612082
schema = {}
20622083
spec = SchemaPath.from_dict(schema)

‎tests/unit/unmarshalling/test_schema_unmarshallers.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from openapi_core.unmarshalling.schemas.exceptions import (
99
FormatterNotFoundError,
1010
)
11-
from openapi_core.unmarshalling.schemas.exceptions import FormatUnmarshalError
1211
from openapi_core.unmarshalling.schemas.factories import (
1312
SchemaUnmarshallersFactory,
1413
)
@@ -102,8 +101,9 @@ def custom_format_unmarshaller(value):
102101
extra_format_unmarshallers=extra_format_unmarshallers,
103102
)
104103

105-
with pytest.raises(FormatUnmarshalError):
106-
unmarshaller.unmarshal(value)
104+
result = unmarshaller.unmarshal(value)
105+
106+
assert result == value
107107

108108
def test_schema_extra_format_unmarshaller_format_custom(
109109
self, schema_unmarshaller_factory

0 commit comments

Comments
 (0)
Please sign in to comment.