Skip to content

Commit b4cff7b

Browse files
authored
Merge pull request #347 from p1c2u/refactor/deserializing-refactor
Deserializing refactor
2 parents f3d7d73 + a7125ca commit b4cff7b

File tree

12 files changed

+143
-39
lines changed

12 files changed

+143
-39
lines changed
Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
from dataclasses import dataclass
2-
31
from openapi_core.exceptions import OpenAPIError
42

53

6-
@dataclass
74
class DeserializeError(OpenAPIError):
85
"""Deserialize operation error"""
9-
value: str
10-
style: str
11-
12-
def __str__(self):
13-
return "Failed to deserialize value {value} with style {style}".format(
14-
value=self.value, style=self.style)
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
1-
from openapi_core.deserializing.exceptions import DeserializeError
1+
import warnings
22

3+
from openapi_core.deserializing.media_types.exceptions import (
4+
MediaTypeDeserializeError,
5+
)
36

4-
class PrimitiveDeserializer:
7+
8+
class BaseMediaTypeDeserializer:
9+
10+
def __init__(self, mimetype):
11+
self.mimetype = mimetype
12+
13+
def __call__(self, value):
14+
raise NotImplementedError
15+
16+
17+
class UnsupportedMimetypeDeserializer(BaseMediaTypeDeserializer):
18+
19+
def __call__(self, value):
20+
warnings.warn(f"Unsupported {self.mimetype} mimetype")
21+
return value
22+
23+
24+
class CallableMediaTypeDeserializer(BaseMediaTypeDeserializer):
525

626
def __init__(self, mimetype, deserializer_callable):
727
self.mimetype = mimetype
@@ -11,4 +31,4 @@ def __call__(self, value):
1131
try:
1232
return self.deserializer_callable(value)
1333
except (ValueError, TypeError, AttributeError):
14-
raise DeserializeError(value, self.mimetype)
34+
raise MediaTypeDeserializeError(self.mimetype, value)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from dataclasses import dataclass
2+
3+
from openapi_core.deserializing.exceptions import DeserializeError
4+
5+
6+
@dataclass
7+
class MediaTypeDeserializeError(DeserializeError):
8+
"""Media type deserialize operation error"""
9+
mimetype: str
10+
value: str
11+
12+
def __str__(self):
13+
return (
14+
"Failed to deserialize value with {mimetype} mimetype: {value}"
15+
).format(value=self.value, mimetype=self.mimetype)

openapi_core/deserializing/media_types/factories.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
)
66

77
from openapi_core.deserializing.media_types.deserializers import (
8-
PrimitiveDeserializer,
8+
CallableMediaTypeDeserializer, UnsupportedMimetypeDeserializer,
99
)
1010

1111

