Skip to content

Commit 5905ff8

Browse files
committed
Separate cast and unmarshal
1 parent 0e30b71 commit 5905ff8

File tree

2 files changed

+69
-31
lines changed

2 files changed

+69
-31
lines changed

openapi_core/schema/parameters/models.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,16 @@ def unmarshal(self, value, custom_formatters=None):
108108
except (ValueError, AttributeError) as exc:
109109
raise InvalidParameterValue(self.name, exc)
110110

111+
try:
112+
casted = self.schema.cast(deserialized)
113+
except OpenAPISchemaError as exc:
114+
raise InvalidParameterValue(self.name, exc)
115+
111116
try:
112117
unmarshalled = self.schema.unmarshal(
113-
deserialized,
118+
casted,
114119
custom_formatters=custom_formatters,
115-
strict=False,
120+
strict=True,
116121
)
117122
except OpenAPISchemaError as exc:
118123
raise InvalidParameterValue(self.name, exc)

openapi_core/schema/schemas/models.py

+62-29
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ class Format(object):
3838
class Schema(object):
3939
"""Represents an OpenAPI Schema."""
4040

41-
DEFAULT_CAST_CALLABLE_GETTER = {
41+
TYPE_CAST_CALLABLE_GETTER = {
42+
SchemaType.INTEGER: int,
43+
SchemaType.NUMBER: float,
44+
SchemaType.BOOLEAN: forcebool,
45+
}
46+
47+
DEFAULT_UNMARSHAL_CALLABLE_GETTER = {
4248
}
4349

4450
STRING_FORMAT_CALLABLE_GETTER = {
@@ -155,7 +161,46 @@ def get_all_required_properties_names(self):
155161

156162
return set(required)
157163

158-
def get_cast_mapping(self, custom_formatters=None, strict=True):
164+
def are_additional_properties_allowed(self, one_of_schema=None):
165+
return (
166+
(self.additional_properties is not False) and
167+
(one_of_schema is None or
168+
one_of_schema.additional_properties is not False)
169+
)
170+
171+
def get_cast_mapping(self):
172+
mapping = self.TYPE_CAST_CALLABLE_GETTER.copy()
173+
mapping.update({
174+
SchemaType.ARRAY: self._cast_collection,
175+
})
176+
177+
return defaultdict(lambda: lambda x: x, mapping)
178+
179+
def cast(self, value):
180+
"""Cast value from string to schema type"""
181+
if value is None:
182+
return value
183+
184+
cast_mapping = self.get_cast_mapping()
185+
186+
cast_callable = cast_mapping[self.type]
187+
try:
188+
return cast_callable(value)
189+
except ValueError:
190+
raise InvalidSchemaValue(
191+
"Failed to cast value {value} to type {type}", value, self.type)
192+
193+
def _cast_collection(self, value):
194+
if not isinstance(value, (list, tuple)):
195+
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
196+
197+
if self.items is None:
198+
raise UndefinedItemsSchema(self.type)
199+
200+
f = functools.partial(self.items.cast)
201+
return list(map(f, value))
202+
203+
def get_unmarshal_mapping(self, custom_formatters=None, strict=True):
159204
primitive_unmarshallers = self.get_primitive_unmarshallers(
160205
custom_formatters=custom_formatters)
161206

@@ -166,7 +211,7 @@ def get_cast_mapping(self, custom_formatters=None, strict=True):
166211

167212
pass_defaults = lambda f: functools.partial(
168213
f, custom_formatters=custom_formatters, strict=strict)
169-
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
214+
mapping = self.DEFAULT_UNMARSHAL_CALLABLE_GETTER.copy()
170215
mapping.update(primitive_unmarshallers_partial)
171216
mapping.update({
172217
SchemaType.ANY: pass_defaults(self._unmarshal_any),
@@ -176,15 +221,10 @@ def get_cast_mapping(self, custom_formatters=None, strict=True):
176221

177222
return defaultdict(lambda: lambda x: x, mapping)
178223

179-
def are_additional_properties_allowed(self, one_of_schema=None):
180-
return (
181-
(self.additional_properties is not False) and
182-
(one_of_schema is None or
183-
one_of_schema.additional_properties is not False)
184-
)
185-
186-
def cast(self, value, custom_formatters=None, strict=True):
187-
"""Cast value to schema type"""
224+
def unmarshal(self, value, custom_formatters=None, strict=True):
225+
"""Unmarshal parameter from the value."""
226+
if self.deprecated:
227+
warnings.warn("The schema is deprecated", DeprecationWarning)
188228
if value is None:
189229
if not self.nullable:
190230
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
@@ -194,33 +234,26 @@ def cast(self, value, custom_formatters=None, strict=True):
194234
raise InvalidSchemaValue(
195235
"Value {value} not in enum choices: {type}", value, self.enum)
196236

197-
cast_mapping = self.get_cast_mapping(
237+
unmarshal_mapping = self.get_unmarshal_mapping(
198238
custom_formatters=custom_formatters, strict=strict)
199239

200240
if self.type is not SchemaType.STRING and value == '':
201241
return None
202242

203-
cast_callable = cast_mapping[self.type]
243+
unmarshal_callable = unmarshal_mapping[self.type]
204244
try:
205-
return cast_callable(value)
245+
unmarshalled = unmarshal_callable(value)
206246
except UnmarshallerStrictTypeError:
207247
raise InvalidSchemaValue(
208248
"Value {value} is not of type {type}", value, self.type)
209249
except ValueError:
210250
raise InvalidSchemaValue(
211251
"Failed to cast value {value} to type {type}", value, self.type)
212252

213-
def unmarshal(self, value, custom_formatters=None, strict=True):
214-
"""Unmarshal parameter from the value."""
215-
if self.deprecated:
216-
warnings.warn("The schema is deprecated", DeprecationWarning)
217-
218-
casted = self.cast(value, custom_formatters=custom_formatters, strict=strict)
219-
220-
if casted is None and not self.required:
253+
if unmarshalled is None and not self.required:
221254
return None
222255

223-
return casted
256+
return unmarshalled
224257

225258
def get_primitive_unmarshallers(self, **options):
226259
from openapi_core.schema.schemas.unmarshallers import (
@@ -247,28 +280,28 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
247280
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
248281
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
249282
]
250-
cast_mapping = self.get_cast_mapping()
283+
unmarshal_mapping = self.get_unmarshal_mapping()
251284
if self.one_of:
252285
result = None
253286
for subschema in self.one_of:
254287
try:
255-
casted = subschema.cast(value, custom_formatters)
288+
unmarshalled = subschema.unmarshal(value, custom_formatters)
256289
except (OpenAPISchemaError, TypeError, ValueError):
257290
continue
258291
else:
259292
if result is not None:
260293
raise MultipleOneOfSchema(self.type)
261-
result = casted
294+
result = unmarshalled
262295

263296
if result is None:
264297
raise NoOneOfSchema(self.type)
265298

266299
return result
267300
else:
268301
for schema_type in types_resolve_order:
269-
cast_callable = cast_mapping[schema_type]
302+
unmarshal_callable = unmarshal_mapping[schema_type]
270303
try:
271-
return cast_callable(value)
304+
return unmarshal_callable(value)
272305
except UnmarshallerStrictTypeError:
273306
continue
274307
# @todo: remove ValueError when validation separated

0 commit comments

Comments
 (0)