Skip to content

Commit d6099fe

Browse files
committed
Move Unmarshallers to separate subpackage
1 parent 57cc70a commit d6099fe

File tree

12 files changed

+411
-128
lines changed

12 files changed

+411
-128
lines changed

openapi_core/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""OpenAPI core exceptions module"""
2+
3+
4+
class OpenAPIError(Exception):
5+
pass

openapi_core/schema/exceptions.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
"""OpenAPI core schema exceptions module"""
2-
3-
4-
class OpenAPIError(Exception):
5-
pass
2+
from openapi_core.exceptions import OpenAPIError
63

74

85
class OpenAPIMappingError(OpenAPIError):

openapi_core/schema/extensions/__init__.py

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""OpenAPI core extensions generators module"""
2+
from six import iteritems
3+
4+
from openapi_core.schema.extensions.models import Extension
5+
6+
7+
class ExtensionsGenerator(object):
8+
9+
def __init__(self, dereferencer):
10+
self.dereferencer = dereferencer
11+
12+
def generate(self, item_spec):
13+
for field_name, value in iteritems(item_spec):
14+
if not field_name.startswith('x-'):
15+
continue
16+
yield field_name, Extension(field_name, value)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""OpenAPI core extensions models module"""
2+
3+
4+
class Extension(object):
5+
"""Represents an OpenAPI Extension."""
6+
7+
def __init__(self, field_name, value=None):
8+
self.field_name = field_name
9+
self.value = value

openapi_core/schema/schemas/factories.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from six import iteritems
55

66
from openapi_core.compat import lru_cache
7+
from openapi_core.schema.extensions.generators import ExtensionsGenerator
78
from openapi_core.schema.properties.generators import PropertiesGenerator
89
from openapi_core.schema.schemas.models import Schema
910
from openapi_core.schema.schemas.types import Contribution
@@ -21,7 +22,6 @@ def create(self, schema_spec):
2122

2223
schema_type = schema_deref.get('type', None)
2324
schema_format = schema_deref.get('format')
24-
model = schema_deref.get('x-model', None)
2525
required = schema_deref.get('required', False)
2626
default = schema_deref.get('default', None)
2727
properties_spec = schema_deref.get('properties', None)
@@ -47,6 +47,8 @@ def create(self, schema_spec):
4747
min_properties = schema_deref.get('minProperties', None)
4848
max_properties = schema_deref.get('maxProperties', None)
4949

50+
extensions = self.extensions_generator.generate(schema_deref)
51+
5052
properties = None
5153
if properties_spec:
5254
properties = self.properties_generator.generate(properties_spec)
@@ -68,7 +70,7 @@ def create(self, schema_spec):
6870
additional_properties = self.create(additional_properties_spec)
6971

7072
return Schema(
71-
schema_type=schema_type, model=model, properties=properties,
73+
schema_type=schema_type, properties=properties,
7274
items=items, schema_format=schema_format, required=required,
7375
default=default, nullable=nullable, enum=enum,
7476
deprecated=deprecated, all_of=all_of, one_of=one_of,
@@ -79,9 +81,15 @@ def create(self, schema_spec):
7981
exclusive_maximum=exclusive_maximum,
8082
exclusive_minimum=exclusive_minimum,
8183
min_properties=min_properties, max_properties=max_properties,
84+
extensions=extensions,
8285
_source=schema_deref,
8386
)
8487

88+
@property
89+
@lru_cache()
90+
def extensions_generator(self):
91+
return ExtensionsGenerator(self.dereferencer)
92+
8593
@property
8694
@lru_cache()
8795
def properties_generator(self):

openapi_core/schema/schemas/models.py

Lines changed: 11 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ class Schema(object):
4646
}
4747

4848
def __init__(
49-
self, schema_type=None, model=None, properties=None, items=None,
49+
self, schema_type=None, properties=None, items=None,
5050
schema_format=None, required=None, default=None, nullable=False,
5151
enum=None, deprecated=False, all_of=None, one_of=None,
5252
additional_properties=True, min_items=None, max_items=None,
5353
min_length=None, max_length=None, pattern=None, unique_items=False,
5454
minimum=None, maximum=None, multiple_of=None,
5555
exclusive_minimum=False, exclusive_maximum=False,
56-
min_properties=None, max_properties=None, _source=None):
56+
min_properties=None, max_properties=None, extensions=None,
57+
_source=None):
5758
self.type = SchemaType(schema_type)
58-
self.model = model
5959
self.properties = properties and dict(properties) or {}
6060
self.items = items
6161
self.format = schema_format
@@ -84,6 +84,8 @@ def __init__(
8484
self.max_properties = int(max_properties)\
8585
if max_properties is not None else None
8686

87+
self.extensions = extensions and dict(extensions) or {}
88+
8789
self._all_required_properties_cache = None
8890
self._all_optional_properties_cache = None
8991

@@ -198,50 +200,13 @@ def validate(self, value, resolver=None):
198200

199201
def unmarshal(self, value, custom_formatters=None, strict=True):
200202
"""Unmarshal parameter from the value."""
201-
if self.deprecated:
202-
warnings.warn("The schema is deprecated", DeprecationWarning)
203-
if value is None:
204-
if not self.nullable:
205-
raise UnmarshalError(
206-
"Null value for non-nullable schema", value, self.type)
207-
return self.default
208-
209-
if self.enum and value not in self.enum:
210-
raise UnmarshalError("Invalid value for enum: {0}".format(value))
211-
212-
unmarshal_mapping = self.get_unmarshal_mapping(
213-
custom_formatters=custom_formatters, strict=strict)
214-
215-
if self.type is not SchemaType.STRING and value == '':
216-
return None
217-
218-
unmarshal_callable = unmarshal_mapping[self.type]
219-
try:
220-
unmarshalled = unmarshal_callable(value)
221-
except ValueError as exc:
222-
raise UnmarshalValueError(value, self.type, exc)
223-
224-
return unmarshalled
225-
226-
def get_primitive_unmarshallers(self, **options):
227-
from openapi_core.schema.schemas.unmarshallers import (
228-
StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller,
229-
NumberUnmarshaller,
203+
from openapi_core.unmarshalling.schemas.factories import (
204+
SchemaUnmarshallersFactory,
230205
)
231-
232-
unmarshallers_classes = {
233-
SchemaType.STRING: StringUnmarshaller,
234-
SchemaType.BOOLEAN: BooleanUnmarshaller,
235-
SchemaType.INTEGER: IntegerUnmarshaller,
236-
SchemaType.NUMBER: NumberUnmarshaller,
237-
}
238-
239-
unmarshallers = dict(
240-
(t, klass(**options))
241-
for t, klass in unmarshallers_classes.items()
242-
)
243-
244-
return unmarshallers
206+
unmarshallers_factory = SchemaUnmarshallersFactory(
207+
custom_formatters)
208+
unmarshaller = unmarshallers_factory.create(self)
209+
return unmarshaller(value, strict=strict)
245210

246211
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
247212
types_resolve_order = [
@@ -276,79 +241,3 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
276241

277242
log.warning("failed to unmarshal any type")
278243
return value
279-
280-
def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
281-
if not isinstance(value, (list, tuple)):
282-
raise ValueError("Invalid value for collection: {0}".format(value))
283-
284-
f = functools.partial(
285-
self.items.unmarshal,
286-
custom_formatters=custom_formatters, strict=strict,
287-
)
288-
return list(map(f, value))
289-
290-
def _unmarshal_object(self, value, model_factory=None,
291-
custom_formatters=None, strict=True):
292-
if not isinstance(value, (dict, )):
293-
raise ValueError("Invalid value for object: {0}".format(value))
294-
295-
model_factory = model_factory or ModelFactory()
296-
297-
if self.one_of:
298-
properties = None
299-
for one_of_schema in self.one_of:
300-
try:
301-
unmarshalled = self._unmarshal_properties(
302-
value, one_of_schema, custom_formatters=custom_formatters)
303-
except (UnmarshalError, ValueError):
304-
pass
305-
else:
306-
if properties is not None:
307-
log.warning("multiple valid oneOf schemas found")
308-
continue
309-
properties = unmarshalled
310-
311-
if properties is None:
312-
log.warning("valid oneOf schema not found")
313-
314-
else:
315-
properties = self._unmarshal_properties(
316-
value, custom_formatters=custom_formatters)
317-
318-
return model_factory.create(properties, name=self.model)
319-
320-
def _unmarshal_properties(self, value, one_of_schema=None,
321-
custom_formatters=None, strict=True):
322-
all_props = self.get_all_properties()
323-
all_props_names = self.get_all_properties_names()
324-
all_req_props_names = self.get_all_required_properties_names()
325-
326-
if one_of_schema is not None:
327-
all_props.update(one_of_schema.get_all_properties())
328-
all_props_names |= one_of_schema.\
329-
get_all_properties_names()
330-
all_req_props_names |= one_of_schema.\
331-
get_all_required_properties_names()
332-
333-
value_props_names = value.keys()
334-
extra_props = set(value_props_names) - set(all_props_names)
335-
336-
properties = {}
337-
if self.additional_properties is not True:
338-
for prop_name in extra_props:
339-
prop_value = value[prop_name]
340-
properties[prop_name] = self.additional_properties.unmarshal(
341-
prop_value, custom_formatters=custom_formatters)
342-
343-
for prop_name, prop in iteritems(all_props):
344-
try:
345-
prop_value = value[prop_name]
346-
except KeyError:
347-
if not prop.nullable and not prop.default:
348-
continue
349-
prop_value = prop.default
350-
351-
properties[prop_name] = prop.unmarshal(
352-
prop_value, custom_formatters=custom_formatters)
353-
354-
return properties

openapi_core/unmarshalling/__init__.py

Whitespace-only changes.

openapi_core/unmarshalling/schemas/__init__.py

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import attr
2+
3+
from openapi_core.exceptions import OpenAPIError
4+
5+
6+
class UnmarshalError(OpenAPIError):
7+
"""Schema unmarshal operation error"""
8+
pass
9+
10+
11+
class UnmarshallerError(UnmarshalError):
12+
"""Unmarshaller error"""
13+
pass
14+
15+
16+
@attr.s(hash=True)
17+
class UnmarshalValueError(UnmarshalError):
18+
"""Failed to unmarshal value to type"""
19+
value = attr.ib()
20+
type = attr.ib()
21+
original_exception = attr.ib(default=None)
22+
23+
def __str__(self):
24+
return (
25+
"Failed to unmarshal value {value} to type {type}: {exception}"
26+
).format(
27+
value=self.value, type=self.type,
28+
exception=self.original_exception,
29+
)
30+
31+
32+
@attr.s(hash=True)
33+
class InvalidCustomFormatSchemaValue(UnmarshallerError):
34+
"""Value failed to format with custom formatter"""
35+
value = attr.ib()
36+
type = attr.ib()
37+
original_exception = attr.ib()
38+
39+
def __str__(self):
40+
return (
41+
"Failed to format value {value} to format {type}: {exception}"
42+
).format(
43+
value=self.value, type=self.type,
44+
exception=self.original_exception,
45+
)
46+
47+
48+
@attr.s(hash=True)
49+
class FormatterNotFoundError(UnmarshallerError):
50+
"""Formatter not found to unmarshal"""
51+
type_format = attr.ib()
52+
53+
def __str__(self):
54+
return "Formatter not found for {format} format".format(
55+
format=self.type_format)
56+
57+
58+
@attr.s(hash=True)
59+
class UnmarshallerStrictTypeError(UnmarshallerError):
60+
value = attr.ib()
61+
types = attr.ib()
62+
63+
def __str__(self):
64+
types = ', '.join(list(map(str, self.types)))
65+
return "Value {value} is not one of types: {types}".format(
66+
value=self.value, types=types)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from collections import defaultdict
2+
from functools import partial
3+
import warnings
4+
5+
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
6+
from openapi_core.unmarshalling.schemas.exceptions import FormatterNotFoundError
7+
from openapi_core.unmarshalling.schemas.unmarshallers import (
8+
StringUnmarshaller, IntegerUnmarshaller, NumberUnmarshaller,
9+
BooleanUnmarshaller, ArrayUnmarshaller, ObjectUnmarshaller,
10+
AnyUnmarshaller,
11+
)
12+
13+
14+
class SchemaUnmarshallersFactory(object):
15+
16+
PRIMITIVE_UNMARSHALLERS = {
17+
SchemaType.STRING: StringUnmarshaller,
18+
SchemaType.INTEGER: IntegerUnmarshaller,
19+
SchemaType.NUMBER: NumberUnmarshaller,
20+
SchemaType.BOOLEAN: BooleanUnmarshaller,
21+
}
22+
COMPLEX_UNMARSHALLERS = {
23+
SchemaType.ARRAY: ArrayUnmarshaller,
24+
SchemaType.OBJECT: ObjectUnmarshaller,
25+
SchemaType.ANY: AnyUnmarshaller,
26+
}
27+
28+
def __init__(self, custom_formatters=None):
29+
self.custom_formatters = custom_formatters
30+
31+
def create(self, schema, type_override=None):
32+
"""Create unmarshaller from the schema."""
33+
if schema.deprecated:
34+
warnings.warn("The schema is deprecated", DeprecationWarning)
35+
36+
schema_type = type_override or schema.type
37+
if schema_type in self.PRIMITIVE_UNMARSHALLERS:
38+
klass = self.PRIMITIVE_UNMARSHALLERS[schema_type]
39+
kwargs = {}
40+
41+
elif schema_type in self.COMPLEX_UNMARSHALLERS:
42+
klass = self.COMPLEX_UNMARSHALLERS[schema_type]
43+
kwargs = dict(schema=schema, unmarshallers_factory=self)
44+
45+
formatter = self.get_formatter(klass.FORMATTERS, schema.format)
46+
47+
if formatter is None:
48+
raise FormatterNotFoundError(schema.format)
49+
50+
return klass(formatter, **kwargs)
51+
52+
def get_formatter(self, formatters, type_format=SchemaFormat.NONE):
53+
try:
54+
schema_format = SchemaFormat(type_format)
55+
except ValueError:
56+
return self.custom_formatters.get(type_format)
57+
else:
58+
if schema_format == SchemaFormat.NONE:
59+
return lambda x: x
60+
return formatters.get(schema_format)

0 commit comments

Comments
 (0)