Skip to content

Commit 8048578

Browse files
authored
Merge pull request #191 from p1c2u/refactor/move-cast-out-of-schema-models
Move deserialize/cast out of schema models
2 parents 2c1a6c1 + 4044483 commit 8048578

File tree

27 files changed

+352
-285
lines changed

27 files changed

+352
-285
lines changed

openapi_core/casting/schemas/casters.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
from openapi_core.casting.schemas.exceptions import CastError
12
from openapi_core.schema.schemas.types import NoValue
23

34

45
class PrimitiveCaster(object):
56

6-
def __init__(self, caster_callable):
7+
def __init__(self, schema, caster_callable):
8+
self.schema = schema
79
self.caster_callable = caster_callable
810

911
def __call__(self, value):
1012
if value in (None, NoValue):
1113
return value
12-
return self.caster_callable(value)
14+
try:
15+
return self.caster_callable(value)
16+
except (ValueError, TypeError):
17+
raise CastError(value, self.schema.type.value)
1318

1419

1520
class DummyCaster(object):

openapi_core/casting/schemas/factories.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@
88

99
class SchemaCastersFactory(object):
1010

11-
DUMMY_CASTER = DummyCaster()
11+
DUMMY_CASTERS = [
12+
SchemaType.STRING, SchemaType.OBJECT, SchemaType.ANY,
13+
]
1214
PRIMITIVE_CASTERS = {
13-
SchemaType.STRING: DUMMY_CASTER,
14-
SchemaType.INTEGER: PrimitiveCaster(int),
15-
SchemaType.NUMBER: PrimitiveCaster(float),
16-
SchemaType.BOOLEAN: PrimitiveCaster(forcebool),
17-
SchemaType.OBJECT: DUMMY_CASTER,
18-
SchemaType.ANY: DUMMY_CASTER,
15+
SchemaType.INTEGER: int,
16+
SchemaType.NUMBER: float,
17+
SchemaType.BOOLEAN: forcebool,
1918
}
2019
COMPLEX_CASTERS = {
2120
SchemaType.ARRAY: ArrayCaster,
2221
}
2322

2423
def create(self, schema):
25-
if schema.type in self.PRIMITIVE_CASTERS:
26-
return self.PRIMITIVE_CASTERS[schema.type]
24+
if schema.type in self.DUMMY_CASTERS:
25+
return DummyCaster()
26+
elif schema.type in self.PRIMITIVE_CASTERS:
27+
caster_callable = self.PRIMITIVE_CASTERS[schema.type]
28+
return PrimitiveCaster(schema, caster_callable)
2729
elif schema.type in self.COMPLEX_CASTERS:
2830
caster_class = self.COMPLEX_CASTERS[schema.type]
29-
return caster_class(schema=schema, casters_factory=self)
31+
return caster_class(schema, self)

openapi_core/deserializing/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import attr
2+
3+
from openapi_core.exceptions import OpenAPIError
4+
5+
6+
@attr.s(hash=True)
7+
class DeserializeError(OpenAPIError):
8+
"""Deserialize operation error"""
9+
value = attr.ib()
10+
style = attr.ib()
11+
12+
def __str__(self):
13+
return "Failed to deserialize value {value} with style {style}".format(
14+
value=self.value, style=self.style)

openapi_core/deserializing/media_types/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from openapi_core.deserializing.exceptions import DeserializeError
2+
3+
4+
class PrimitiveDeserializer(object):
5+
6+
def __init__(self, mimetype, deserializer_callable):
7+
self.mimetype = mimetype
8+
self.deserializer_callable = deserializer_callable
9+
10+
def __call__(self, value):
11+
try:
12+
return self.deserializer_callable(value)
13+
except (ValueError, TypeError, AttributeError):
14+
raise DeserializeError(value, self.mimetype)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from openapi_core.schema.media_types.util import json_loads
2+
3+
from openapi_core.deserializing.media_types.deserializers import (
4+
PrimitiveDeserializer,
5+
)
6+
7+
8+
class MediaTypeDeserializersFactory(object):
9+
10+
MEDIA_TYPE_DESERIALIZERS = {
11+
'application/json': json_loads,
12+
}
13+
14+
def create(self, media_type):
15+
deserialize_callable = self.MEDIA_TYPE_DESERIALIZERS.get(
16+
media_type.mimetype, lambda x: x)
17+
return PrimitiveDeserializer(
18+
media_type.mimetype, deserialize_callable)

