mirror of
https://github.com/correl/openapi-core.git
synced 2025-03-17 01:06:27 -09:00
Merge pull request #138 from p1c2u/feature/primitive-types-unmarshallers
Primitive types unmarshallers
This commit is contained in:
commit
78ede74825
5 changed files with 160 additions and 67 deletions
|
@ -47,6 +47,7 @@ class MediaType(object):
|
||||||
raise InvalidMediaTypeValue(exc)
|
raise InvalidMediaTypeValue(exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
return self.schema.validate(
|
||||||
|
unmarshalled, custom_formatters=custom_formatters)
|
||||||
except OpenAPISchemaError as exc:
|
except OpenAPISchemaError as exc:
|
||||||
raise InvalidMediaTypeValue(exc)
|
raise InvalidMediaTypeValue(exc)
|
||||||
|
|
|
@ -118,6 +118,7 @@ class Parameter(object):
|
||||||
raise InvalidParameterValue(self.name, exc)
|
raise InvalidParameterValue(self.name, exc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
|
return self.schema.validate(
|
||||||
|
unmarshalled, custom_formatters=custom_formatters)
|
||||||
except OpenAPISchemaError as exc:
|
except OpenAPISchemaError as exc:
|
||||||
raise InvalidParameterValue(self.name, exc)
|
raise InvalidParameterValue(self.name, exc)
|
||||||
|
|
|
@ -77,3 +77,17 @@ class MultipleOneOfSchema(OpenAPISchemaError):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)
|
return "Exactly one schema type {0} should be valid, more than one found".format(self.type)
|
||||||
|
|
||||||
|
|
||||||
|
class UnmarshallerError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class UnmarshallerStrictTypeError(UnmarshallerError):
|
||||||
|
value = attr.ib()
|
||||||
|
types = attr.ib()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Value {value} is not one of types {types}".format(
|
||||||
|
self.value, self.types)
|
||||||
|
|
|
@ -16,6 +16,7 @@ from openapi_core.schema.schemas.exceptions import (
|
||||||
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
|
||||||
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
|
||||||
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
|
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
|
||||||
|
UnmarshallerStrictTypeError,
|
||||||
)
|
)
|
||||||
from openapi_core.schema.schemas.util import (
|
from openapi_core.schema.schemas.util import (
|
||||||
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
forcebool, format_date, format_datetime, format_byte, format_uuid,
|
||||||
|
@ -155,14 +156,19 @@ class Schema(object):
|
||||||
return set(required)
|
return set(required)
|
||||||
|
|
||||||
def get_cast_mapping(self, custom_formatters=None, strict=True):
|
def get_cast_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()
|
||||||
|
)
|
||||||
|
|
||||||
pass_defaults = lambda f: functools.partial(
|
pass_defaults = lambda f: functools.partial(
|
||||||
f, custom_formatters=custom_formatters, strict=strict)
|
f, custom_formatters=custom_formatters, strict=strict)
|
||||||
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
|
||||||
|
mapping.update(primitive_unmarshallers_partial)
|
||||||
mapping.update({
|
mapping.update({
|
||||||
SchemaType.STRING: pass_defaults(self._unmarshal_string),
|
|
||||||
SchemaType.BOOLEAN: pass_defaults(self._unmarshal_boolean),
|
|
||||||
SchemaType.INTEGER: pass_defaults(self._unmarshal_integer),
|
|
||||||
SchemaType.NUMBER: pass_defaults(self._unmarshal_number),
|
|
||||||
SchemaType.ANY: pass_defaults(self._unmarshal_any),
|
SchemaType.ANY: pass_defaults(self._unmarshal_any),
|
||||||
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
|
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
|
||||||
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
|
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
|
||||||
|
@ -184,6 +190,10 @@ class Schema(object):
|
||||||
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
|
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
|
||||||
return self.default
|
return self.default
|
||||||
|
|
||||||
|
if self.enum and value not in self.enum:
|
||||||
|
raise InvalidSchemaValue(
|
||||||
|
"Value {value} not in enum choices: {type}", value, self.enum)
|
||||||
|
|
||||||
cast_mapping = self.get_cast_mapping(
|
cast_mapping = self.get_cast_mapping(
|
||||||
custom_formatters=custom_formatters, strict=strict)
|
custom_formatters=custom_formatters, strict=strict)
|
||||||
|
|
||||||
|
@ -193,6 +203,9 @@ class Schema(object):
|
||||||
cast_callable = cast_mapping[self.type]
|
cast_callable = cast_mapping[self.type]
|
||||||
try:
|
try:
|
||||||
return cast_callable(value)
|
return cast_callable(value)
|
||||||
|
except UnmarshallerStrictTypeError:
|
||||||
|
raise InvalidSchemaValue(
|
||||||
|
"Value {value} is not of type {type}", value, self.type)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise InvalidSchemaValue(
|
raise InvalidSchemaValue(
|
||||||
"Failed to cast value {value} to type {type}", value, self.type)
|
"Failed to cast value {value} to type {type}", value, self.type)
|
||||||
|
@ -207,72 +220,27 @@ class Schema(object):
|
||||||
if casted is None and not self.required:
|
if casted is None and not self.required:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.enum and casted not in self.enum:
|
|
||||||
raise InvalidSchemaValue(
|
|
||||||
"Value {value} not in enum choices: {type}", value, self.enum)
|
|
||||||
|
|
||||||
return casted
|
return casted
|
||||||
|
|
||||||
def _unmarshal_string(self, value, custom_formatters=None, strict=True):
|
def get_primitive_unmarshallers(self, **options):
|
||||||
if strict and not isinstance(value, (text_type, binary_type)):
|
from openapi_core.schema.schemas.unmarshallers import (
|
||||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller,
|
||||||
|
NumberUnmarshaller,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
unmarshallers_classes = {
|
||||||
schema_format = SchemaFormat(self.format)
|
SchemaType.STRING: StringUnmarshaller,
|
||||||
except ValueError:
|
SchemaType.BOOLEAN: BooleanUnmarshaller,
|
||||||
msg = "Unsupported format {type} unmarshalling for value {value}"
|
SchemaType.INTEGER: IntegerUnmarshaller,
|
||||||
if custom_formatters is not None:
|
SchemaType.NUMBER: NumberUnmarshaller,
|
||||||
formatstring = custom_formatters.get(self.format)
|
}
|
||||||
if formatstring is None:
|
|
||||||
raise InvalidSchemaValue(msg, value, self.format)
|
|
||||||
else:
|
|
||||||
raise InvalidSchemaValue(msg, value, self.format)
|
|
||||||
else:
|
|
||||||
if self.enum and value not in self.enum:
|
|
||||||
raise InvalidSchemaValue(
|
|
||||||
"Value {value} not in enum choices: {type}", value, self.enum)
|
|
||||||
formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format]
|
|
||||||
|
|
||||||
try:
|
unmarshallers = dict(
|
||||||
return formatstring.unmarshal(value)
|
(t, klass(**options))
|
||||||
except ValueError as exc:
|
for t, klass in unmarshallers_classes.items()
|
||||||
raise InvalidCustomFormatSchemaValue(
|
)
|
||||||
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
|
|
||||||
|
|
||||||
def _unmarshal_integer(self, value, custom_formatters=None, strict=True):
|
return unmarshallers
|
||||||
if strict and not isinstance(value, integer_types):
|
|
||||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
|
||||||
|
|
||||||
return int(value)
|
|
||||||
|
|
||||||
def _unmarshal_number(self, value, custom_formatters=None, strict=True):
|
|
||||||
if strict and not isinstance(value, (float, ) + integer_types):
|
|
||||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
|
||||||
|
|
||||||
try:
|
|
||||||
schema_format = SchemaFormat(self.format)
|
|
||||||
except ValueError:
|
|
||||||
msg = "Unsupported format {type} unmarshalling for value {value}"
|
|
||||||
if custom_formatters is not None:
|
|
||||||
formatnumber = custom_formatters.get(self.format)
|
|
||||||
if formatnumber is None:
|
|
||||||
raise InvalidSchemaValue(msg, value, self.format)
|
|
||||||
else:
|
|
||||||
raise InvalidSchemaValue(msg, value, self.format)
|
|
||||||
else:
|
|
||||||
formatnumber = self.NUMBER_FORMAT_CALLABLE_GETTER[schema_format]
|
|
||||||
|
|
||||||
try:
|
|
||||||
return formatnumber.unmarshal(value)
|
|
||||||
except ValueError as exc:
|
|
||||||
raise InvalidCustomFormatSchemaValue(
|
|
||||||
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
|
|
||||||
|
|
||||||
def _unmarshal_boolean(self, value, custom_formatters=None, strict=True):
|
|
||||||
if strict and not isinstance(value, (bool, )):
|
|
||||||
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
|
|
||||||
|
|
||||||
return forcebool(value)
|
|
||||||
|
|
||||||
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
|
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
|
||||||
types_resolve_order = [
|
types_resolve_order = [
|
||||||
|
@ -301,6 +269,8 @@ class Schema(object):
|
||||||
cast_callable = cast_mapping[schema_type]
|
cast_callable = cast_mapping[schema_type]
|
||||||
try:
|
try:
|
||||||
return cast_callable(value)
|
return cast_callable(value)
|
||||||
|
except UnmarshallerStrictTypeError:
|
||||||
|
continue
|
||||||
# @todo: remove ValueError when validation separated
|
# @todo: remove ValueError when validation separated
|
||||||
except (OpenAPISchemaError, TypeError, ValueError):
|
except (OpenAPISchemaError, TypeError, ValueError):
|
||||||
continue
|
continue
|
||||||
|
|
107
openapi_core/schema/schemas/unmarshallers.py
Normal file
107
openapi_core/schema/schemas/unmarshallers.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
from six import text_type, binary_type, integer_types
|
||||||
|
|
||||||
|
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
|
||||||
|
from openapi_core.schema.schemas.exceptions import (
|
||||||
|
InvalidSchemaValue, InvalidCustomFormatSchemaValue,
|
||||||
|
OpenAPISchemaError, MultipleOneOfSchema, NoOneOfSchema,
|
||||||
|
InvalidSchemaProperty,
|
||||||
|
UnmarshallerStrictTypeError,
|
||||||
|
)
|
||||||
|
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 InvalidSchemaValue(
|
||||||
|
"Unsupported format {type} unmarshalling "
|
||||||
|
"for value {value}",
|
||||||
|
value, type_format)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return formatter(value)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise InvalidCustomFormatSchemaValue(
|
||||||
|
"Failed to format value {value} to format {type}: {exception}",
|
||||||
|
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,
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue