Skip to content

Commit 78ede74

Browse files
authored
Merge pull request #138 from p1c2u/feature/primitive-types-unmarshallers
Primitive types unmarshallers
2 parents 9376b2e + 9d9629b commit 78ede74

File tree

5 files changed

+160
-67
lines changed

5 files changed

+160
-67
lines changed

openapi_core/schema/media_types/models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def unmarshal(self, value, custom_formatters=None):
4747
raise InvalidMediaTypeValue(exc)
4848

4949
try:
50-
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
50+
return self.schema.validate(
51+
unmarshalled, custom_formatters=custom_formatters)
5152
except OpenAPISchemaError as exc:
5253
raise InvalidMediaTypeValue(exc)

openapi_core/schema/parameters/models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def unmarshal(self, value, custom_formatters=None):
118118
raise InvalidParameterValue(self.name, exc)
119119

120120
try:
121-
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
121+
return self.schema.validate(
122+
unmarshalled, custom_formatters=custom_formatters)
122123
except OpenAPISchemaError as exc:
123124
raise InvalidParameterValue(self.name, exc)

openapi_core/schema/schemas/exceptions.py

+14
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,17 @@ class MultipleOneOfSchema(OpenAPISchemaError):
7777

7878
def __str__(self):
7979
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)
80+
81+
82+
class UnmarshallerError(Exception):
83+
pass
84+
85+
86+
@attr.s
87+
class UnmarshallerStrictTypeError(UnmarshallerError):
88+
value = attr.ib()
89+
types = attr.ib()
90+
91+
def __str__(self):
92+
return "Value {value} is not one of types {types}".format(
93+
self.value, self.types)

openapi_core/schema/schemas/models.py

