Skip to content

Move deserialize/cast out of schema models #191

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 7 commits into from
Feb 3, 2020
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
9 changes: 7 additions & 2 deletions openapi_core/casting/schemas/casters.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.schema.schemas.types import NoValue


class PrimitiveCaster(object):

def __init__(self, caster_callable):
def __init__(self, schema, caster_callable):
self.schema = schema
self.caster_callable = caster_callable

def __call__(self, value):
if value in (None, NoValue):
return value
return self.caster_callable(value)
try:
return self.caster_callable(value)
except (ValueError, TypeError):
raise CastError(value, self.schema.type.value)


class DummyCaster(object):
Expand Down
22 changes: 12 additions & 10 deletions openapi_core/casting/schemas/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@

class SchemaCastersFactory(object):

DUMMY_CASTER = DummyCaster()
DUMMY_CASTERS = [
SchemaType.STRING, SchemaType.OBJECT, SchemaType.ANY,
]
PRIMITIVE_CASTERS = {
SchemaType.STRING: DUMMY_CASTER,
SchemaType.INTEGER: PrimitiveCaster(int),
SchemaType.NUMBER: PrimitiveCaster(float),
SchemaType.BOOLEAN: PrimitiveCaster(forcebool),
SchemaType.OBJECT: DUMMY_CASTER,
SchemaType.ANY: DUMMY_CASTER,
SchemaType.INTEGER: int,
SchemaType.NUMBER: float,
SchemaType.BOOLEAN: forcebool,
}
COMPLEX_CASTERS = {
SchemaType.ARRAY: ArrayCaster,
}

def create(self, schema):
if schema.type in self.PRIMITIVE_CASTERS:
return self.PRIMITIVE_CASTERS[schema.type]
if schema.type in self.DUMMY_CASTERS:
return DummyCaster()
elif schema.type in self.PRIMITIVE_CASTERS:
caster_callable = self.PRIMITIVE_CASTERS[schema.type]
return PrimitiveCaster(schema, caster_callable)
elif schema.type in self.COMPLEX_CASTERS:
caster_class = self.COMPLEX_CASTERS[schema.type]
return caster_class(schema=schema, casters_factory=self)
return caster_class(schema, self)
Empty file.
14 changes: 14 additions & 0 deletions openapi_core/deserializing/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import attr

from openapi_core.exceptions import OpenAPIError


@attr.s(hash=True)
class DeserializeError(OpenAPIError):
"""Deserialize operation error"""
value = attr.ib()
style = attr.ib()

def __str__(self):
return "Failed to deserialize value {value} with style {style}".format(
value=self.value, style=self.style)
Empty file.
14 changes: 14 additions & 0 deletions openapi_core/deserializing/media_types/deserializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from openapi_core.deserializing.exceptions import DeserializeError


class PrimitiveDeserializer(object):

def __init__(self, mimetype, deserializer_callable):
self.mimetype = mimetype
self.deserializer_callable = deserializer_callable

def __call__(self, value):
try:
return self.deserializer_callable(value)
except (ValueError, TypeError, AttributeError):
raise DeserializeError(value, self.mimetype)
18 changes: 18 additions & 0 deletions openapi_core/deserializing/media_types/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from openapi_core.schema.media_types.util import json_loads

from openapi_core.deserializing.media_types.deserializers import (
PrimitiveDeserializer,
)


class MediaTypeDeserializersFactory(object):

MEDIA_TYPE_DESERIALIZERS = {
'application/json': json_loads,
}

def create(self, media_type):
deserialize_callable = self.MEDIA_TYPE_DESERIALIZERS.get(
media_type.mimetype, lambda x: x)
return PrimitiveDeserializer(
media_type.mimetype, deserialize_callable)
Empty file.
25 changes: 25 additions & 0 deletions openapi_core/deserializing/parameters/deserializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.deserializing.parameters.exceptions import (
EmptyParameterValue,
)
from openapi_core.schema.parameters.enums import ParameterLocation


class PrimitiveDeserializer(object):

