mirror of
https://github.com/correl/openapi-core.git
synced 2024-11-28 19:19:52 +00:00
Merge pull request #183 from p1c2u/refactor/move-unmarshallers-to-subpackage
Move Unmarshallers to separate subpackage
This commit is contained in:
commit
ca63475826
23 changed files with 537 additions and 468 deletions
5
openapi_core/exceptions.py
Normal file
5
openapi_core/exceptions.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
"""OpenAPI core exceptions module"""
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAPIError(Exception):
|
||||||
|
pass
|
|
@ -1,8 +1,5 @@
|
||||||
"""OpenAPI core schema exceptions module"""
|
"""OpenAPI core schema exceptions module"""
|
||||||
|
from openapi_core.exceptions import OpenAPIError
|
||||||
|
|
||||||
class OpenAPIError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAPIMappingError(OpenAPIError):
|
class OpenAPIMappingError(OpenAPIError):
|
||||||
|
|
0
openapi_core/schema/extensions/__init__.py
Normal file
0
openapi_core/schema/extensions/__init__.py
Normal file
16
openapi_core/schema/extensions/generators.py
Normal file
16
openapi_core/schema/extensions/generators.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""OpenAPI core extensions generators module"""
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
from openapi_core.schema.extensions.models import Extension
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionsGenerator(object):
|
||||||
|
|
||||||
|
def __init__(self, dereferencer):
|
||||||
|
self.dereferencer = dereferencer
|
||||||
|
|
||||||
|
def generate(self, item_spec):
|
||||||
|
for field_name, value in iteritems(item_spec):
|
||||||
|
if not field_name.startswith('x-'):
|
||||||
|
continue
|
||||||
|
yield field_name, Extension(field_name, value)
|
9
openapi_core/schema/extensions/models.py
Normal file
9
openapi_core/schema/extensions/models.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""OpenAPI core extensions models module"""
|
||||||
|
|
||||||
|
|
||||||
|
class Extension(object):
|
||||||
|
"""Represents an OpenAPI Extension."""
|
||||||
|
|
||||||
|
def __init__(self, field_name, value=None):
|
||||||
|
self.field_name = field_name
|
||||||
|
self.value = value
|
|
@ -5,8 +5,9 @@ from json import loads
|
||||||
|
|
||||||
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
|
from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
CastError, ValidateError, UnmarshalError,
|
CastError, ValidateError,
|
||||||
)
|
)
|
||||||
|
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
|
||||||
|
|
||||||
|
|
||||||
MEDIA_TYPE_DESERIALIZERS = {
|
MEDIA_TYPE_DESERIALIZERS = {
|
||||||
|
|
|
@ -11,8 +11,9 @@ from openapi_core.schema.parameters.exceptions import (
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.enums import SchemaType
|
from openapi_core.schema.schemas.enums import SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
CastError, ValidateError, UnmarshalError,
|
CastError, ValidateError,
|
||||||
)
|
)
|
||||||
|
from openapi_core.unmarshalling.schemas.exceptions import UnmarshalError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -81,11 +82,8 @@ class Parameter(object):
|
||||||
if self.required:
|
if self.required:
|
||||||
raise MissingRequiredParameter(self.name)
|
raise MissingRequiredParameter(self.name)
|
||||||
|
|
||||||
if not self.schema or self.schema.default is None:
|
|
||||||
raise MissingParameter(self.name)
|
raise MissingParameter(self.name)
|
||||||
|
|
||||||
return self.schema.default
|
|
||||||
|
|
||||||
if self.aslist and self.explode:
|
if self.aslist and self.explode:
|
||||||
if hasattr(location, 'getall'):
|
if hasattr(location, 'getall'):
|
||||||
return location.getall(self.name)
|
return location.getall(self.name)
|
||||||
|
|
|
@ -23,27 +23,6 @@ class ValidateError(OpenAPISchemaError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UnmarshalError(OpenAPISchemaError):
|
|
||||||
"""Schema unmarshal operation error"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(hash=True)
|
|
||||||
class UnmarshalValueError(UnmarshalError):
|
|
||||||
"""Failed to unmarshal value to type"""
|
|
||||||
value = attr.ib()
|
|
||||||
type = attr.ib()
|
|
||||||
original_exception = attr.ib(default=None)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return (
|
|
||||||
"Failed to unmarshal value {value} to type {type}: {exception}"
|
|
||||||
).format(
|
|
||||||
value=self.value, type=self.type,
|
|
||||||
exception=self.original_exception,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(hash=True)
|
@attr.s(hash=True)
|
||||||
class InvalidSchemaValue(ValidateError):
|
class InvalidSchemaValue(ValidateError):
|
||||||
value = attr.ib()
|
value = attr.ib()
|
||||||
|
@ -61,48 +40,3 @@ class InvalidSchemaValue(ValidateError):
|
||||||
return (
|
return (
|
||||||
"Value {value} not valid for schema of type {type}: {errors}"
|
"Value {value} not valid for schema of type {type}: {errors}"
|
||||||
).format(value=self.value, type=self.type, errors=self.schema_errors)
|
).format(value=self.value, type=self.type, errors=self.schema_errors)
|
||||||
|
|
||||||
|
|
||||||
class UnmarshallerError(UnmarshalError):
|
|
||||||
"""Unmarshaller error"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(hash=True)
|
|
||||||
class InvalidCustomFormatSchemaValue(UnmarshallerError):
|
|
||||||
"""Value failed to format with custom formatter"""
|
|
||||||
value = attr.ib()
|
|
||||||
type = attr.ib()
|
|
||||||
original_exception = attr.ib()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return (
|
|
||||||
"Failed to format value {value} to format {type}: {exception}"
|
|
||||||
).format(
|
|
||||||
value=self.value, type=self.type,
|
|
||||||
exception=self.original_exception,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(hash=True)
|
|
||||||
class FormatterNotFoundError(UnmarshallerError):
|
|
||||||
"""Formatter not found to unmarshal"""
|
|
||||||
value = attr.ib()
|
|
||||||
type_format = attr.ib()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return (
|
|
||||||
"Formatter not found for {format} format "
|
|
||||||
"to unmarshal value {value}"
|
|
||||||
).format(format=self.type_format, value=self.value)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(hash=True)
|
|
||||||
class UnmarshallerStrictTypeError(UnmarshallerError):
|
|
||||||
value = attr.ib()
|
|
||||||
types = attr.ib()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
types = ', '.join(list(map(str, self.types)))
|
|
||||||
return "Value {value} is not one of types: {types}".format(
|
|
||||||
value=self.value, types=types)
|
|
||||||
|
|
|
@ -4,9 +4,10 @@ import logging
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
from openapi_core.compat import lru_cache
|
from openapi_core.compat import lru_cache
|
||||||
|
from openapi_core.schema.extensions.generators import ExtensionsGenerator
|
||||||
from openapi_core.schema.properties.generators import PropertiesGenerator
|
from openapi_core.schema.properties.generators import PropertiesGenerator
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
from openapi_core.schema.schemas.types import Contribution
|
from openapi_core.schema.schemas.types import Contribution, NoValue
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -21,9 +22,8 @@ class SchemaFactory(object):
|
||||||
|
|
||||||
schema_type = schema_deref.get('type', None)
|
schema_type = schema_deref.get('type', None)
|
||||||
schema_format = schema_deref.get('format')
|
schema_format = schema_deref.get('format')
|
||||||
model = schema_deref.get('x-model', None)
|
|
||||||
required = schema_deref.get('required', False)
|
required = schema_deref.get('required', False)
|
||||||
default = schema_deref.get('default', None)
|
default = schema_deref.get('default', NoValue)
|
||||||
properties_spec = schema_deref.get('properties', None)
|
properties_spec = schema_deref.get('properties', None)
|
||||||
items_spec = schema_deref.get('items', None)
|
items_spec = schema_deref.get('items', None)
|
||||||
nullable = schema_deref.get('nullable', False)
|
nullable = schema_deref.get('nullable', False)
|
||||||
|
@ -47,6 +47,8 @@ class SchemaFactory(object):
|
||||||
min_properties = schema_deref.get('minProperties', None)
|
min_properties = schema_deref.get('minProperties', None)
|
||||||
max_properties = schema_deref.get('maxProperties', None)
|
max_properties = schema_deref.get('maxProperties', None)
|
||||||
|
|
||||||
|
extensions = self.extensions_generator.generate(schema_deref)
|
||||||
|
|
||||||
properties = None
|
properties = None
|
||||||
if properties_spec:
|
if properties_spec:
|
||||||
properties = self.properties_generator.generate(properties_spec)
|
properties = self.properties_generator.generate(properties_spec)
|
||||||
|
@ -68,7 +70,7 @@ class SchemaFactory(object):
|
||||||
additional_properties = self.create(additional_properties_spec)
|
additional_properties = self.create(additional_properties_spec)
|
||||||
|
|
||||||
return Schema(
|
return Schema(
|
||||||
schema_type=schema_type, model=model, properties=properties,
|
schema_type=schema_type, properties=properties,
|
||||||
items=items, schema_format=schema_format, required=required,
|
items=items, schema_format=schema_format, required=required,
|
||||||
default=default, nullable=nullable, enum=enum,
|
default=default, nullable=nullable, enum=enum,
|
||||||
deprecated=deprecated, all_of=all_of, one_of=one_of,
|
deprecated=deprecated, all_of=all_of, one_of=one_of,
|
||||||
|
@ -79,9 +81,15 @@ class SchemaFactory(object):
|
||||||
exclusive_maximum=exclusive_maximum,
|
exclusive_maximum=exclusive_maximum,
|
||||||
exclusive_minimum=exclusive_minimum,
|
exclusive_minimum=exclusive_minimum,
|
||||||
min_properties=min_properties, max_properties=max_properties,
|
min_properties=min_properties, max_properties=max_properties,
|
||||||
|
extensions=extensions,
|
||||||
_source=schema_deref,
|
_source=schema_deref,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@lru_cache()
|
||||||
|
def extensions_generator(self):
|
||||||
|
return ExtensionsGenerator(self.dereferencer)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def properties_generator(self):
|
def properties_generator(self):
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
"""OpenAPI core schemas models module"""
|
"""OpenAPI core schemas models module"""
|
||||||
import attr
|
import attr
|
||||||
import functools
|
|
||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import re
|
import re
|
||||||
import warnings
|
|
||||||
|
|
||||||
from six import iteritems
|
|
||||||
from jsonschema.exceptions import ValidationError
|
from jsonschema.exceptions import ValidationError
|
||||||
|
|
||||||
from openapi_core.extensions.models.factories import ModelFactory
|
|
||||||
from openapi_core.schema.schemas._format import oas30_format_checker
|
from openapi_core.schema.schemas._format import oas30_format_checker
|
||||||
from openapi_core.schema.schemas.enums import SchemaType
|
from openapi_core.schema.schemas.enums import SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
CastError, InvalidSchemaValue,
|
CastError, InvalidSchemaValue,
|
||||||
UnmarshalValueError, UnmarshalError,
|
|
||||||
)
|
)
|
||||||
|
from openapi_core.schema.schemas.types import NoValue
|
||||||
from openapi_core.schema.schemas.util import forcebool
|
from openapi_core.schema.schemas.util import forcebool
|
||||||
from openapi_core.schema.schemas.validators import OAS30Validator
|
from openapi_core.schema.schemas.validators import OAS30Validator
|
||||||
|
from openapi_core.unmarshalling.schemas.exceptions import (
|
||||||
|
UnmarshalValueError,
|
||||||
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -37,20 +36,17 @@ class Schema(object):
|
||||||
SchemaType.BOOLEAN: forcebool,
|
SchemaType.BOOLEAN: forcebool,
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_UNMARSHAL_CALLABLE_GETTER = {
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, schema_type=None, model=None, properties=None, items=None,
|
self, schema_type=None, properties=None, items=None,
|
||||||
schema_format=None, required=None, default=None, nullable=False,
|
schema_format=None, required=None, default=NoValue, nullable=False,
|
||||||
enum=None, deprecated=False, all_of=None, one_of=None,
|
enum=None, deprecated=False, all_of=None, one_of=None,
|
||||||
additional_properties=True, min_items=None, max_items=None,
|
additional_properties=True, min_items=None, max_items=None,
|
||||||
min_length=None, max_length=None, pattern=None, unique_items=False,
|
min_length=None, max_length=None, pattern=None, unique_items=False,
|
||||||
minimum=None, maximum=None, multiple_of=None,
|
minimum=None, maximum=None, multiple_of=None,
|
||||||
exclusive_minimum=False, exclusive_maximum=False,
|
exclusive_minimum=False, exclusive_maximum=False,
|
||||||
min_properties=None, max_properties=None, _source=None):
|
min_properties=None, max_properties=None, extensions=None,
|
||||||
|
_source=None):
|
||||||
self.type = SchemaType(schema_type)
|
self.type = SchemaType(schema_type)
|
||||||
self.model = model
|
|
||||||
self.properties = properties and dict(properties) or {}
|
self.properties = properties and dict(properties) or {}
|
||||||
self.items = items
|
self.items = items
|
||||||
self.format = schema_format
|
self.format = schema_format
|
||||||
|
@ -79,6 +75,8 @@ class Schema(object):
|
||||||
self.max_properties = int(max_properties)\
|
self.max_properties = int(max_properties)\
|
||||||
if max_properties is not None else None
|
if max_properties is not None else None
|
||||||
|
|
||||||
|
self.extensions = extensions and dict(extensions) or {}
|
||||||
|
|
||||||
self._all_required_properties_cache = None
|
self._all_required_properties_cache = None
|
||||||
self._all_optional_properties_cache = None
|
self._all_optional_properties_cache = None
|
||||||
|
|
||||||
|
@ -95,6 +93,9 @@ class Schema(object):
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
return self.properties[name]
|
return self.properties[name]
|
||||||
|
|
||||||
|
def has_default(self):
|
||||||
|
return self.default is not NoValue
|
||||||
|
|
||||||
def get_all_properties(self):
|
def get_all_properties(self):
|
||||||
properties = self.properties.copy()
|
properties = self.properties.copy()
|
||||||
|
|
||||||
|
@ -108,32 +109,6 @@ class Schema(object):
|
||||||
all_properties = self.get_all_properties()
|
all_properties = self.get_all_properties()
|
||||||
return set(all_properties.keys())
|
return set(all_properties.keys())
|
||||||
|
|
||||||
def get_all_required_properties(self):
|
|
||||||
if self._all_required_properties_cache is None:
|
|
||||||
self._all_required_properties_cache =\
|
|
||||||
self._get_all_required_properties()
|
|
||||||
|
|
||||||
return self._all_required_properties_cache
|
|
||||||
|
|
||||||
def _get_all_required_properties(self):
|
|
||||||
all_properties = self.get_all_properties()
|
|
||||||
required = self.get_all_required_properties_names()
|
|
||||||
|
|
||||||
return dict(
|
|
||||||
(prop_name, val)
|
|
||||||
for prop_name, val in iteritems(all_properties)
|
|
||||||
if prop_name in required
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_all_required_properties_names(self):
|
|
||||||
required = self.required[:]
|
|
||||||
|
|
||||||
for subschema in self.all_of:
|
|
||||||
subschema_req = subschema.get_all_required_properties()
|
|
||||||
required += subschema_req
|
|
||||||
|
|
||||||
return set(required)
|
|
||||||
|
|
||||||
def get_cast_mapping(self):
|
def get_cast_mapping(self):
|
||||||
mapping = self.TYPE_CAST_CALLABLE_GETTER.copy()
|
mapping = self.TYPE_CAST_CALLABLE_GETTER.copy()
|
||||||
mapping.update({
|
mapping.update({
|
||||||
|
@ -144,7 +119,7 @@ class Schema(object):
|
||||||
|
|
||||||
def cast(self, value):
|
def cast(self, value):
|
||||||
"""Cast value from string to schema type"""
|
"""Cast value from string to schema type"""
|
||||||
if value is None:
|
if value in (None, NoValue):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
cast_mapping = self.get_cast_mapping()
|
cast_mapping = self.get_cast_mapping()
|
||||||
|
@ -158,28 +133,6 @@ class Schema(object):
|
||||||
def _cast_collection(self, value):
|
def _cast_collection(self, value):
|
||||||
return list(map(self.items.cast, value))
|
return list(map(self.items.cast, value))
|
||||||
|
|
||||||
def get_unmarshal_mapping(self, custom_formatters=None, strict=True):
|
|
||||||
primitive_unmarshallers = self.get_primitive_unmarshallers(
|
|
||||||
custom_formatters=custom_formatters)
|
|
||||||
|
|
||||||
primitive_unmarshallers_partial = dict(
|
|
||||||
(t, functools.partial(u, type_format=self.format, strict=strict))
|
|
||||||
for t, u in primitive_unmarshallers.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
def pass_defaults(f):
|
|
||||||
return functools.partial(
|
|
||||||
f, custom_formatters=custom_formatters, strict=strict)
|
|
||||||
mapping = self.DEFAULT_UNMARSHAL_CALLABLE_GETTER.copy()
|
|
||||||
mapping.update(primitive_unmarshallers_partial)
|
|
||||||
mapping.update({
|
|
||||||
SchemaType.ANY: pass_defaults(self._unmarshal_any),
|
|
||||||
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
|
|
||||||
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
|
|
||||||
})
|
|
||||||
|
|
||||||
return defaultdict(lambda: lambda x: x, mapping)
|
|
||||||
|
|
||||||
def get_validator(self, resolver=None):
|
def get_validator(self, resolver=None):
|
||||||
return OAS30Validator(
|
return OAS30Validator(
|
||||||
self.__dict__,
|
self.__dict__,
|
||||||
|
@ -197,162 +150,13 @@ class Schema(object):
|
||||||
|
|
||||||
def unmarshal(self, value, custom_formatters=None, strict=True):
|
def unmarshal(self, value, custom_formatters=None, strict=True):
|
||||||
"""Unmarshal parameter from the value."""
|
"""Unmarshal parameter from the value."""
|
||||||
if self.deprecated:
|
from openapi_core.unmarshalling.schemas.factories import (
|
||||||
warnings.warn("The schema is deprecated", DeprecationWarning)
|
SchemaUnmarshallersFactory,
|
||||||
if value is None:
|
)
|
||||||
if not self.nullable:
|
unmarshallers_factory = SchemaUnmarshallersFactory(
|
||||||
raise UnmarshalError(
|
custom_formatters)
|
||||||
"Null value for non-nullable schema", value, self.type)
|
unmarshaller = unmarshallers_factory.create(self)
|
||||||
return self.default
|
|
||||||
|
|
||||||
if self.enum and value not in self.enum:
|
|
||||||
raise UnmarshalError("Invalid value for enum: {0}".format(value))
|
|
||||||
|
|
||||||
unmarshal_mapping = self.get_unmarshal_mapping(
|
|
||||||
custom_formatters=custom_formatters, strict=strict)
|
|
||||||
|
|
||||||
if self.type is not SchemaType.STRING and value == '':
|
|
||||||
return None
|
|
||||||
|
|
||||||
unmarshal_callable = unmarshal_mapping[self.type]
|
|
||||||
try:
|
try:
|
||||||
unmarshalled = unmarshal_callable(value)
|
return unmarshaller(value, strict=strict)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise UnmarshalValueError(value, self.type, exc)
|
raise UnmarshalValueError(value, self.type, exc)
|
||||||
|
|
||||||
return unmarshalled
|
|
||||||
|
|
||||||
def get_primitive_unmarshallers(self, **options):
|
|
||||||
from openapi_core.schema.schemas.unmarshallers import (
|
|
||||||
StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller,
|
|
||||||
NumberUnmarshaller,
|
|
||||||
)
|
|
||||||
|
|
||||||
unmarshallers_classes = {
|
|
||||||
SchemaType.STRING: StringUnmarshaller,
|
|
||||||
SchemaType.BOOLEAN: BooleanUnmarshaller,
|
|
||||||
SchemaType.INTEGER: IntegerUnmarshaller,
|
|
||||||
SchemaType.NUMBER: NumberUnmarshaller,
|
|
||||||
}
|
|
||||||
|
|
||||||
unmarshallers = dict(
|
|
||||||
(t, klass(**options))
|
|
||||||
for t, klass in unmarshallers_classes.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
return unmarshallers
|
|
||||||
|
|
||||||
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
|
|
||||||
types_resolve_order = [
|
|
||||||
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
|
|
||||||
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
|
|
||||||
]
|
|
||||||
unmarshal_mapping = self.get_unmarshal_mapping()
|
|
||||||
if self.one_of:
|
|
||||||
result = None
|
|
||||||
for subschema in self.one_of:
|
|
||||||
try:
|
|
||||||
unmarshalled = subschema.unmarshal(
|
|
||||||
value, custom_formatters)
|
|
||||||
except UnmarshalError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if result is not None:
|
|
||||||
log.warning("multiple valid oneOf schemas found")
|
|
||||||
continue
|
|
||||||
result = unmarshalled
|
|
||||||
|
|
||||||
if result is None:
|
|
||||||
log.warning("valid oneOf schema not found")
|
|
||||||
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
for schema_type in types_resolve_order:
|
|
||||||
unmarshal_callable = unmarshal_mapping[schema_type]
|
|
||||||
try:
|
|
||||||
return unmarshal_callable(value)
|
|
||||||
except (UnmarshalError, ValueError):
|
|
||||||
continue
|
|
||||||
|
|
||||||
log.warning("failed to unmarshal any type")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def _unmarshal_collection(
|
|
||||||
self, value, custom_formatters=None, strict=True):
|
|
||||||
if not isinstance(value, (list, tuple)):
|
|
||||||
raise ValueError(
|
|
||||||
"Invalid value for collection: {0}".format(value))
|
|
||||||
|
|
||||||
f = functools.partial(
|
|
||||||
self.items.unmarshal,
|
|
||||||
custom_formatters=custom_formatters, strict=strict,
|
|
||||||
)
|
|
||||||
return list(map(f, value))
|
|
||||||
|
|
||||||
def _unmarshal_object(self, value, model_factory=None,
|
|
||||||
custom_formatters=None, strict=True):
|
|
||||||
if not isinstance(value, (dict, )):
|
|
||||||
raise ValueError("Invalid value for object: {0}".format(value))
|
|
||||||
|
|
||||||
model_factory = model_factory or ModelFactory()
|
|
||||||
|
|
||||||
if self.one_of:
|
|
||||||
properties = None
|
|
||||||
for one_of_schema in self.one_of:
|
|
||||||
try:
|
|
||||||
unmarshalled = self._unmarshal_properties(
|
|
||||||
value, one_of_schema,
|
|
||||||
custom_formatters=custom_formatters,
|
|
||||||
)
|
|
||||||
except (UnmarshalError, ValueError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if properties is not None:
|
|
||||||
log.warning("multiple valid oneOf schemas found")
|
|
||||||
continue
|
|
||||||
properties = unmarshalled
|
|
||||||
|
|
||||||
if properties is None:
|
|
||||||
log.warning("valid oneOf schema not found")
|
|
||||||
|
|
||||||
else:
|
|
||||||
properties = self._unmarshal_properties(
|
|
||||||
value, custom_formatters=custom_formatters)
|
|
||||||
|
|
||||||
return model_factory.create(properties, name=self.model)
|
|
||||||
|
|
||||||
def _unmarshal_properties(self, value, one_of_schema=None,
|
|
||||||
custom_formatters=None, strict=True):
|
|
||||||
all_props = self.get_all_properties()
|
|
||||||
all_props_names = self.get_all_properties_names()
|
|
||||||
all_req_props_names = self.get_all_required_properties_names()
|
|
||||||
|
|
||||||
if one_of_schema is not None:
|
|
||||||
all_props.update(one_of_schema.get_all_properties())
|
|
||||||
all_props_names |= one_of_schema.\
|
|
||||||
get_all_properties_names()
|
|
||||||
all_req_props_names |= one_of_schema.\
|
|
||||||
get_all_required_properties_names()
|
|
||||||
|
|
||||||
value_props_names = value.keys()
|
|
||||||
extra_props = set(value_props_names) - set(all_props_names)
|
|
||||||
|
|
||||||
properties = {}
|
|
||||||
if self.additional_properties is not True:
|
|
||||||
for prop_name in extra_props:
|
|
||||||
prop_value = value[prop_name]
|
|
||||||
properties[prop_name] = self.additional_properties.unmarshal(
|
|
||||||
prop_value, custom_formatters=custom_formatters)
|
|
||||||
|
|
||||||
for prop_name, prop in iteritems(all_props):
|
|
||||||
try:
|
|
||||||
prop_value = value[prop_name]
|
|
||||||
except KeyError:
|
|
||||||
if not prop.nullable and not prop.default:
|
|
||||||
continue
|
|
||||||
prop_value = prop.default
|
|
||||||
|
|
||||||
properties[prop_name] = prop.unmarshal(
|
|
||||||
prop_value, custom_formatters=custom_formatters)
|
|
||||||
|
|
||||||
return properties
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
|
||||||
|
NoValue = object()
|
||||||
|
|
||||||
|
|
||||||
@attr.s(hash=True)
|
@attr.s(hash=True)
|
||||||
class Contribution(object):
|
class Contribution(object):
|
||||||
src_prop_name = attr.ib()
|
src_prop_name = attr.ib()
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
from six import text_type, binary_type, integer_types
|
|
||||||
|
|
||||||
from openapi_core.schema.schemas.enums import SchemaFormat
|
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
|
||||||
InvalidCustomFormatSchemaValue,
|
|
||||||
UnmarshallerStrictTypeError,
|
|
||||||
FormatterNotFoundError,
|
|
||||||
)
|
|
||||||
from openapi_core.schema.schemas.util import (
|
|
||||||
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
|
||||||
format_number,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class StrictUnmarshaller(object):
|
|
||||||
|
|
||||||
STRICT_TYPES = ()
|
|
||||||
|
|
||||||
def __call__(self, value, type_format=SchemaFormat.NONE, strict=True):
|
|
||||||
if self.STRICT_TYPES and strict and not isinstance(
|
|
||||||
value, self.STRICT_TYPES):
|
|
||||||
raise UnmarshallerStrictTypeError(value, self.STRICT_TYPES)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class PrimitiveTypeUnmarshaller(StrictUnmarshaller):
|
|
||||||
|
|
||||||
FORMATTERS = {
|
|
||||||
SchemaFormat.NONE: lambda x: x,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, custom_formatters=None):
|
|
||||||
if custom_formatters is None:
|
|
||||||
custom_formatters = {}
|
|
||||||
self.custom_formatters = custom_formatters
|
|
||||||
|
|
||||||
def __call__(self, value, type_format=SchemaFormat.NONE, strict=True):
|
|
||||||
value = super(PrimitiveTypeUnmarshaller, self).__call__(
|
|
||||||
value, type_format=type_format, strict=strict)
|
|
||||||
|
|
||||||
try:
|
|
||||||
schema_format = SchemaFormat(type_format)
|
|
||||||
except ValueError:
|
|
||||||
formatter = self.custom_formatters.get(type_format)
|
|
||||||
else:
|
|
||||||
formatters = self.get_formatters()
|
|
||||||
formatter = formatters.get(schema_format)
|
|
||||||
|
|
||||||
if formatter is None:
|
|
||||||
raise FormatterNotFoundError(value, type_format)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return formatter(value)
|
|
||||||
except ValueError as exc:
|
|
||||||
raise InvalidCustomFormatSchemaValue(value, type_format, exc)
|
|
||||||
|
|
||||||
def get_formatters(self):
|
|
||||||
return self.FORMATTERS
|
|
||||||
|
|
||||||
|
|
||||||
class StringUnmarshaller(PrimitiveTypeUnmarshaller):
|
|
||||||
|
|
||||||
STRICT_TYPES = (text_type, binary_type)
|
|
||||||
FORMATTERS = {
|
|
||||||
SchemaFormat.NONE: text_type,
|
|
||||||
SchemaFormat.PASSWORD: text_type,
|
|
||||||
SchemaFormat.DATE: format_date,
|
|
||||||
SchemaFormat.DATETIME: format_datetime,
|
|
||||||
SchemaFormat.BINARY: binary_type,
|
|
||||||
SchemaFormat.UUID: format_uuid,
|
|
||||||
SchemaFormat.BYTE: format_byte,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class IntegerUnmarshaller(PrimitiveTypeUnmarshaller):
|
|
||||||
|
|
||||||
STRICT_TYPES = integer_types
|
|
||||||
FORMATTERS = {
|
|
||||||
SchemaFormat.NONE: int,
|
|
||||||
SchemaFormat.INT32: int,
|
|
||||||
SchemaFormat.INT64: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NumberUnmarshaller(PrimitiveTypeUnmarshaller):
|
|
||||||
|
|
||||||
STRICT_TYPES = (float, ) + integer_types
|
|
||||||
FORMATTERS = {
|
|
||||||
SchemaFormat.NONE: format_number,
|
|
||||||
SchemaFormat.FLOAT: float,
|
|
||||||
SchemaFormat.DOUBLE: float,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class BooleanUnmarshaller(PrimitiveTypeUnmarshaller):
|
|
||||||
|
|
||||||
STRICT_TYPES = (bool, )
|
|
||||||
FORMATTERS = {
|
|
||||||
SchemaFormat.NONE: forcebool,
|
|
||||||
}
|
|
|
@ -1,11 +1,7 @@
|
||||||
"""OpenAPI core schemas util module"""
|
"""OpenAPI core schemas util module"""
|
||||||
from base64 import b64decode
|
|
||||||
import datetime
|
|
||||||
from distutils.util import strtobool
|
from distutils.util import strtobool
|
||||||
|
from six import string_types
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from six import string_types, text_type, integer_types
|
|
||||||
import strict_rfc3339
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
|
|
||||||
def forcebool(val):
|
def forcebool(val):
|
||||||
|
@ -17,29 +13,3 @@ def forcebool(val):
|
||||||
|
|
||||||
def dicthash(d):
|
def dicthash(d):
|
||||||
return hash(dumps(d, sort_keys=True))
|
return hash(dumps(d, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
def format_date(value):
|
|
||||||
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
|
|
||||||
|
|
||||||
|
|
||||||
def format_datetime(value):
|
|
||||||
timestamp = strict_rfc3339.rfc3339_to_timestamp(value)
|
|
||||||
return datetime.datetime.utcfromtimestamp(timestamp)
|
|
||||||
|
|
||||||
|
|
||||||
def format_uuid(value):
|
|
||||||
if isinstance(value, UUID):
|
|
||||||
return value
|
|
||||||
return UUID(value)
|
|
||||||
|
|
||||||
|
|
||||||
def format_byte(value, encoding='utf8'):
|
|
||||||
return text_type(b64decode(value), encoding)
|
|
||||||
|
|
||||||
|
|
||||||
def format_number(value):
|
|
||||||
if isinstance(value, integer_types + (float, )):
|
|
||||||
return value
|
|
||||||
|
|
||||||
return float(value)
|
|
||||||
|
|
0
openapi_core/unmarshalling/__init__.py
Normal file
0
openapi_core/unmarshalling/__init__.py
Normal file
0
openapi_core/unmarshalling/schemas/__init__.py
Normal file
0
openapi_core/unmarshalling/schemas/__init__.py
Normal file
66
openapi_core/unmarshalling/schemas/exceptions.py
Normal file
66
openapi_core/unmarshalling/schemas/exceptions.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import attr
|
||||||
|
|
||||||
|
from openapi_core.exceptions import OpenAPIError
|
||||||
|
|
||||||
|
|
||||||
|
class UnmarshalError(OpenAPIError):
|
||||||
|
"""Schema unmarshal operation error"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnmarshallerError(UnmarshalError):
|
||||||
|
"""Unmarshaller error"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(hash=True)
|
||||||
|
class UnmarshalValueError(UnmarshalError):
|
||||||
|
"""Failed to unmarshal value to type"""
|
||||||
|
value = attr.ib()
|
||||||
|
type = attr.ib()
|
||||||
|
original_exception = attr.ib(default=None)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (
|
||||||
|
"Failed to unmarshal value {value} to type {type}: {exception}"
|
||||||
|
).format(
|
||||||
|
value=self.value, type=self.type,
|
||||||
|
exception=self.original_exception,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(hash=True)
|
||||||
|
class InvalidCustomFormatSchemaValue(UnmarshallerError):
|
||||||
|
"""Value failed to format with custom formatter"""
|
||||||
|
value = attr.ib()
|
||||||
|
type = attr.ib()
|
||||||
|
original_exception = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (
|
||||||
|
"Failed to format value {value} to format {type}: {exception}"
|
||||||
|
).format(
|
||||||
|
value=self.value, type=self.type,
|
||||||
|
exception=self.original_exception,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(hash=True)
|
||||||
|
class FormatterNotFoundError(UnmarshallerError):
|
||||||
|
"""Formatter not found to unmarshal"""
|
||||||
|
type_format = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Formatter not found for {format} format".format(
|
||||||
|
format=self.type_format)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(hash=True)
|
||||||
|
class UnmarshallerStrictTypeError(UnmarshallerError):
|
||||||
|
value = attr.ib()
|
||||||
|
types = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
types = ', '.join(list(map(str, self.types)))
|
||||||
|
return "Value {value} is not one of types: {types}".format(
|
||||||
|
value=self.value, types=types)
|
62
openapi_core/unmarshalling/schemas/factories.py
Normal file
62
openapi_core/unmarshalling/schemas/factories.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from openapi_core.schema.schemas.enums import SchemaType, SchemaFormat
|
||||||
|
from openapi_core.unmarshalling.schemas.exceptions import (
|
||||||
|
FormatterNotFoundError,
|
||||||
|
)
|
||||||
|
from openapi_core.unmarshalling.schemas.unmarshallers import (
|
||||||
|
StringUnmarshaller, IntegerUnmarshaller, NumberUnmarshaller,
|
||||||
|
BooleanUnmarshaller, ArrayUnmarshaller, ObjectUnmarshaller,
|
||||||
|
AnyUnmarshaller,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SchemaUnmarshallersFactory(object):
|
||||||
|
|
||||||
|
PRIMITIVE_UNMARSHALLERS = {
|
||||||
|
SchemaType.STRING: StringUnmarshaller,
|
||||||
|
SchemaType.INTEGER: IntegerUnmarshaller,
|
||||||
|
SchemaType.NUMBER: NumberUnmarshaller,
|
||||||
|
SchemaType.BOOLEAN: BooleanUnmarshaller,
|
||||||
|
}
|
||||||
|
COMPLEX_UNMARSHALLERS = {
|
||||||
|
SchemaType.ARRAY: ArrayUnmarshaller,
|
||||||
|
SchemaType.OBJECT: ObjectUnmarshaller,
|
||||||
|
SchemaType.ANY: AnyUnmarshaller,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, custom_formatters=None):
|
||||||
|
if custom_formatters is None:
|
||||||
|
custom_formatters = {}
|
||||||
|
self.custom_formatters = custom_formatters
|
||||||
|
|
||||||
|
def create(self, schema, type_override=None):
|
||||||
|
"""Create unmarshaller from the schema."""
|
||||||
|
if schema.deprecated:
|
||||||
|
warnings.warn("The schema is deprecated", DeprecationWarning)
|
||||||
|
|
||||||
|
schema_type = type_override or schema.type
|
||||||
|
if schema_type in self.PRIMITIVE_UNMARSHALLERS:
|
||||||
|
klass = self.PRIMITIVE_UNMARSHALLERS[schema_type]
|
||||||
|
kwargs = dict(schema=schema)
|
||||||
|
|
||||||
|
elif schema_type in self.COMPLEX_UNMARSHALLERS:
|
||||||
|
klass = self.COMPLEX_UNMARSHALLERS[schema_type]
|
||||||
|
kwargs = dict(schema=schema, unmarshallers_factory=self)
|
||||||
|
|
||||||
|
formatter = self.get_formatter(klass.FORMATTERS, schema.format)
|
||||||
|
|
||||||
|
if formatter is None:
|
||||||
|
raise FormatterNotFoundError(schema.format)
|
||||||
|
|
||||||
|
return klass(formatter, **kwargs)
|
||||||
|
|
||||||
|
def get_formatter(self, formatters, type_format=SchemaFormat.NONE):
|
||||||
|
try:
|
||||||
|
schema_format = SchemaFormat(type_format)
|
||||||
|
except ValueError:
|
||||||
|
return self.custom_formatters.get(type_format)
|
||||||
|
else:
|
||||||
|
if schema_format == SchemaFormat.NONE:
|
||||||
|
return lambda x: x
|
||||||
|
return formatters.get(schema_format)
|
240
openapi_core/unmarshalling/schemas/unmarshallers.py
Normal file
240
openapi_core/unmarshalling/schemas/unmarshallers.py
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from six import text_type, binary_type, integer_types
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
from openapi_core.extensions.models.factories import ModelFactory
|
||||||
|
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
|
ValidateError,
|
||||||
|
)
|
||||||
|
from openapi_core.schema.schemas.types import NoValue
|
||||||
|
from openapi_core.unmarshalling.schemas.exceptions import (
|
||||||
|
UnmarshalError,
|
||||||
|
InvalidCustomFormatSchemaValue,
|
||||||
|
UnmarshallerStrictTypeError,
|
||||||
|
)
|
||||||
|
from openapi_core.unmarshalling.schemas.util import (
|
||||||
|
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
||||||
|
format_number,
|
||||||
|
)
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class StrictUnmarshaller(object):
|
||||||
|
|
||||||
|
STRICT_TYPES = ()
|
||||||
|
|
||||||
|
def __call__(self, value, strict=True):
|
||||||
|
if strict and not self._is_strict(value):
|
||||||
|
raise UnmarshallerStrictTypeError(value, self.STRICT_TYPES)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _is_strict(self, value):
|
||||||
|
if not self.STRICT_TYPES:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return isinstance(value, self.STRICT_TYPES)
|
||||||
|
|
||||||
|
|
||||||
|
class PrimitiveTypeUnmarshaller(StrictUnmarshaller):
|
||||||
|
|
||||||
|
FORMATTERS = {}
|
||||||
|
|
||||||
|
def __init__(self, formatter, schema):
|
||||||
|
self.formatter = formatter
|
||||||
|
self.schema = schema
|
||||||
|
|
||||||
|
def __call__(self, value=NoValue, strict=True):
|
||||||
|
if value is NoValue:
|
||||||
|
value = self.schema.default
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
value = super(PrimitiveTypeUnmarshaller, self).__call__(
|
||||||
|
value, strict=strict)
|
||||||
|
|
||||||
|
return self.format(value)
|
||||||
|
|
||||||
|
def format(self, value):
|
||||||
|
try:
|
||||||
|
return self.formatter(value)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise InvalidCustomFormatSchemaValue(
|
||||||
|
value, self.schema.format, exc)
|
||||||
|
|
||||||
|
|
||||||
|
class StringUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||||
|
|
||||||
|
STRICT_TYPES = (text_type, binary_type)
|
||||||
|
FORMATTERS = {
|
||||||
|
SchemaFormat.NONE: text_type,
|
||||||
|
SchemaFormat.PASSWORD: text_type,
|
||||||
|
SchemaFormat.DATE: format_date,
|
||||||
|
SchemaFormat.DATETIME: format_datetime,
|
||||||
|
SchemaFormat.BINARY: binary_type,
|
||||||
|
SchemaFormat.UUID: format_uuid,
|
||||||
|
SchemaFormat.BYTE: format_byte,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IntegerUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||||
|
|
||||||
|
STRICT_TYPES = integer_types
|
||||||
|
FORMATTERS = {
|
||||||
|
SchemaFormat.NONE: int,
|
||||||
|
SchemaFormat.INT32: int,
|
||||||
|
SchemaFormat.INT64: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NumberUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||||
|
|
||||||
|
STRICT_TYPES = (float, ) + integer_types
|
||||||
|
FORMATTERS = {
|
||||||
|
SchemaFormat.NONE: format_number,
|
||||||
|
SchemaFormat.FLOAT: float,
|
||||||
|
SchemaFormat.DOUBLE: float,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||||
|
|
||||||
|
STRICT_TYPES = (bool, )
|
||||||
|
FORMATTERS = {
|
||||||
|
SchemaFormat.NONE: forcebool,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexUnmarshaller(PrimitiveTypeUnmarshaller):
|
||||||
|
|
||||||
|
def __init__(self, formatter, schema, unmarshallers_factory):
|
||||||
|
super(ComplexUnmarshaller, self).__init__(formatter, schema)
|
||||||
|
self.unmarshallers_factory = unmarshallers_factory
|
||||||
|
|
||||||
|
|
||||||
|
class ArrayUnmarshaller(ComplexUnmarshaller):
|
||||||
|
|
||||||
|
STRICT_TYPES = (list, tuple)
|
||||||
|
FORMATTERS = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def items_unmarshaller(self):
|
||||||
|
return self.unmarshallers_factory.create(self.schema.items)
|
||||||
|
|
||||||
|
def __call__(self, value=NoValue, strict=True):
|
||||||
|
value = super(ArrayUnmarshaller, self).__call__(value, strict=strict)
|
||||||
|
|
||||||
|
self.unmarshallers_factory.create(self.schema.items)
|
||||||
|
|
||||||
|
return list(map(self.items_unmarshaller, value))
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectUnmarshaller(ComplexUnmarshaller):
|
||||||
|
|
||||||
|
STRICT_TYPES = (dict, )
|
||||||
|
FORMATTERS = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model_factory(self):
|
||||||
|
return ModelFactory()
|
||||||
|
|
||||||
|
def __call__(self, value=NoValue, strict=True):
|
||||||
|
value = super(ObjectUnmarshaller, self).__call__(value, strict=strict)
|
||||||
|
|
||||||
|
if self.schema.one_of:
|
||||||
|
properties = None
|
||||||
|
for one_of_schema in self.schema.one_of:
|
||||||
|
try:
|
||||||
|
unmarshalled = self._unmarshal_properties(
|
||||||
|
value, one_of_schema, strict=strict)
|
||||||
|
except (UnmarshalError, ValueError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if properties is not None:
|
||||||
|
log.warning("multiple valid oneOf schemas found")
|
||||||
|
continue
|
||||||
|
properties = unmarshalled
|
||||||
|
|
||||||
|
if properties is None:
|
||||||
|
log.warning("valid oneOf schema not found")
|
||||||
|
|
||||||
|
else:
|
||||||
|
properties = self._unmarshal_properties(value)
|
||||||
|
|
||||||
|
if 'x-model' in self.schema.extensions:
|
||||||
|
extension = self.schema.extensions['x-model']
|
||||||
|
return self.model_factory.create(properties, name=extension.value)
|
||||||
|
|
||||||
|
return properties
|
||||||
|
|
||||||
|
def _unmarshal_properties(
|
||||||
|
self, value=NoValue, one_of_schema=None, strict=True):
|
||||||
|
all_props = self.schema.get_all_properties()
|
||||||
|
all_props_names = self.schema.get_all_properties_names()
|
||||||
|
|
||||||
|
if one_of_schema is not None:
|
||||||
|
all_props.update(one_of_schema.get_all_properties())
|
||||||
|
all_props_names |= one_of_schema.\
|
||||||
|
get_all_properties_names()
|
||||||
|
|
||||||
|
value_props_names = value.keys()
|
||||||
|
extra_props = set(value_props_names) - set(all_props_names)
|
||||||
|
|
||||||
|
properties = {}
|
||||||
|
if self.schema.additional_properties is not True:
|
||||||
|
for prop_name in extra_props:
|
||||||
|
prop_value = value[prop_name]
|
||||||
|
properties[prop_name] = self.unmarshallers_factory.create(
|
||||||
|
self.schema.additional_properties)(
|
||||||
|
prop_value, strict=strict)
|
||||||
|
|
||||||
|
for prop_name, prop in iteritems(all_props):
|
||||||
|
try:
|
||||||
|
prop_value = value[prop_name]
|
||||||
|
except KeyError:
|
||||||
|
if prop.default is NoValue:
|
||||||
|
continue
|
||||||
|
prop_value = prop.default
|
||||||
|
|
||||||
|
properties[prop_name] = self.unmarshallers_factory.create(
|
||||||
|
prop)(prop_value, strict=strict)
|
||||||
|
|
||||||
|
return properties
|
||||||
|
|
||||||
|
|
||||||
|
class AnyUnmarshaller(ComplexUnmarshaller):
|
||||||
|
|
||||||
|
SCHEMA_TYPES_ORDER = [
|
||||||
|
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
|
||||||
|
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
|
||||||
|
]
|
||||||
|
|
||||||
|
def __call__(self, value=NoValue, strict=True):
|
||||||
|
one_of_schema = self._get_one_of_schema(value)
|
||||||
|
if one_of_schema:
|
||||||
|
return self.unmarshallers_factory.create(one_of_schema)(
|
||||||
|
value, strict=strict)
|
||||||
|
|
||||||
|
for schema_type in self.SCHEMA_TYPES_ORDER:
|
||||||
|
try:
|
||||||
|
unmarshaller = self.unmarshallers_factory.create(
|
||||||
|
self.schema, type_override=schema_type)
|
||||||
|
return unmarshaller(value, strict=strict)
|
||||||
|
except (UnmarshalError, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.warning("failed to unmarshal any type")
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _get_one_of_schema(self, value):
|
||||||
|
if not self.schema.one_of:
|
||||||
|
return
|
||||||
|
for subschema in self.schema.one_of:
|
||||||
|
try:
|
||||||
|
subschema.validate(value)
|
||||||
|
except ValidateError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return subschema
|
40
openapi_core/unmarshalling/schemas/util.py
Normal file
40
openapi_core/unmarshalling/schemas/util.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
"""OpenAPI core schemas util module"""
|
||||||
|
from base64 import b64decode
|
||||||
|
import datetime
|
||||||
|
from distutils.util import strtobool
|
||||||
|
from six import string_types, text_type, integer_types
|
||||||
|
import strict_rfc3339
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
|
||||||
|
def forcebool(val):
|
||||||
|
if isinstance(val, string_types):
|
||||||
|
val = strtobool(val)
|
||||||
|
|
||||||
|
return bool(val)
|
||||||
|
|
||||||
|
|
||||||
|
def format_date(value):
|
||||||
|
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
|
||||||
|
|
||||||
|
|
||||||
|
def format_datetime(value):
|
||||||
|
timestamp = strict_rfc3339.rfc3339_to_timestamp(value)
|
||||||
|
return datetime.datetime.utcfromtimestamp(timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
def format_uuid(value):
|
||||||
|
if isinstance(value, UUID):
|
||||||
|
return value
|
||||||
|
return UUID(value)
|
||||||
|
|
||||||
|
|
||||||
|
def format_byte(value, encoding='utf8'):
|
||||||
|
return text_type(b64decode(value), encoding)
|
||||||
|
|
||||||
|
|
||||||
|
def format_number(value):
|
||||||
|
if isinstance(value, integer_types + (float, )):
|
||||||
|
return value
|
||||||
|
|
||||||
|
return float(value)
|
|
@ -10,6 +10,18 @@ from six.moves.urllib.parse import urljoin
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class RequestParameters(object):
|
class RequestParameters(object):
|
||||||
|
"""OpenAPI request parameters dataclass.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
path
|
||||||
|
Path parameters as dict.
|
||||||
|
query
|
||||||
|
Query string parameters as MultiDict. Must support getlist method.
|
||||||
|
header
|
||||||
|
Request headers as dict.
|
||||||
|
cookie
|
||||||
|
Request cookies as dict.
|
||||||
|
"""
|
||||||
path = attr.ib(factory=dict)
|
path = attr.ib(factory=dict)
|
||||||
query = attr.ib(factory=ImmutableMultiDict)
|
query = attr.ib(factory=ImmutableMultiDict)
|
||||||
header = attr.ib(factory=dict)
|
header = attr.ib(factory=dict)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from openapi_core.schema.media_types.exceptions import (
|
||||||
)
|
)
|
||||||
from openapi_core.schema.operations.exceptions import InvalidOperation
|
from openapi_core.schema.operations.exceptions import InvalidOperation
|
||||||
from openapi_core.schema.parameters.exceptions import (
|
from openapi_core.schema.parameters.exceptions import (
|
||||||
OpenAPIParameterError, MissingRequiredParameter,
|
OpenAPIParameterError, MissingRequiredParameter, MissingParameter,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.paths.exceptions import InvalidPath
|
from openapi_core.schema.paths.exceptions import InvalidPath
|
||||||
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
|
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
|
||||||
|
@ -74,9 +74,11 @@ class RequestValidator(object):
|
||||||
except MissingRequiredParameter as exc:
|
except MissingRequiredParameter as exc:
|
||||||
errors.append(exc)
|
errors.append(exc)
|
||||||
continue
|
continue
|
||||||
except OpenAPIParameterError:
|
except MissingParameter:
|
||||||
|
if not param.schema or not param.schema.has_default():
|
||||||
continue
|
continue
|
||||||
|
casted = param.schema.default
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
casted = param.cast(raw_value)
|
casted = param.cast(raw_value)
|
||||||
except OpenAPIParameterError as exc:
|
except OpenAPIParameterError as exc:
|
||||||
|
|
|
@ -278,6 +278,7 @@ components:
|
||||||
$ref: "#/components/schemas/Pet"
|
$ref: "#/components/schemas/Pet"
|
||||||
PetsData:
|
PetsData:
|
||||||
type: object
|
type: object
|
||||||
|
x-model: PetsData
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
properties:
|
properties:
|
||||||
|
@ -285,6 +286,7 @@ components:
|
||||||
$ref: "#/components/schemas/Pets"
|
$ref: "#/components/schemas/Pets"
|
||||||
PetData:
|
PetData:
|
||||||
type: object
|
type: object
|
||||||
|
x-model: PetData
|
||||||
required:
|
required:
|
||||||
- data
|
- data
|
||||||
properties:
|
properties:
|
||||||
|
@ -292,6 +294,7 @@ components:
|
||||||
$ref: "#/components/schemas/Pet"
|
$ref: "#/components/schemas/Pet"
|
||||||
TagCreate:
|
TagCreate:
|
||||||
type: object
|
type: object
|
||||||
|
x-model: TagCreate
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
properties:
|
properties:
|
||||||
|
@ -316,6 +319,7 @@ components:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
ExtendedError:
|
ExtendedError:
|
||||||
|
x-model: ExtendedError
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/Error"
|
- $ref: "#/components/schemas/Error"
|
||||||
- type: object
|
- type: object
|
||||||
|
|
|
@ -7,11 +7,16 @@ import pytest
|
||||||
from openapi_core.extensions.models.models import Model
|
from openapi_core.extensions.models.models import Model
|
||||||
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
from openapi_core.schema.schemas.exceptions import (
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
InvalidSchemaValue, OpenAPISchemaError, UnmarshallerStrictTypeError,
|
InvalidSchemaValue, OpenAPISchemaError,
|
||||||
UnmarshalValueError, UnmarshalError, InvalidCustomFormatSchemaValue,
|
|
||||||
FormatterNotFoundError,
|
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.models import Schema
|
from openapi_core.schema.schemas.models import Schema
|
||||||
|
from openapi_core.schema.schemas.types import NoValue
|
||||||
|
from openapi_core.unmarshalling.schemas.exceptions import (
|
||||||
|
InvalidCustomFormatSchemaValue,
|
||||||
|
FormatterNotFoundError,
|
||||||
|
UnmarshalError,
|
||||||
|
UnmarshallerStrictTypeError,
|
||||||
|
)
|
||||||
|
|
||||||
from six import b, u
|
from six import b, u
|
||||||
|
|
||||||
|
@ -50,9 +55,8 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema(schema_type)
|
schema = Schema(schema_type)
|
||||||
value = ''
|
value = ''
|
||||||
|
|
||||||
result = schema.unmarshal(value)
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
|
schema.unmarshal(value)
|
||||||
assert result is None
|
|
||||||
|
|
||||||
def test_string_valid(self):
|
def test_string_valid(self):
|
||||||
schema = Schema('string')
|
schema = Schema('string')
|
||||||
|
@ -93,25 +97,19 @@ class TestSchemaUnmarshal(object):
|
||||||
with pytest.raises(UnmarshallerStrictTypeError):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_string_none(self):
|
|
||||||
schema = Schema('string')
|
|
||||||
value = None
|
|
||||||
|
|
||||||
with pytest.raises(UnmarshalError):
|
|
||||||
schema.unmarshal(value)
|
|
||||||
|
|
||||||
def test_string_default(self):
|
def test_string_default(self):
|
||||||
default_value = 'default'
|
default_value = 'default'
|
||||||
schema = Schema('string', default=default_value)
|
schema = Schema('string', default=default_value)
|
||||||
value = None
|
value = NoValue
|
||||||
|
|
||||||
with pytest.raises(UnmarshalError):
|
result = schema.unmarshal(value)
|
||||||
schema.unmarshal(value)
|
|
||||||
|
|
||||||
def test_string_default_nullable(self):
|
assert result == default_value
|
||||||
default_value = 'default'
|
|
||||||
|
@pytest.mark.parametrize('default_value', ['default', None])
|
||||||
|
def test_string_default_nullable(self, default_value):
|
||||||
schema = Schema('string', default=default_value, nullable=True)
|
schema = Schema('string', default=default_value, nullable=True)
|
||||||
value = None
|
value = NoValue
|
||||||
|
|
||||||
result = schema.unmarshal(value)
|
result = schema.unmarshal(value)
|
||||||
|
|
||||||
|
@ -161,7 +159,7 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('string', schema_format=unknown_format)
|
schema = Schema('string', schema_format=unknown_format)
|
||||||
value = 'x'
|
value = 'x'
|
||||||
|
|
||||||
with pytest.raises(OpenAPISchemaError):
|
with pytest.raises(FormatterNotFoundError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_string_format_invalid_value(self):
|
def test_string_format_invalid_value(self):
|
||||||
|
@ -172,7 +170,7 @@ class TestSchemaUnmarshal(object):
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
FormatterNotFoundError,
|
FormatterNotFoundError,
|
||||||
message=(
|
message=(
|
||||||
'Formatter not found for custom format to unmarshal value x'
|
'Formatter not found for custom format'
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
@ -215,21 +213,22 @@ class TestSchemaUnmarshal(object):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_integer_default(self):
|
def test_integer_default(self):
|
||||||
default_value = '123'
|
default_value = 123
|
||||||
schema = Schema('integer', default=default_value)
|
schema = Schema('integer', default=default_value)
|
||||||
value = None
|
value = NoValue
|
||||||
|
|
||||||
with pytest.raises(UnmarshalError):
|
result = schema.unmarshal(value)
|
||||||
schema.unmarshal(value)
|
|
||||||
|
assert result == default_value
|
||||||
|
|
||||||
def test_integer_default_nullable(self):
|
def test_integer_default_nullable(self):
|
||||||
default_value = '123'
|
default_value = 123
|
||||||
schema = Schema('integer', default=default_value, nullable=True)
|
schema = Schema('integer', default=default_value, nullable=True)
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
result = schema.unmarshal(value)
|
result = schema.unmarshal(value)
|
||||||
|
|
||||||
assert result == default_value
|
assert result is None
|
||||||
|
|
||||||
def test_integer_invalid(self):
|
def test_integer_invalid(self):
|
||||||
schema = Schema('integer')
|
schema = Schema('integer')
|
||||||
|
@ -250,14 +249,14 @@ class TestSchemaUnmarshal(object):
|
||||||
schema = Schema('array', items=Schema('string'))
|
schema = Schema('array', items=Schema('string'))
|
||||||
value = '123'
|
value = '123'
|
||||||
|
|
||||||
with pytest.raises(UnmarshalValueError):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_array_of_integer_string_invalid(self):
|
def test_array_of_integer_string_invalid(self):
|
||||||
schema = Schema('array', items=Schema('integer'))
|
schema = Schema('array', items=Schema('integer'))
|
||||||
value = '123'
|
value = '123'
|
||||||
|
|
||||||
with pytest.raises(UnmarshalValueError):
|
with pytest.raises(UnmarshallerStrictTypeError):
|
||||||
schema.unmarshal(value)
|
schema.unmarshal(value)
|
||||||
|
|
||||||
def test_boolean_valid(self):
|
def test_boolean_valid(self):
|
||||||
|
@ -702,7 +701,7 @@ class TestSchemaValidate(object):
|
||||||
unknown_format = 'unknown'
|
unknown_format = 'unknown'
|
||||||
schema = Schema('string', schema_format=unknown_format)
|
schema = Schema('string', schema_format=unknown_format)
|
||||||
|
|
||||||
with pytest.raises(OpenAPISchemaError):
|
with pytest.raises(InvalidSchemaValue):
|
||||||
schema.validate(value)
|
schema.validate(value)
|
||||||
|
|
||||||
@pytest.mark.parametrize('value', [u(""), u("a"), u("ab")])
|
@pytest.mark.parametrize('value', [u(""), u("a"), u("ab")])
|
||||||
|
|
Loading…
Reference in a new issue