Skip to content

Commit 5713b79

Browse files
committed
Multi type unmarshaller
1 parent 7db11f4 commit 5713b79

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

openapi_core/unmarshalling/schemas/factories.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import warnings
22
from typing import Any
33
from typing import Dict
4+
from typing import Iterable
45
from typing import Optional
56
from typing import Type
67
from typing import Union
@@ -30,6 +31,9 @@
3031
from openapi_core.unmarshalling.schemas.unmarshallers import (
3132
IntegerUnmarshaller,
3233
)
34+
from openapi_core.unmarshalling.schemas.unmarshallers import (
35+
MultiTypeUnmarshaller,
36+
)
3337
from openapi_core.unmarshalling.schemas.unmarshallers import NullUnmarshaller
3438
from openapi_core.unmarshalling.schemas.unmarshallers import NumberUnmarshaller
3539
from openapi_core.unmarshalling.schemas.unmarshallers import ObjectUnmarshaller
@@ -89,6 +93,12 @@ def create(
8993
formatter = self.custom_formatters.get(schema_format)
9094

9195
schema_type = type_override or schema.getkey("type", "any")
96+
if isinstance(schema_type, Iterable) and not isinstance(
97+
schema_type, str
98+
):
99+
return MultiTypeUnmarshaller(
100+
schema, validator, formatter, self, context=self.context
101+
)
92102
if schema_type in self.COMPLEX_UNMARSHALLERS:
93103
complex_klass = self.COMPLEX_UNMARSHALLERS[schema_type]
94104
return complex_klass(

openapi_core/unmarshalling/schemas/unmarshallers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,27 @@ def _unmarshal_object(self, value: Any) -> Any:
312312
return properties
313313

314314

315+
class MultiTypeUnmarshaller(ComplexUnmarshaller):
316+
@property
317+
def types_unmarshallers(self) -> List["BaseSchemaUnmarshaller"]:
318+
types = self.schema.getkey("type", ["any"])
319+
unmarshaller = partial(self.unmarshallers_factory.create, self.schema)
320+
return list(map(unmarshaller, types))
321+
322+
def unmarshal(self, value: Any) -> Any:
323+
for unmarshaller in self.types_unmarshallers:
324+
# validate with validator of formatter (usualy type validator)
325+
try:
326+
unmarshaller._formatter_validate(value)
327+
except ValidateError:
328+
continue
329+
else:
330+
return unmarshaller(value)
331+
332+
log.warning("failed to unmarshal multi type")
333+
return value
334+
335+
315336
class AnyUnmarshaller(ComplexUnmarshaller):
316337

317338
SCHEMA_TYPES_ORDER = [

tests/unit/unmarshalling/test_unmarshal.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,32 @@ def test_additional_properties_list(self, unmarshaller_factory):
835835
"user_ids": [1, 2, 3, 4],
836836
}
837837

838+
@pytest.mark.xfail(message="None and NOTSET should be distinguished")
839+
def test_null_not_supported(self, unmarshaller_factory):
840+
schema = {"type": "null"}
841+
spec = Spec.from_dict(schema)
842+
843+
with pytest.raises(InvalidSchemaValue):
844+
unmarshaller_factory(spec)(None)
845+
846+
@pytest.mark.parametrize(
847+
"types,value",
848+
[
849+
(["string", "null"], "string"),
850+
(["number", "null"], 2),
851+
(["number", "null"], 3.14),
852+
(["boolean", "null"], True),
853+
(["array", "null"], [1, 2]),
854+
(["object", "null"], {}),
855+
],
856+
)
857+
def test_nultiple_types_not_supported(self, unmarshaller_factory, types, value):
858+
schema = {"type": types}
859+
spec = Spec.from_dict(schema)
860+
861+
with pytest.raises(TypeError):
862+
unmarshaller_factory(spec)(value)
863+
838864

839865
class TestOAS31SchemaUnmarshallerCall:
840866
@pytest.fixture
@@ -856,3 +882,40 @@ def test_null_invalid(self, unmarshaller_factory, value):
856882

857883
with pytest.raises(InvalidSchemaValue):
858884
unmarshaller_factory(spec)(value)
885+
886+
@pytest.mark.parametrize(
887+
"types,value",
888+
[
889+
(["string", "null"], "string"),
890+
(["number", "null"], 2),
891+
(["number", "null"], 3.14),
892+
(["boolean", "null"], True),
893+
(["array", "null"], [1, 2]),
894+
(["object", "null"], {}),
895+
],
896+
)
897+
def test_nultiple_types(self, unmarshaller_factory, types, value):
898+
schema = {"type": types}
899+
spec = Spec.from_dict(schema)
900+
901+
result = unmarshaller_factory(spec)(value)
902+
903+
assert result == value
904+
905+
@pytest.mark.parametrize(
906+
"types,value",
907+
[
908+
(["string", "null"], 2),
909+
(["number", "null"], "string"),
910+
(["number", "null"], True),
911+
(["boolean", "null"], 3.14),
912+
(["array", "null"], {}),
913+
(["object", "null"], [1, 2]),
914+
],
915+
)
916+
def test_nultiple_types_invalid(self, unmarshaller_factory, types, value):
917+
schema = {"type": types}
918+
spec = Spec.from_dict(schema)
919+
920+
with pytest.raises(InvalidSchemaValue):
921+
unmarshaller_factory(spec)(value)

0 commit comments

Comments
 (0)