Skip to content

Commit f0329bb

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

File tree

18 files changed

+485
-172
lines changed

18 files changed

+485
-172
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/parameters/models.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ def get_raw_value(self, request):
8181
if self.required:
8282
raise MissingRequiredParameter(self.name)
8383

84-
if not self.schema or self.schema.default is None:
85-
raise MissingParameter(self.name)
86-
87-
return self.schema.default
84+
raise MissingParameter(self.name)
8885

8986
if self.aslist and self.explode:
9087
return location.getlist(self.name)

openapi_core/schema/schemas/factories.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
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
9-
from openapi_core.schema.schemas.types import Contribution
10+
from openapi_core.schema.schemas.types import Contribution, NoValue
1011

1112
log = logging.getLogger(__name__)
1213

@@ -21,9 +22,8 @@ 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)
26-
default = schema_deref.get('default', None)
26+
default = schema_deref.get('default', NoValue)
2727
properties_spec = schema_deref.get('properties', None)
2828
items_spec = schema_deref.get('items', None)
2929
nullable = schema_deref.get('nullable', False)
@@ -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: 18 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
CastError, InvalidSchemaValue,
1919
UnmarshallerError, UnmarshalValueError, UnmarshalError,
2020
)
21+
from openapi_core.schema.schemas.types import NoValue
2122
from openapi_core.schema.schemas.util import (
2223
forcebool, format_date, format_datetime, format_byte, format_uuid,
2324
format_number,
@@ -46,16 +47,16 @@ class Schema(object):
4647
}
4748

4849
def __init__(
49-
self, schema_type=None, model=None, properties=None, items=None,
50-
schema_format=None, required=None, default=None, nullable=False,
50+
self, schema_type=None, properties=None, items=None,
51+
schema_format=None, required=None, default=NoValue, nullable=False,
5152
enum=None, deprecated=False, all_of=None, one_of=None,
5253
additional_properties=True, min_items=None, max_items=None,
5354
min_length=None, max_length=None, pattern=None, unique_items=False,
5455
minimum=None, maximum=None, multiple_of=None,
5556
exclusive_minimum=False, exclusive_maximum=False,
56-
min_properties=None, max_properties=None, _source=None):
57+
min_properties=None, max_properties=None, extensions=None,
58+
_source=None):
5759
self.type = SchemaType(schema_type)
58-
self.model = model
5960
self.properties = properties and dict(properties) or {}
6061
self.items = items
6162
self.format = schema_format
@@ -84,6 +85,8 @@ def __init__(
8485
self.max_properties = int(max_properties)\
8586
if max_properties is not None else None
8687

88+
self.extensions = extensions and dict(extensions) or {}
89+
8790
self._all_required_properties_cache = None
8891
self._all_optional_properties_cache = None
8992

@@ -100,6 +103,9 @@ def to_dict(self):
100103
def __getitem__(self, name):
101104
return self.properties[name]
102105

106+
def has_default(self):
107+
return not self.default is NoValue
108+
103109
def get_all_properties(self):
104110
properties = self.properties.copy()
105111

@@ -149,7 +155,7 @@ def get_cast_mapping(self):
149155

150156
def cast(self, value):
151157
"""Cast value from string to schema type"""
152-
if value is None:
158+
if value in (None, NoValue):
153159
return value
154160

155161
cast_mapping = self.get_cast_mapping()
@@ -198,51 +204,17 @@ def validate(self, value, resolver=None):
198204

199205
def unmarshal(self, value, custom_formatters=None, strict=True):
200206
"""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]
207+
from openapi_core.unmarshalling.schemas.factories import (
208+
SchemaUnmarshallersFactory,
209+
)
210+
unmarshallers_factory = SchemaUnmarshallersFactory(
211+
custom_formatters)
212+
unmarshaller = unmarshallers_factory.create(self)
219213
try:
220-
unmarshalled = unmarshal_callable(value)
214+
return unmarshaller(value, strict=strict)
221215
except ValueError as exc:
222216
raise UnmarshalValueError(value, self.type, exc)
223217

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,
230-
)
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
245-
246218
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
247219
types_resolve_order = [
248220
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
@@ -276,79 +248,3 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
276248

277249
log.warning("failed to unmarshal any type")
278250
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/schema/schemas/types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import attr
22

33

4+
NoValue = object()
5+
6+
47
@attr.s(hash=True)
58
class Contribution(object):
69
src_prop_name = attr.ib()

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)

0 commit comments

Comments
 (0)