@@ -25,10 +25,14 @@ def __init__(self, custom_deserializers=None):
2525
def create(self, mimetype):
2626
deserialize_callable = self.get_deserializer_callable(
2727
mimetype)
28-
return PrimitiveDeserializer(
28+
29+
if deserialize_callable is None:
30+
return UnsupportedMimetypeDeserializer(mimetype)
31+
32+
return CallableMediaTypeDeserializer(
2933
mimetype, deserialize_callable)
3034

3135
def get_deserializer_callable(self, mimetype):
3236
if mimetype in self.custom_deserializers:
3337
return self.custom_deserializers[mimetype]
34-
return self.MEDIA_TYPE_DESERIALIZERS.get(mimetype, lambda x: x)
38+
return self.MEDIA_TYPE_DESERIALIZERS.get(mimetype)

openapi_core/deserializing/parameters/deserializers.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,36 @@
22

33
from openapi_core.deserializing.exceptions import DeserializeError
44
from openapi_core.deserializing.parameters.exceptions import (
5-
EmptyParameterValue,
5+
EmptyQueryParameterValue,
66
)
7-
from openapi_core.schema.parameters import get_aslist, get_explode, get_style
7+
from openapi_core.schema.parameters import get_aslist, get_explode
88

99

10-
class PrimitiveDeserializer:
10+
class BaseParameterDeserializer:
1111

12-
def __init__(self, param_or_header, deserializer_callable):
12+
def __init__(self, param_or_header, style):
1313
self.param_or_header = param_or_header
14+
self.style = style
15+
16+
def __call__(self, value):
17+
raise NotImplementedError
18+
19+
20+
class UnsupportedStyleDeserializer(BaseParameterDeserializer):
21+
22+
def __call__(self, value):
23+
warnings.warn(f"Unsupported {self.style} style")
24+
return value
25+
26+
27+
class CallableParameterDeserializer(BaseParameterDeserializer):
28+
29+
def __init__(self, param_or_header, style, deserializer_callable):
30+
super().__init__(param_or_header, style)
1431
self.deserializer_callable = deserializer_callable
1532

1633
self.aslist = get_aslist(self.param_or_header)
1734
self.explode = get_explode(self.param_or_header)
18-
self.style = get_style(self.param_or_header)
1935

2036
def __call__(self, value):
2137
# if "in" not defined then it's a Header
@@ -29,12 +45,12 @@ def __call__(self, value):
2945
location_name = self.param_or_header.getkey('in', 'header')
3046
if (location_name == 'query' and value == "" and
3147
not allow_empty_values):
32-
name = self.param_or_header.getkey('name', 'header')
33-
raise EmptyParameterValue(value, self.style, name)
48+
name = self.param_or_header['name']
49+
raise EmptyQueryParameterValue(name)
3450

3551
if not self.aslist or self.explode:
3652
return value
3753
try:
3854
return self.deserializer_callable(value)
3955
except (ValueError, TypeError, AttributeError):
40-
raise DeserializeError(value, self.style)
56+
raise DeserializeError(location_name, self.style, value)

openapi_core/deserializing/parameters/exceptions.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,32 @@
44

55

66
@dataclass
7-
class EmptyParameterValue(DeserializeError):
7+
class BaseParameterDeserializeError(DeserializeError):
8+
"""Base parameter deserialize operation error"""
9+
location: str
10+
11+
12+
@dataclass
13+
class ParameterDeserializeError(BaseParameterDeserializeError):
14+
"""Parameter deserialize operation error"""
15+
style: str
16+
value: str
17+
18+
def __str__(self):
19+
return (
20+
"Failed to deserialize value "
21+
"of {location} parameter with style {style}: {value}"
22+
).format(location=self.location, style=self.style, value=self.value)
23+
24+
25+
@dataclass(init=False)
26+
class EmptyQueryParameterValue(BaseParameterDeserializeError):
827
name: str
928

29+
def __init__(self, name):
30+
super().__init__(location='query')
31+
self.name = name
32+
1033
def __str__(self):
11-
return "Value of parameter cannot be empty: {0}".format(self.name)
34+
return "Value of {name} {location} parameter cannot be empty".format(
35+
name=self.name, location=self.location)
Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
from functools import partial
2+
13
from openapi_core.deserializing.parameters.deserializers import (
2-
PrimitiveDeserializer,
4+
CallableParameterDeserializer, UnsupportedStyleDeserializer,
35
)
6+
from openapi_core.deserializing.parameters.util import split
47
from openapi_core.schema.parameters import get_style
58

69

710
class ParameterDeserializersFactory:
811

912
PARAMETER_STYLE_DESERIALIZERS = {
10-
'form': lambda x: x.split(','),
11-
'simple': lambda x: x.split(','),
12-
'spaceDelimited': lambda x: x.split(' '),
13-
'pipeDelimited': lambda x: x.split('|'),
13+
'form': partial(split, separator=','),
14+
'simple': partial(split, separator=','),
15+
'spaceDelimited': partial(split, separator=' '),
16+
'pipeDelimited': partial(split, separator='|'),
1417
}
1518

16-
def create(self, param):
17-
style = get_style(param)
19+
def create(self, param_or_header):
20+
style = get_style(param_or_header)
21+
22+
if style not in self.PARAMETER_STYLE_DESERIALIZERS:
23+
return UnsupportedStyleDeserializer(param_or_header, style)
1824

1925
deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[style]
20-
return PrimitiveDeserializer(param, deserialize_callable)
26+
return CallableParameterDeserializer(
27+
param_or_header, style, deserialize_callable)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def split(value, separator=','):
2+
return value.split(separator)

tests/integration/validation/test_petstore.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from openapi_core.casting.schemas.exceptions import CastError
99
from openapi_core.deserializing.exceptions import DeserializeError
1010
from openapi_core.deserializing.parameters.exceptions import (
11-
EmptyParameterValue,
11+
EmptyQueryParameterValue,
1212
)
1313
from openapi_core.extensions.models.models import BaseModel
1414
from openapi_core.exceptions import (
@@ -375,7 +375,7 @@ def test_get_pets_empty_value(self, spec):
375375
path_pattern=path_pattern, args=query_params,
376376
)
377377

378-
with pytest.raises(EmptyParameterValue):
378+
with pytest.raises(EmptyQueryParameterValue):
379379
spec_validate_parameters(spec, request)
380380
body = spec_validate_body(spec, request)
381381

tests/integration/validation/test_validators.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import pytest
44

55
from openapi_core.casting.schemas.exceptions import CastError
6-
from openapi_core.deserializing.exceptions import DeserializeError
6+
from openapi_core.deserializing.media_types.exceptions import (
7+
MediaTypeDeserializeError,
8+
)
79
from openapi_core.extensions.models.models import BaseModel
810
from openapi_core.exceptions import (
911
MissingRequiredParameter, MissingRequiredRequestBody,
@@ -572,7 +574,7 @@ def test_invalid_media_type(self, validator):
572574
result = validator.validate(request, response)
573575

574576
assert len(result.errors) == 1
575-
assert type(result.errors[0]) == DeserializeError
577+
assert type(result.errors[0]) == MediaTypeDeserializeError
576578
assert result.data is None
577579
assert result.headers == {}
578580

0 commit comments

Comments
 (0)