def __init__(self, param, deserializer_callable):
self.param = param
self.deserializer_callable = deserializer_callable

def __call__(self, value):
if (self.param.location == ParameterLocation.QUERY and value == "" and
not self.param.allow_empty_value):
raise EmptyParameterValue(
value, self.param.style, self.param.name)

if not self.param.aslist or self.param.explode:
return value
try:
return self.deserializer_callable(value)
except (ValueError, TypeError, AttributeError):
raise DeserializeError(value, self.param.style)
11 changes: 11 additions & 0 deletions openapi_core/deserializing/parameters/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import attr

from openapi_core.deserializing.exceptions import DeserializeError


@attr.s(hash=True)
class EmptyParameterValue(DeserializeError):
name = attr.ib()

def __str__(self):
return "Value of parameter cannot be empty: {0}".format(self.name)
26 changes: 26 additions & 0 deletions openapi_core/deserializing/parameters/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import warnings

from openapi_core.deserializing.parameters.deserializers import (
PrimitiveDeserializer,
)
from openapi_core.schema.parameters.enums import ParameterStyle


class ParameterDeserializersFactory(object):

PARAMETER_STYLE_DESERIALIZERS = {
ParameterStyle.FORM: lambda x: x.split(','),
ParameterStyle.SIMPLE: lambda x: x.split(','),
ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '),
ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'),
}

def create(self, param):
if param.deprecated:
warnings.warn(
"{0} parameter is deprecated".format(param.name),
DeprecationWarning,
)

deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[param.style]
return PrimitiveDeserializer(param, deserialize_callable)
8 changes: 0 additions & 8 deletions openapi_core/schema/media_types/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ class OpenAPIMediaTypeError(OpenAPIMappingError):
pass


@attr.s(hash=True)
class InvalidMediaTypeValue(OpenAPIMediaTypeError):
original_exception = attr.ib()

def __str__(self):
return "Mimetype invalid: {0}".format(self.original_exception)


@attr.s(hash=True)
class InvalidContentType(OpenAPIMediaTypeError):
mimetype = attr.ib()
Expand Down
36 changes: 0 additions & 36 deletions openapi_core/schema/media_types/models.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
"""OpenAPI core media types models module"""
from collections import defaultdict

from openapi_core.schema.media_types.exceptions import InvalidMediaTypeValue
from openapi_core.schema.media_types.util import json_loads
from openapi_core.casting.schemas.exceptions import CastError


MEDIA_TYPE_DESERIALIZERS = {
'application/json': json_loads,
}


class MediaType(object):
Expand All @@ -18,29 +8,3 @@ def __init__(self, mimetype, schema=None, example=None):
self.mimetype = mimetype
self.schema = schema
self.example = example

def get_deserializer_mapping(self):
mapping = MEDIA_TYPE_DESERIALIZERS.copy()
return defaultdict(lambda: lambda x: x, mapping)

def get_dererializer(self):
mapping = self.get_deserializer_mapping()
return mapping[self.mimetype]

def deserialize(self, value):
deserializer = self.get_dererializer()
return deserializer(value)

def cast(self, value):
try:
deserialized = self.deserialize(value)
except ValueError as exc:
raise InvalidMediaTypeValue(exc)

if not self.schema:
return deserialized

try:
return self.schema.cast(deserialized)
except CastError as exc:
raise InvalidMediaTypeValue(exc)
18 changes: 0 additions & 18 deletions openapi_core/schema/parameters/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,3 @@ class MissingRequiredParameter(MissingParameterError):

def __str__(self):
return "Missing required parameter: {0}".format(self.name)


@attr.s(hash=True)
class EmptyParameterValue(OpenAPIParameterError):
name = attr.ib()

def __str__(self):
return "Value of parameter cannot be empty: {0}".format(self.name)


@attr.s(hash=True)
class InvalidParameterValue(OpenAPIParameterError):
name = attr.ib()
original_exception = attr.ib()

