Skip to content

Commit 7c9c0ff

Browse files
authored
Merge pull request #518 from p1c2u/refactor/media-type-deserializers-refactor
media type deserializers refactor
2 parents 7a2ae56 + 648a591 commit 7c9c0ff

File tree

13 files changed

+191
-108
lines changed

13 files changed

+191
-108
lines changed

docs/customizations.rst

+6-12
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,26 @@ If you know you have a valid specification already, disabling the validator can
1414
1515
spec = Spec.from_dict(spec_dict, validator=None)
1616
17-
Deserializers
18-
-------------
17+
Media type deserializers
18+
------------------------
1919

20-
Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `MediaTypeDeserializersFactory` and then pass it to `RequestValidator` or `ResponseValidator` constructor:
20+
Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function:
2121

2222
.. code-block:: python
2323
24-
from openapi_core.deserializing.media_types.factories import MediaTypeDeserializersFactory
25-
2624
def protobuf_deserializer(message):
2725
feature = route_guide_pb2.Feature()
2826
feature.ParseFromString(message)
2927
return feature
3028
31-
custom_media_type_deserializers = {
29+
extra_media_type_deserializers = {
3230
'application/protobuf': protobuf_deserializer,
3331
}
34-
media_type_deserializers_factory = MediaTypeDeserializersFactory(
35-
custom_deserializers=custom_media_type_deserializers,
36-
)
3732
38-
result = validate_response(
33+
result = unmarshal_response(
3934
request, response,
4035
spec=spec,
41-
cls=V30ResponseValidator,
42-
media_type_deserializers_factory=media_type_deserializers_factory,
36+
extra_media_type_deserializers=extra_media_type_deserializers,
4337
)
4438
4539
Format validators
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1+
from json import loads
2+
3+
from openapi_core.deserializing.media_types.datatypes import (
4+
MediaTypeDeserializersDict,
5+
)
16
from openapi_core.deserializing.media_types.factories import (
27
MediaTypeDeserializersFactory,
38
)
9+
from openapi_core.deserializing.media_types.util import data_form_loads
10+
from openapi_core.deserializing.media_types.util import urlencoded_form_loads
411

512
__all__ = ["media_type_deserializers_factory"]
613

7-
media_type_deserializers_factory = MediaTypeDeserializersFactory()
14+
media_type_deserializers: MediaTypeDeserializersDict = {
15+
"application/json": loads,
16+
"application/x-www-form-urlencoded": urlencoded_form_loads,
17+
"multipart/form-data": data_form_loads,
18+
}
19+
20+
media_type_deserializers_factory = MediaTypeDeserializersFactory(
21+
media_type_deserializers=media_type_deserializers,
22+
)
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from typing import Any
22
from typing import Callable
3+
from typing import Dict
34

45
DeserializerCallable = Callable[[Any], Any]
6+
MediaTypeDeserializersDict = Dict[str, DeserializerCallable]

openapi_core/deserializing/media_types/deserializers.py

+10-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import warnings
22
from typing import Any
3-
from typing import Callable
3+
from typing import Optional
44

55
from openapi_core.deserializing.media_types.datatypes import (
66
DeserializerCallable,
@@ -10,28 +10,20 @@
1010
)
1111

1212

13-
class BaseMediaTypeDeserializer:
14-
def __init__(self, mimetype: str):
15-
self.mimetype = mimetype
16-
17-
def __call__(self, value: Any) -> Any:
18-
raise NotImplementedError
19-
20-
21-
class UnsupportedMimetypeDeserializer(BaseMediaTypeDeserializer):
22-
def __call__(self, value: Any) -> Any:
23-
warnings.warn(f"Unsupported {self.mimetype} mimetype")
24-
return value
25-
26-
27-
class CallableMediaTypeDeserializer(BaseMediaTypeDeserializer):
13+
class CallableMediaTypeDeserializer:
2814
def __init__(
29-
self, mimetype: str, deserializer_callable: DeserializerCallable
15+
self,
16+
mimetype: str,
17+
deserializer_callable: Optional[DeserializerCallable] = None,
3018
):
3119
self.mimetype = mimetype
3220
self.deserializer_callable = deserializer_callable
3321

34-
def __call__(self, value: Any) -> Any:
22+
def deserialize(self, value: Any) -> Any:
23+
if self.deserializer_callable is None:
24+
warnings.warn(f"Unsupported {self.mimetype} mimetype")
25+
return value
26+
3527
try:
3628
return self.deserializer_callable(value)
3729
except (ValueError, TypeError, AttributeError):
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,60 @@
1-
from json import loads
2-
from typing import Any
3-
from typing import Callable
1+
import warnings
42
from typing import Dict
53
from typing import Optional
64

75
from openapi_core.deserializing.media_types.datatypes import (
86
DeserializerCallable,
97
)
10-
from openapi_core.deserializing.media_types.deserializers import (
11-
BaseMediaTypeDeserializer,
8+
from openapi_core.deserializing.media_types.datatypes import (
9+
MediaTypeDeserializersDict,
1210
)
1311
from openapi_core.deserializing.media_types.deserializers import (
1412
CallableMediaTypeDeserializer,
1513
)
16-
from openapi_core.deserializing.media_types.deserializers import (
17-
UnsupportedMimetypeDeserializer,
18-
)
19-
from openapi_core.deserializing.media_types.util import data_form_loads
20-
from openapi_core.deserializing.media_types.util import urlencoded_form_loads
2114

2215

2316
class MediaTypeDeserializersFactory:
24-
MEDIA_TYPE_DESERIALIZERS: Dict[str, DeserializerCallable] = {
25-
"application/json": loads,
26-
"application/x-www-form-urlencoded": urlencoded_form_loads,
27-
"multipart/form-data": data_form_loads,
28-
}
29-
3017
def __init__(
3118
self,
32-
custom_deserializers: Optional[Dict[str, DeserializerCallable]] = None,
19+
media_type_deserializers: Optional[MediaTypeDeserializersDict] = None,
20+
custom_deserializers: Optional[MediaTypeDeserializersDict] = None,
3321
):
22+
if media_type_deserializers is None:
23+
media_type_deserializers = {}
24+
self.media_type_deserializers = media_type_deserializers
3425
if custom_deserializers is None:
3526
custom_deserializers = {}
27+
else:
28+
warnings.warn(
29+
"custom_deserializers is deprecated. "
30+
"Use extra_media_type_deserializers.",
31+
DeprecationWarning,
32+
)
3633
self.custom_deserializers = custom_deserializers
3734

38-
def create(self, mimetype: str) -> BaseMediaTypeDeserializer:
39-
deserialize_callable = self.get_deserializer_callable(mimetype)
40-
41-
if deserialize_callable is None:
42-
return UnsupportedMimetypeDeserializer(mimetype)
35+
def create(
36+
self,
37+
mimetype: str,
38+
extra_media_type_deserializers: Optional[
39+
MediaTypeDeserializersDict
40+
] = None,
41+
) -> CallableMediaTypeDeserializer:
42+
if extra_media_type_deserializers is None:
43+
extra_media_type_deserializers = {}
44+
deserialize_callable = self.get_deserializer_callable(
45+
mimetype,
46+
extra_media_type_deserializers=extra_media_type_deserializers,
47+
)
4348

4449
return CallableMediaTypeDeserializer(mimetype, deserialize_callable)
4550

4651
def get_deserializer_callable(
47-
self, mimetype: str
52+
self,
53+
mimetype: str,
54+
extra_media_type_deserializers: MediaTypeDeserializersDict,
4855
) -> Optional[DeserializerCallable]:
4956
if mimetype in self.custom_deserializers:
5057
return self.custom_deserializers[mimetype]
51-
return self.MEDIA_TYPE_DESERIALIZERS.get(mimetype)
58+
if mimetype in extra_media_type_deserializers:
59+
return extra_media_type_deserializers[mimetype]
60+
return self.media_type_deserializers.get(mimetype)

openapi_core/deserializing/parameters/deserializers.py

+10-19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any
33
from typing import Callable
44
from typing import List
5+
from typing import Optional
56

67
from openapi_core.deserializing.exceptions import DeserializeError
78
from openapi_core.deserializing.parameters.datatypes import (
@@ -15,35 +16,25 @@
1516
from openapi_core.spec import Spec
1617

1718

18-
class BaseParameterDeserializer:
19-
def __init__(self, param_or_header: Spec, style: str):
20-
self.param_or_header = param_or_header
21-
self.style = style
22-
23-
def __call__(self, value: Any) -> Any:
24-
raise NotImplementedError
25-
26-
27-
class UnsupportedStyleDeserializer(BaseParameterDeserializer):
28-
def __call__(self, value: Any) -> Any:
29-
warnings.warn(f"Unsupported {self.style} style")
30-
return value
31-
32-
33-
class CallableParameterDeserializer(BaseParameterDeserializer):
19+
class CallableParameterDeserializer:
3420
def __init__(
3521
self,
3622
param_or_header: Spec,
3723
style: str,
38-
deserializer_callable: DeserializerCallable,
24+
deserializer_callable: Optional[DeserializerCallable] = None,
3925
):
40-
super().__init__(param_or_header, style)
26+
self.param_or_header = param_or_header
27+
self.style = style
4128
self.deserializer_callable = deserializer_callable
4229

4330
self.aslist = get_aslist(self.param_or_header)
4431
self.explode = get_explode(self.param_or_header)
4532

46-
def __call__(self, value: Any) -> Any:
33+
def deserialize(self, value: Any) -> Any:
34+
if self.deserializer_callable is None:
35+
warnings.warn(f"Unsupported {self.style} style")
36+
return value
37+
4738
# if "in" not defined then it's a Header
4839
if "allowEmptyValue" in self.param_or_header:
4940
warnings.warn(

openapi_core/deserializing/parameters/factories.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,9 @@
55
from openapi_core.deserializing.parameters.datatypes import (
66
DeserializerCallable,
77
)
8-
from openapi_core.deserializing.parameters.deserializers import (
9-
BaseParameterDeserializer,
10-
)
118
from openapi_core.deserializing.parameters.deserializers import (
129
CallableParameterDeserializer,
1310
)
14-
from openapi_core.deserializing.parameters.deserializers import (
15-
UnsupportedStyleDeserializer,
16-
)
1711
from openapi_core.deserializing.parameters.util import split
1812
from openapi_core.schema.parameters import get_style
1913
from openapi_core.spec import Spec
@@ -28,13 +22,10 @@ class ParameterDeserializersFactory:
2822
"deepObject": partial(re.split, pattern=r"\[|\]"),
2923
}
3024

31-
def create(self, param_or_header: Spec) -> BaseParameterDeserializer:
25+
def create(self, param_or_header: Spec) -> CallableParameterDeserializer:
3226
style = get_style(param_or_header)
3327

34-
if style not in self.PARAMETER_STYLE_DESERIALIZERS:
35-
return UnsupportedStyleDeserializer(param_or_header, style)
36-
37-
deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[style]
28+
deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS.get(style)
3829
return CallableParameterDeserializer(
3930
param_or_header, style, deserialize_callable
4031
)

openapi_core/unmarshalling/request/unmarshallers.py

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from openapi_core.deserializing.media_types import (
77
media_type_deserializers_factory,
88
)
9+
from openapi_core.deserializing.media_types.datatypes import (
10+
MediaTypeDeserializersDict,
11+
)
912
from openapi_core.deserializing.media_types.factories import (
1013
MediaTypeDeserializersFactory,
1114
)
@@ -90,6 +93,9 @@ def __init__(
9093
schema_validators_factory: Optional[SchemaValidatorsFactory] = None,
9194
format_validators: Optional[FormatValidatorsDict] = None,
9295
extra_format_validators: Optional[FormatValidatorsDict] = None,
96+
extra_media_type_deserializers: Optional[
97+
MediaTypeDeserializersDict
98+
] = None,
9399
security_provider_factory: SecurityProviderFactory = security_provider_factory,
94100
schema_unmarshallers_factory: Optional[
95101
SchemaUnmarshallersFactory
@@ -107,6 +113,7 @@ def __init__(
107113
schema_validators_factory=schema_validators_factory,
108114
format_validators=format_validators,
109115
extra_format_validators=extra_format_validators,
116+
extra_media_type_deserializers=extra_media_type_deserializers,
110117
schema_unmarshallers_factory=schema_unmarshallers_factory,
111118
format_unmarshallers=format_unmarshallers,
112119
extra_format_unmarshallers=extra_format_unmarshallers,
@@ -121,6 +128,7 @@ def __init__(
121128
schema_validators_factory=schema_validators_factory,
122129
format_validators=format_validators,
123130
extra_format_validators=extra_format_validators,
131+
extra_media_type_deserializers=extra_media_type_deserializers,
124132
security_provider_factory=security_provider_factory,
125133
)
126134

openapi_core/unmarshalling/unmarshallers.py

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
from openapi_core.deserializing.media_types import (
99
media_type_deserializers_factory,
1010
)
11+
from openapi_core.deserializing.media_types.datatypes import (
12+
MediaTypeDeserializersDict,
13+
)
1114
from openapi_core.deserializing.media_types.factories import (
1215
MediaTypeDeserializersFactory,
1316
)
@@ -42,6 +45,9 @@ def __init__(
4245
schema_validators_factory: Optional[SchemaValidatorsFactory] = None,
4346
format_validators: Optional[FormatValidatorsDict] = None,
4447
extra_format_validators: Optional[FormatValidatorsDict] = None,
48+
extra_media_type_deserializers: Optional[
49+
MediaTypeDeserializersDict
50+
] = None,
4551
schema_unmarshallers_factory: Optional[
4652
SchemaUnmarshallersFactory
4753
] = None,
@@ -61,6 +67,7 @@ def __init__(
6167
schema_validators_factory=schema_validators_factory,
6268
format_validators=format_validators,
6369
extra_format_validators=extra_format_validators,
70+
extra_media_type_deserializers=extra_media_type_deserializers,
6471
)
6572
self.schema_unmarshallers_factory = (
6673
schema_unmarshallers_factory or self.schema_unmarshallers_factory

openapi_core/validation/request/validators.py

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from openapi_core.deserializing.media_types import (
1313
media_type_deserializers_factory,
1414
)
15+
from openapi_core.deserializing.media_types.datatypes import (
16+
MediaTypeDeserializersDict,
17+
)
1518
from openapi_core.deserializing.media_types.factories import (
1619
MediaTypeDeserializersFactory,
1720
)
@@ -68,6 +71,9 @@ def __init__(
6871
schema_validators_factory: Optional[SchemaValidatorsFactory] = None,
6972
format_validators: Optional[FormatValidatorsDict] = None,
7073
extra_format_validators: Optional[FormatValidatorsDict] = None,
74+
extra_media_type_deserializers: Optional[
75+
MediaTypeDeserializersDict
76+
] = None,
7177
security_provider_factory: SecurityProviderFactory = security_provider_factory,
7278
):
7379
super().__init__(
@@ -79,6 +85,7 @@ def __init__(
7985
schema_validators_factory=schema_validators_factory,
8086
format_validators=format_validators,
8187
extra_format_validators=extra_format_validators,
88+
extra_media_type_deserializers=extra_media_type_deserializers,
8289
)
8390
self.security_provider_factory = security_provider_factory
8491

0 commit comments

Comments
 (0)