openapi_core/deserializing/parameters/__init__.py

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from openapi_core.deserializing.exceptions import DeserializeError
2+
from openapi_core.deserializing.parameters.exceptions import (
3+
EmptyParameterValue,
4+
)
5+
from openapi_core.schema.parameters.enums import ParameterLocation
6+
7+
8+
class PrimitiveDeserializer(object):
9+
10+
def __init__(self, param, deserializer_callable):
11+
self.param = param
12+
self.deserializer_callable = deserializer_callable
13+
14+
def __call__(self, value):
15+
if (self.param.location == ParameterLocation.QUERY and value == "" and
16+
not self.param.allow_empty_value):
17+
raise EmptyParameterValue(
18+
value, self.param.style, self.param.name)
19+
20+
if not self.param.aslist or self.param.explode:
21+
return value
22+
try:
23+
return self.deserializer_callable(value)
24+
except (ValueError, TypeError, AttributeError):
25+
raise DeserializeError(value, self.param.style)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import attr
2+
3+
from openapi_core.deserializing.exceptions import DeserializeError
4+
5+
6+
@attr.s(hash=True)
7+
class EmptyParameterValue(DeserializeError):
8+
name = attr.ib()
9+
10+
def __str__(self):
11+
return "Value of parameter cannot be empty: {0}".format(self.name)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import warnings
2+
3+
from openapi_core.deserializing.parameters.deserializers import (
4+
PrimitiveDeserializer,
5+
)
6+
from openapi_core.schema.parameters.enums import ParameterStyle
7+
8+
9+
class ParameterDeserializersFactory(object):
10+
11+
PARAMETER_STYLE_DESERIALIZERS = {
12+
ParameterStyle.FORM: lambda x: x.split(','),
13+
ParameterStyle.SIMPLE: lambda x: x.split(','),
14+
ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '),
15+
ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'),
16+
}
17+
18+
def create(self, param):
19+
if param.deprecated:
20+
warnings.warn(
21+
"{0} parameter is deprecated".format(param.name),
22+
DeprecationWarning,
23+
)
24+
25+
deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[param.style]
26+
return PrimitiveDeserializer(param, deserialize_callable)

openapi_core/schema/media_types/exceptions.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,6 @@ class OpenAPIMediaTypeError(OpenAPIMappingError):
77
pass
88

99

10-
@attr.s(hash=True)
11-
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
12-
original_exception = attr.ib()
13-
14-
def __str__(self):
15-
return "Mimetype invalid: {0}".format(self.original_exception)
16-
17-
1810
@attr.s(hash=True)
1911
class InvalidContentType(OpenAPIMediaTypeError):
2012
mimetype = attr.ib()
Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
"""OpenAPI core media types models module"""
2-
from collections import defaultdict
3-
4-
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
5-
from openapi_core.schema.media_types.util import json_loads
6-
from openapi_core.casting.schemas.exceptions import CastError
7-
8-
9-
MEDIA_TYPE_DESERIALIZERS = {
10-
'application/json': json_loads,
11-
}
122

133

144
class MediaType(object):
@@ -18,29 +8,3 @@ def __init__(self, mimetype, schema=None, example=None):
188
self.mimetype = mimetype
199
self.schema = schema
2010
self.example = example
21-
22-
def get_deserializer_mapping(self):
23-
mapping = MEDIA_TYPE_DESERIALIZERS.copy()
24-
return defaultdict(lambda: lambda x: x, mapping)
25-
26-
def get_dererializer(self):
27-
mapping = self.get_deserializer_mapping()
28-
return mapping[self.mimetype]
29-
30-
def deserialize(self, value):
31-
deserializer = self.get_dererializer()
32-
return deserializer(value)
33-
34-
def cast(self, value):
35-
try:
36-
deserialized = self.deserialize(value)
37-
except ValueError as exc:
38-
raise InvalidMediaTypeValue(exc)
39-
40-
if not self.schema:
41-
return deserialized
42-
43-
try:
44-
return self.schema.cast(deserialized)
45-
except CastError as exc:
46-
raise InvalidMediaTypeValue(exc)

openapi_core/schema/parameters/exceptions.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,3 @@ class MissingRequiredParameter(MissingParameterError):
2727

2828
def __str__(self):
2929
return "Missing required parameter: {0}".format(self.name)
30-
31-
32-
@attr.s(hash=True)
33-
class EmptyParameterValue(OpenAPIParameterError):
34-
name = attr.ib()
35-
36-
def __str__(self):
37-
return "Value of parameter cannot be empty: {0}".format(self.name)
38-
39-
40-
@attr.s(hash=True)
41-
class InvalidParameterValue(OpenAPIParameterError):
42-
name = attr.ib()
43-
original_exception = attr.ib()
44-
45-
def __str__(self):
46-
return "Invalid parameter value for `{0}`: {1}".format(
47-
self.name, self.original_exception)
Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,17 @@
11
"""OpenAPI core parameters models module"""
22
import logging
3-
import warnings
43

