openapi-core/openapi_core/unmarshalling/schemas/unmarshallers.py

310 lines
10 KiB
Python
Raw Normal View History

2020-01-28 15:40:45 +00:00
from functools import partial
import logging
2020-04-11 12:45:46 +00:00
from isodate.isodatetime import parse_datetime
2020-03-05 11:28:21 +00:00
from openapi_schema_validator._types import (
is_array, is_bool, is_integer,
is_object, is_number, is_string,
)
from openapi_schema_validator._format import oas30_format_checker
2020-01-28 15:40:45 +00:00
from six import text_type, binary_type
from six import iteritems
from openapi_core.extensions.models.factories import ModelFactory
2021-04-23 11:36:27 +00:00
from openapi_core.spec.schemas import (
get_all_properties, get_all_properties_names
)
2021-04-27 21:16:30 +00:00
from openapi_core.types import NoValue
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
from openapi_core.unmarshalling.schemas.exceptions import (
2020-01-28 15:40:45 +00:00
UnmarshalError, ValidateError, InvalidSchemaValue,
InvalidSchemaFormatValue,
)
2020-01-28 15:40:45 +00:00
from openapi_core.unmarshalling.schemas.formatters import Formatter
from openapi_core.unmarshalling.schemas.util import (
2020-04-11 12:45:46 +00:00
forcebool, format_date, format_byte, format_uuid,
format_number,
)
log = logging.getLogger(__name__)
2020-01-28 15:40:45 +00:00
class PrimitiveTypeUnmarshaller(object):
FORMATTERS = {}
2020-01-28 15:40:45 +00:00
def __init__(self, formatter, validator, schema):
self.formatter = formatter
2020-01-28 15:40:45 +00:00
self.validator = validator
self.schema = schema
2020-01-28 15:40:45 +00:00
def __call__(self, value=NoValue):
if value is NoValue:
2021-04-23 11:36:27 +00:00
value = self.schema.getkey('default')
if value is None:
return
2020-01-28 15:40:45 +00:00
self.validate(value)
return self.unmarshal(value)
def _formatter_validate(self, value):
result = self.formatter.validate(value)
if not result:
2021-04-23 11:36:27 +00:00
schema_type = self.schema.getkey('type', 'any')
raise InvalidSchemaValue(value, schema_type)
2020-01-28 15:40:45 +00:00
def validate(self, value):
errors_iter = self.validator.iter_errors(value)
errors = tuple(errors_iter)
if errors:
2021-04-23 11:36:27 +00:00
schema_type = self.schema.getkey('type', 'any')
2020-01-28 15:40:45 +00:00
raise InvalidSchemaValue(
2021-04-23 11:36:27 +00:00
value, schema_type, schema_errors=errors)
2020-01-28 15:40:45 +00:00
def unmarshal(self, value):
try:
2020-01-28 15:40:45 +00:00
return self.formatter.unmarshal(value)
except ValueError as exc:
2021-04-23 11:36:27 +00:00
schema_format = self.schema.getkey('format')
2020-01-28 15:40:45 +00:00
raise InvalidSchemaFormatValue(
2021-04-23 11:36:27 +00:00
value, schema_format, exc)
class StringUnmarshaller(PrimitiveTypeUnmarshaller):
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(is_string, None), text_type),
2021-04-27 21:16:30 +00:00
'password': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='password'), text_type),
2021-04-27 21:16:30 +00:00
'date': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='date'), format_date),
2021-04-27 21:16:30 +00:00
'date-time': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='date-time'),
2020-04-11 12:45:46 +00:00
parse_datetime),
2021-04-27 21:16:30 +00:00
'binary': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='binary'), binary_type),
2021-04-27 21:16:30 +00:00
'uuid': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='uuid'), format_uuid),
2021-04-27 21:16:30 +00:00
'byte': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='byte'), format_byte),
}
class IntegerUnmarshaller(PrimitiveTypeUnmarshaller):
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(is_integer, None), int),
2021-04-27 21:16:30 +00:00
'int32': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='int32'), int),
2021-04-27 21:16:30 +00:00
'int64': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='int64'), int),
}
class NumberUnmarshaller(PrimitiveTypeUnmarshaller):
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(is_number, None), format_number),
2021-04-27 21:16:30 +00:00
'float': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='float'), float),
2021-04-27 21:16:30 +00:00
'double': Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(oas30_format_checker.check, format='double'), float),
}
class BooleanUnmarshaller(PrimitiveTypeUnmarshaller):
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(is_bool, None), forcebool),
}
class ComplexUnmarshaller(PrimitiveTypeUnmarshaller):
def __init__(
self, formatter, validator, schema, unmarshallers_factory,
context=None):
2020-01-28 15:40:45 +00:00
super(ComplexUnmarshaller, self).__init__(formatter, validator, schema)
self.unmarshallers_factory = unmarshallers_factory
self.context = context
class ArrayUnmarshaller(ComplexUnmarshaller):
2020-01-28 15:40:45 +00:00
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(is_array, None), list),
}
@property
def items_unmarshaller(self):
2021-04-23 11:36:27 +00:00
return self.unmarshallers_factory.create(self.schema / 'items')
2020-01-28 15:40:45 +00:00
def __call__(self, value=NoValue):
value = super(ArrayUnmarshaller, self).__call__(value)
2021-04-23 11:36:27 +00:00
if value is None and self.schema.getkey('nullable', False):
return None
return list(map(self.items_unmarshaller, value))
class ObjectUnmarshaller(ComplexUnmarshaller):
2020-01-28 15:40:45 +00:00
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter.from_callables(
2020-01-28 15:40:45 +00:00
partial(is_object, None), dict),
}
@property
def model_factory(self):
return ModelFactory()
2020-04-12 13:59:22 +00:00
def unmarshal(self, value):
try:
value = self.formatter.unmarshal(value)
except ValueError as exc:
raise InvalidSchemaFormatValue(
value, self.schema.format, exc)
else:
return self._unmarshal_object(value)
2020-04-12 13:59:22 +00:00
def _unmarshal_object(self, value=NoValue):
2021-04-23 11:36:27 +00:00
if 'oneOf' in self.schema:
properties = None
2021-04-23 11:36:27 +00:00
for one_of_schema in self.schema / 'oneOf':
try:
unmarshalled = self._unmarshal_properties(
2020-01-28 15:40:45 +00:00
value, one_of_schema)
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)
2021-04-23 11:36:27 +00:00
if 'x-model' in self.schema:
name = self.schema['x-model']
return self.model_factory.create(properties, name=name)
return properties
2020-01-28 15:40:45 +00:00
def _unmarshal_properties(self, value=NoValue, one_of_schema=None):
2021-04-23 11:36:27 +00:00
all_props = get_all_properties(self.schema)
all_props_names = get_all_properties_names(self.schema)
if one_of_schema is not None:
2021-04-23 11:36:27 +00:00
all_props.update(get_all_properties(one_of_schema))
all_props_names |= get_all_properties_names(one_of_schema)
value_props_names = value.keys()
extra_props = set(value_props_names) - set(all_props_names)
properties = {}
2021-04-23 11:36:27 +00:00
additional_properties = self.schema.getkey('additionalProperties', True)
if isinstance(additional_properties, dict):
additional_prop_schema = self.schema / 'additionalProperties'
for prop_name in extra_props:
prop_value = value[prop_name]
properties[prop_name] = self.unmarshallers_factory.create(
2021-04-23 11:36:27 +00:00
additional_prop_schema)(prop_value)
elif additional_properties is True:
2020-02-03 20:51:15 +00:00
for prop_name in extra_props:
prop_value = value[prop_name]
properties[prop_name] = prop_value
for prop_name, prop in iteritems(all_props):
2021-04-23 11:36:27 +00:00
read_only = prop.getkey('readOnly', False)
if self.context == UnmarshalContext.REQUEST and read_only:
continue
2021-04-23 11:36:27 +00:00
write_only = prop.getkey('writeOnly', False)
if self.context == UnmarshalContext.RESPONSE and write_only:
continue
try:
prop_value = value[prop_name]
except KeyError:
2021-04-23 11:36:27 +00:00
if 'default' not in prop:
continue
2021-04-23 11:36:27 +00:00
prop_value = prop['default']
properties[prop_name] = self.unmarshallers_factory.create(
2020-01-28 15:40:45 +00:00
prop)(prop_value)
return properties
class AnyUnmarshaller(ComplexUnmarshaller):
2020-01-28 15:40:45 +00:00
FORMATTERS = {
2021-04-27 21:16:30 +00:00
None: Formatter(),
2020-01-28 15:40:45 +00:00
}
SCHEMA_TYPES_ORDER = [
2021-04-23 11:36:27 +00:00
'object', 'array', 'boolean',
'integer', 'number', 'string',
]
2021-02-15 11:58:16 +00:00
def unmarshal(self, value=NoValue):
one_of_schema = self._get_one_of_schema(value)
if one_of_schema:
2020-01-28 15:40:45 +00:00
return self.unmarshallers_factory.create(one_of_schema)(value)
2021-02-08 22:55:45 +00:00
all_of_schema = self._get_all_of_schema(value)
if all_of_schema:
return self.unmarshallers_factory.create(all_of_schema)(value)
for schema_type in self.SCHEMA_TYPES_ORDER:
2020-01-28 15:40:45 +00:00
unmarshaller = self.unmarshallers_factory.create(
self.schema, type_override=schema_type)
# validate with validator of formatter (usualy type validator)
try:
2020-01-28 15:40:45 +00:00
unmarshaller._formatter_validate(value)
except ValidateError:
continue
2020-01-28 15:40:45 +00:00
else:
return unmarshaller(value)
log.warning("failed to unmarshal any type")
return value
def _get_one_of_schema(self, value):
2021-04-23 11:36:27 +00:00
if 'oneOf' not in self.schema:
return
2021-04-23 11:36:27 +00:00
one_of_schemas = self.schema / 'oneOf'
for subschema in one_of_schemas:
2020-01-28 15:40:45 +00:00
unmarshaller = self.unmarshallers_factory.create(subschema)
try:
2020-01-28 15:40:45 +00:00
unmarshaller.validate(value)
except ValidateError:
continue
else:
return subschema
2021-02-08 22:55:45 +00:00
def _get_all_of_schema(self, value):
2021-04-23 11:36:27 +00:00
if 'allOf' not in self.schema:
2021-02-08 22:55:45 +00:00
return
2021-04-23 11:36:27 +00:00
all_of_schemas = self.schema / 'allOf'
for subschema in all_of_schemas:
if 'type' not in subschema:
2021-02-08 22:55:45 +00:00
continue
unmarshaller = self.unmarshallers_factory.create(subschema)
try:
unmarshaller.validate(value)
except ValidateError:
continue
else:
return subschema