Skip to content

Primitive types unmarshallers #138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion openapi_core/schema/media_types/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def unmarshal(self, value, custom_formatters=None):
raise InvalidMediaTypeValue(exc)

try:
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
return self.schema.validate(
unmarshalled, custom_formatters=custom_formatters)
except OpenAPISchemaError as exc:
raise InvalidMediaTypeValue(exc)
3 changes: 2 additions & 1 deletion openapi_core/schema/parameters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def unmarshal(self, value, custom_formatters=None):
raise InvalidParameterValue(self.name, exc)

try:
return self.schema.validate(unmarshalled, custom_formatters=custom_formatters)
return self.schema.validate(
unmarshalled, custom_formatters=custom_formatters)
except OpenAPISchemaError as exc:
raise InvalidParameterValue(self.name, exc)
14 changes: 14 additions & 0 deletions openapi_core/schema/schemas/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,17 @@ class MultipleOneOfSchema(OpenAPISchemaError):

def __str__(self):
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)
100 changes: 35 additions & 65 deletions openapi_core/schema/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty,
UnmarshallerStrictTypeError,
)
from openapi_core.schema.schemas.util import (
forcebool, format_date, format_datetime, format_byte, format_uuid,
Expand Down Expand Up @@ -155,14 +156,19 @@ def get_all_required_properties_names(self):
return set(required)

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(
f, custom_formatters=custom_formatters, strict=strict)
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
mapping.update(primitive_unmarshallers_partial)
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.ARRAY: pass_defaults(self._unmarshal_collection),
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
Expand All @@ -184,6 +190,10 @@ def cast(self, value, custom_formatters=None, strict=True):
raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type)
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(
custom_formatters=custom_formatters, strict=strict)

Expand All @@ -193,6 +203,9 @@ def cast(self, value, custom_formatters=None, strict=True):
cast_callable = cast_mapping[self.type]
try:
return cast_callable(value)
except UnmarshallerStrictTypeError:
raise InvalidSchemaValue(
"Value {value} is not of type {type}", value, self.type)
except ValueError:
raise InvalidSchemaValue(
"Failed to cast value {value} to type {type}", value, self.type)
Expand All @@ -207,72 +220,27 @@ def unmarshal(self, value, custom_formatters=None, strict=True):
if casted is None and not self.required:
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

def _unmarshal_string(self, value, custom_formatters=None, strict=True):
if strict and not isinstance(value, (text_type, binary_type)):
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:
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:
return formatstring.unmarshal(value)
except ValueError as exc:
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):
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]
def get_primitive_unmarshallers(self, **options):
from openapi_core.schema.schemas.unmarshallers import (
StringUnmarshaller, BooleanUnmarshaller, IntegerUnmarshaller,
NumberUnmarshaller,
)

try:
return formatnumber.unmarshal(value)
except ValueError as exc:
raise InvalidCustomFormatSchemaValue(
"Failed to format value {value} to format {type}: {exception}", value, self.format, exc)
unmarshallers_classes = {
SchemaType.STRING: StringUnmarshaller,
SchemaType.BOOLEAN: BooleanUnmarshaller,
SchemaType.INTEGER: IntegerUnmarshaller,
SchemaType.NUMBER: NumberUnmarshaller,
}

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)
unmarshallers = dict(
(t, klass(**options))
for t, klass in unmarshallers_classes.items()
)

return forcebool(value)
return unmarshallers

def _unmarshal_any(self, value, custom_formatters=None, strict=True):
types_resolve_order = [
Expand Down Expand Up @@ -301,6 +269,8 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
cast_callable = cast_mapping[schema_type]
try:
return cast_callable(value)
except UnmarshallerStrictTypeError:
continue
# @todo: remove ValueError when validation separated
except (OpenAPISchemaError, TypeError, ValueError):
continue
Expand Down
107 changes: 107 additions & 0 deletions openapi_core/schema/schemas/unmarshallers.py
Original file line number Diff line number Diff line change
@@ -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,
}