54
from openapi_core.schema.parameters.enums import (
65
ParameterLocation, ParameterStyle,
76
)
8-
from openapi_core.schema.parameters.exceptions import (
9-
MissingRequiredParameter, MissingParameter, InvalidParameterValue,
10-
EmptyParameterValue,
11-
)
127
from openapi_core.schema.schemas.enums import SchemaType
13-
from openapi_core.casting.schemas.exceptions import CastError
148

159
log = logging.getLogger(__name__)
1610

1711

1812
class Parameter(object):
1913
"""Represents an OpenAPI operation Parameter."""
2014

21-
PARAMETER_STYLE_DESERIALIZERS = {
22-
ParameterStyle.FORM: lambda x: x.split(','),
23-
ParameterStyle.SIMPLE: lambda x: x.split(','),
24-
ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '),
25-
ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'),
26-
}
27-
2815
def __init__(
2916
self, name, location, schema=None, required=False,
3017
deprecated=False, allow_empty_value=False,
@@ -61,53 +48,3 @@ def default_style(self):
6148
@property
6249
def default_explode(self):
6350
return self.style == ParameterStyle.FORM
64-
65-
def get_dererializer(self):
66-
return self.PARAMETER_STYLE_DESERIALIZERS[self.style]
67-
68-
def deserialize(self, value):
69-
if not self.aslist or self.explode:
70-
return value
71-
72-
deserializer = self.get_dererializer()
73-
return deserializer(value)
74-
75-
def get_raw_value(self, request):
76-
location = request.parameters[self.location.value]
77-
78-
if self.name not in location:
79-
if self.required:
80-
raise MissingRequiredParameter(self.name)
81-
82-
raise MissingParameter(self.name)
83-
84-
if self.aslist and self.explode:
85-
if hasattr(location, 'getall'):
86-
return location.getall(self.name)
87-
return location.getlist(self.name)
88-
89-
return location[self.name]
90-
91-
def cast(self, value):
92-
if self.deprecated:
93-
warnings.warn(
94-
"{0} parameter is deprecated".format(self.name),
95-
DeprecationWarning,
96-
)
97-
98-
if (self.location == ParameterLocation.QUERY and value == "" and
99-
not self.allow_empty_value):
100-
raise EmptyParameterValue(self.name)
101-
102-
try:
103-
deserialized = self.deserialize(value)
104-
except (ValueError, AttributeError) as exc:
105-
raise InvalidParameterValue(self.name, exc)
106-
107-
if not self.schema:
108-
return deserialized
109-
110-
try:
111-
return self.schema.cast(deserialized)
112-
except CastError as exc:
113-
raise InvalidParameterValue(self.name, exc)

openapi_core/schema/request_bodies/models.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from openapi_core.schema.content.exceptions import MimeTypeNotFound
33
from openapi_core.schema.content.models import Content
44
from openapi_core.schema.media_types.exceptions import InvalidContentType
5-
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
65

76

87
class RequestBody(object):
@@ -17,8 +16,3 @@ def __getitem__(self, mimetype):
1716
return self.content[mimetype]
1817
except MimeTypeNotFound:
1918
raise InvalidContentType(mimetype)
20-
21-
def get_value(self, request):
22-
if not request.body and self.required:
23-
raise MissingRequestBody(request)
24-
return request.body

openapi_core/schema/responses/models.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from openapi_core.schema.content.exceptions import MimeTypeNotFound
33
from openapi_core.schema.content.models import Content
44
from openapi_core.schema.media_types.exceptions import InvalidContentType
5-
from openapi_core.schema.responses.exceptions import MissingResponseContent
65

76

87
class Response(object):
@@ -24,9 +23,3 @@ def get_content_type(self, mimetype):
2423
return self.content[mimetype]
2524
except MimeTypeNotFound:
2625
raise InvalidContentType(mimetype)
27-
28-
def get_value(self, response):
29-
if not response.data:
30-
raise MissingResponseContent(response)
31-
32-
return response.data

openapi_core/schema/schemas/models.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,3 @@ def get_all_properties(self):
9090
def get_all_properties_names(self):
9191
all_properties = self.get_all_properties()
9292
return set(all_properties.keys())
93-
94-
def cast(self, value):
95-
"""Cast value from string to schema type"""
96-
from openapi_core.casting.schemas.exceptions import CastError
97-
from openapi_core.casting.schemas.factories import SchemaCastersFactory
98-
casters_factory = SchemaCastersFactory()
99-
caster = casters_factory.create(self)
100-
try:
101-
return caster(value)
102-
except (ValueError, TypeError):
103-
raise CastError(value, self.type)

0 commit comments

Comments
 (0)