+35-65
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
1717
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
1818
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
19+
UnmarshallerStrictTypeError,
1920
)
2021
from openapi_core.schema.schemas.util import (
2122
forcebool, format_date, format_datetime, format_byte, format_uuid,
@@ -155,14 +156,19 @@ def get_all_required_properties_names(self):
155156
return set(required)
156157

157158
def get_cast_mapping(self, custom_formatters=None, strict=True):
159+
primitive_unmarshallers = self.get_primitive_unmarshallers(
160+
custom_formatters=custom_formatters)
161+
162+
primitive_unmarshallers_partial = dict(
163+
(t, functools.partial(u, type_format=self.format, strict=strict))
164+
for t, u in primitive_unmarshallers.items()
165+
)
166+
158167
pass_defaults = lambda f: functools.partial(
159168
f, custom_formatters=custom_formatters, strict=strict)
160169
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
170+
mapping.update(primitive_unmarshallers_partial)
161171
mapping.update({
162-
SchemaType.STRING: pass_defaults(self._unmarshal_string),
163-
SchemaType.BOOLEAN: pass_defaults(self._unmarshal_boolean),
164-
SchemaType.INTEGER: pass_defaults(self._unmarshal_integer),
165-
SchemaType.NUMBER: pass_defaults(self._unmarshal_number),
166172
SchemaType.ANY: pass_defaults(self._unmarshal_any),
167173
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
168174
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
@@ -184,6 +190,10 @@ def cast(self, value, custom_formatters=None, strict=True):
184190
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
185191
return self.default
186192

193+
if self.enum and value not in self.enum:
194+
raise InvalidSchemaValue(
195+
"Value {value} not in enum choices: {type}", value, self.enum)
196+
187197
cast_mapping = self.get_cast_mapping(
188198
custom_formatters=custom_formatters, strict=strict)
189199

@@ -193,6 +203,9 @@ def cast(self, value, custom_formatters=None, strict=True):
193203
cast_callable = cast_mapping[self.type]
194204
try:
195205
return cast_callable(value)
206+
except UnmarshallerStrictTypeError:
207+
raise InvalidSchemaValue(
208+
"Value {value} is not of type {type}", value, self.type)
196209
except ValueError:
197210
raise InvalidSchemaValue(
198211
"Failed to cast value {value} to type {type}", value, self.type)
@@ -207,72 +220,27 @@ def unmarshal(self, value, custom_formatters=None, strict=True):
207220
if casted is None and not self.required:
208221
return None
209222

210-
if self.enum and casted not in self.enum:
211-
raise InvalidSchemaValue(
212-
"Value {value} not in enum choices: {type}", value, self.enum)
213-
214223
return casted
215224

216-
def _unmarshal_string(self, value, custom_formatters=None, strict=True):
217-
if strict and not isinstance(value, (text_type, binary_type)):
218-
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
219-
220-
try:
221-
schema_format = SchemaFormat(self.format)
222-
except ValueError:
223-
msg = "Unsupported format {type} unmarshalling for value {value}"
224-
if custom_formatters is not None:
225-
formatstring = custom_formatters.get(self.format)
226-
if formatstring is None:
227-
raise InvalidSchemaValue(msg, value, self.format)
228-
else:
229-
raise InvalidSchemaValue(msg, value, self.format)
230-
else:
231-
if self.enum and value not in self.enum:
232-
raise InvalidSchemaValue(
233-
"Value {value} not in enum choices: {type}", value, self.enum)
234-
formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
235-
236-
try:
237-
return formatstring.unmarshal(value)
238-
except ValueError as exc:
239-
raise InvalidCustomFormatSchemaValue(
240-
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
241-
242-
def _unmarshal_integer(self, value, custom_formatters=None, strict=True):
243-
if strict and not isinstance(value, integer_types):
244-
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
245-
246-
return int(value)
247-
248-
def _unmarshal_number(self, value, custom_formatters=None, strict=True):
249-
if strict and not isinstance(value, (float, ) + integer_types):
250-
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
251-
252-
try:
253-
schema_format = SchemaFormat(self.format)
254-
except ValueError:
255-
msg = "Unsupported format {type} unmarshalling for value {value}"
256-
if custom_formatters is not None:
257-
formatnumber = custom_formatters.get(self.format)
258-
if formatnumber is None:
259-
raise InvalidSchemaValue(msg, value, self.format)
260-
else:
261-
raise InvalidSchemaValue(msg, value, self.format)
262-
else:
263-
formatnumber = self.NUMBER_FORMAT_CALLABLE_GETTER[schema_format]
225+
def get_primitive_unmarshallers(self, **options):
226+
from openapi_core.schema.schemas.unmarshallers import (
227+
StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller,
228+
NumberUnmarshaller,
229+
)
264230

265-
try:
266-
return formatnumber.unmarshal(value)
267-
except ValueError as exc:
268-
raise InvalidCustomFormatSchemaValue(
269-
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
231+
unmarshallers_classes = {
232+
SchemaType.STRING: StringUnmarshaller,
233+
SchemaType.BOOLEAN: BooleanUnmarshaller,
234+
SchemaType.INTEGER: IntegerUnmarshaller,
235+
SchemaType.NUMBER: NumberUnmarshaller,
236+
}
270237

271-
def _unmarshal_boolean(self, value, custom_formatters=None, strict=True):
272-
if strict and not isinstance(value, (bool, )):
273-
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
238+
unmarshallers = dict(
239+
(t, klass(**options))
240+
for t, klass in unmarshallers_classes.items()
241+
)
274242

275-
return forcebool(value)
243+
return unmarshallers
276244

277245
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
278246
types_resolve_order = [
@@ -301,6 +269,8 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
301269
cast_callable = cast_mapping[schema_type]
302270
try:
303271
return cast_callable(value)
272+
except UnmarshallerStrictTypeError:
273+
continue
304274
# @todo: remove ValueError when validation separated
305275
except (OpenAPISchemaError, TypeError, ValueError):
306276
continue
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from six import text_type, binary_type, integer_types
2+
3+
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
4+
from openapi_core.schema.schemas.exceptions import (
5+
InvalidSchemaValue, InvalidCustomFormatSchemaValue,
6+
OpenAPISchemaError, MultipleOneOfSchema, NoOneOfSchema,
7+
InvalidSchemaProperty,
8+
UnmarshallerStrictTypeError,
9+
)
10+
from openapi_core.schema.schemas.util import (
11+
forcebool, format_date, format_datetime, format_byte, format_uuid,
12+
format_number,
13+
)
14+
15+
16+
class StrictUnmarshaller(object):
17+
18+
STRICT_TYPES = ()
19+
20+
def __call__(self, value, type_format=SchemaFormat.NONE, strict=True):
21+
if self.STRICT_TYPES and strict and not isinstance(
22+
value, self.STRICT_TYPES):
23+
raise UnmarshallerStrictTypeError(value, self.STRICT_TYPES)
24+
25+
return value
26+
27+
28+
class PrimitiveTypeUnmarshaller(StrictUnmarshaller):
29+
30+
FORMATTERS = {
31+
SchemaFormat.NONE: lambda x: x,
32+
}
33+
34+
def __init__(self, custom_formatters=None):
35+
if custom_formatters is None:
36+
custom_formatters = {}
37+
self.custom_formatters = custom_formatters
38+
39+
def __call__(self, value, type_format=SchemaFormat.NONE, strict=True):
40+
value = super(PrimitiveTypeUnmarshaller, self).__call__(
41+
value, type_format=type_format, strict=strict)
42+
43+
try:
44+
schema_format = SchemaFormat(type_format)
45+
except ValueError:
46+
formatter = self.custom_formatters.get(type_format)
47+
else:
48+
formatters = self.get_formatters()
49+
formatter = formatters.get(schema_format)
50+
51+
if formatter is None:
52+
raise InvalidSchemaValue(
53+
"Unsupported format {type} unmarshalling "
54+
"for value {value}",
55+
value, type_format)
56+
57+
try:
58+
return formatter(value)
59+
except ValueError as exc:
60+
raise InvalidCustomFormatSchemaValue(
61+
"Failed to format value {value} to format {type}: {exception}",
62+
value, type_format, exc)
63+
64+
def get_formatters(self):
65+
return self.FORMATTERS
66+
67+
68+
class StringUnmarshaller(PrimitiveTypeUnmarshaller):
69+
70+
STRICT_TYPES = (text_type, binary_type)
71+
FORMATTERS = {
72+
SchemaFormat.NONE: text_type,
73+
SchemaFormat.PASSWORD: text_type,
74+
SchemaFormat.DATE: format_date,
75+
SchemaFormat.DATETIME: format_datetime,
76+
SchemaFormat.BINARY: binary_type,
77+
SchemaFormat.UUID: format_uuid,
78+
SchemaFormat.BYTE: format_byte,
79+
}
80+
81+
82+
class IntegerUnmarshaller(PrimitiveTypeUnmarshaller):
83+
84+
STRICT_TYPES = integer_types
85+
FORMATTERS = {
86+
SchemaFormat.NONE: int,
87+
SchemaFormat.INT32: int,
88+
SchemaFormat.INT64: int,
89+
}
90+
91+
92+
class NumberUnmarshaller(PrimitiveTypeUnmarshaller):
93+
94+
STRICT_TYPES = (float, ) + integer_types
95+
FORMATTERS = {
96+
SchemaFormat.NONE: format_number,
97+
SchemaFormat.FLOAT: float,
98+
SchemaFormat.DOUBLE: float,
99+
}
100+
101+
102+
class BooleanUnmarshaller(PrimitiveTypeUnmarshaller):
103+
104+
STRICT_TYPES = (bool, )
105+
FORMATTERS = {
106+
SchemaFormat.NONE: forcebool,
107+
}

0 commit comments

Comments
 (0)