def __str__(self):
return "Invalid parameter value for `{0}`: {1}".format(
self.name, self.original_exception)
63 changes: 0 additions & 63 deletions openapi_core/schema/parameters/models.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
"""OpenAPI core parameters models module"""
import logging
import warnings

from openapi_core.schema.parameters.enums import (
ParameterLocation, ParameterStyle,
)
from openapi_core.schema.parameters.exceptions import (
MissingRequiredParameter, MissingParameter, InvalidParameterValue,
EmptyParameterValue,
)
from openapi_core.schema.schemas.enums import SchemaType
from openapi_core.casting.schemas.exceptions import CastError

log = logging.getLogger(__name__)


class Parameter(object):
"""Represents an OpenAPI operation Parameter."""

PARAMETER_STYLE_DESERIALIZERS = {
ParameterStyle.FORM: lambda x: x.split(','),
ParameterStyle.SIMPLE: lambda x: x.split(','),
ParameterStyle.SPACE_DELIMITED: lambda x: x.split(' '),
ParameterStyle.PIPE_DELIMITED: lambda x: x.split('|'),
}

def __init__(
self, name, location, schema=None, required=False,
deprecated=False, allow_empty_value=False,
Expand Down Expand Up @@ -61,53 +48,3 @@ def default_style(self):
@property
def default_explode(self):
return self.style == ParameterStyle.FORM

def get_dererializer(self):
return self.PARAMETER_STYLE_DESERIALIZERS[self.style]

def deserialize(self, value):
if not self.aslist or self.explode:
return value

deserializer = self.get_dererializer()
return deserializer(value)

def get_raw_value(self, request):
location = request.parameters[self.location.value]

if self.name not in location:
if self.required:
raise MissingRequiredParameter(self.name)

raise MissingParameter(self.name)

if self.aslist and self.explode:
if hasattr(location, 'getall'):
return location.getall(self.name)
return location.getlist(self.name)

return location[self.name]

def cast(self, value):
if self.deprecated:
warnings.warn(
"{0} parameter is deprecated".format(self.name),
DeprecationWarning,
)

if (self.location == ParameterLocation.QUERY and value == "" and
not self.allow_empty_value):
raise EmptyParameterValue(self.name)

try:
deserialized = self.deserialize(value)
except (ValueError, AttributeError) as exc:
raise InvalidParameterValue(self.name, exc)

if not self.schema:
return deserialized

try:
return self.schema.cast(deserialized)
except CastError as exc:
raise InvalidParameterValue(self.name, exc)
6 changes: 0 additions & 6 deletions openapi_core/schema/request_bodies/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from openapi_core.schema.content.exceptions import MimeTypeNotFound
from openapi_core.schema.content.models import Content
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody


class RequestBody(object):
Expand All @@ -17,8 +16,3 @@ def __getitem__(self, mimetype):
return self.content[mimetype]
except MimeTypeNotFound:
raise InvalidContentType(mimetype)

def get_value(self, request):
if not request.body and self.required:
raise MissingRequestBody(request)
return request.body
7 changes: 0 additions & 7 deletions openapi_core/schema/responses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from openapi_core.schema.content.exceptions import MimeTypeNotFound
from openapi_core.schema.content.models import Content
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.responses.exceptions import MissingResponseContent


class Response(object):
Expand All @@ -24,9 +23,3 @@ def get_content_type(self, mimetype):
return self.content[mimetype]
except MimeTypeNotFound:
raise InvalidContentType(mimetype)

def get_value(self, response):
if not response.data:
raise MissingResponseContent(response)

return response.data
11 changes: 0 additions & 11 deletions openapi_core/schema/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,3 @@ def get_all_properties(self):
def get_all_properties_names(self):
all_properties = self.get_all_properties()
return set(all_properties.keys())

def cast(self, value):
"""Cast value from string to schema type"""
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.casting.schemas.factories import SchemaCastersFactory
casters_factory = SchemaCastersFactory()
caster = casters_factory.create(self)
try:
return caster(value)
except (ValueError, TypeError):
raise CastError(value, self.type)
Loading