Skip to content
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: 0 additions & 9 deletions openapi_core/deserializing/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
from dataclasses import dataclass

from openapi_core.exceptions import OpenAPIError


@dataclass
class DeserializeError(OpenAPIError):
"""Deserialize operation error"""
value: str
style: str

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

from openapi_core.deserializing.media_types.exceptions import (
MediaTypeDeserializeError,
)

class PrimitiveDeserializer:

class BaseMediaTypeDeserializer:

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

def __call__(self, value):
raise NotImplementedError


class UnsupportedMimetypeDeserializer(BaseMediaTypeDeserializer):

def __call__(self, value):
warnings.warn(f"Unsupported {self.mimetype} mimetype")
return value


class CallableMediaTypeDeserializer(BaseMediaTypeDeserializer):

def __init__(self, mimetype, deserializer_callable):
self.mimetype = mimetype
Expand All @@ -11,4 +31,4 @@ def __call__(self, value):
try:
return self.deserializer_callable(value)
except (ValueError, TypeError, AttributeError):
raise DeserializeError(value, self.mimetype)
raise MediaTypeDeserializeError(self.mimetype, value)
15 changes: 15 additions & 0 deletions openapi_core/deserializing/media_types/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from dataclasses import dataclass

from openapi_core.deserializing.exceptions import DeserializeError


@dataclass
class MediaTypeDeserializeError(DeserializeError):
"""Media type deserialize operation error"""
mimetype: str
value: str

def __str__(self):
return (
"Failed to deserialize value with {mimetype} mimetype: {value}"
).format(value=self.value, mimetype=self.mimetype)
10 changes: 7 additions & 3 deletions openapi_core/deserializing/media_types/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
)

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


Expand All @@ -25,10 +25,14 @@ def __init__(self, custom_deserializers=None):
def create(self, mimetype):
deserialize_callable = self.get_deserializer_callable(
mimetype)
return PrimitiveDeserializer(

if deserialize_callable is None:
return UnsupportedMimetypeDeserializer(mimetype)

return CallableMediaTypeDeserializer(
mimetype, deserialize_callable)

def get_deserializer_callable(self, mimetype):
if mimetype in self.custom_deserializers:
return self.custom_deserializers[mimetype]
return self.MEDIA_TYPE_DESERIALIZERS.get(mimetype, lambda x: x)
return self.MEDIA_TYPE_DESERIALIZERS.get(mimetype)
32 changes: 24 additions & 8 deletions openapi_core/deserializing/parameters/deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@

from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.deserializing.parameters.exceptions import (
EmptyParameterValue,
EmptyQueryParameterValue,
)
from openapi_core.schema.parameters import get_aslist, get_explode, get_style
from openapi_core.schema.parameters import get_aslist, get_explode


class PrimitiveDeserializer:
class BaseParameterDeserializer:

def __init__(self, param_or_header, deserializer_callable):
def __init__(self, param_or_header, style):
self.param_or_header = param_or_header
self.style = style

def __call__(self, value):
raise NotImplementedError


class UnsupportedStyleDeserializer(BaseParameterDeserializer):

def __call__(self, value):
warnings.warn(f"Unsupported {self.style} style")
return value


class CallableParameterDeserializer(BaseParameterDeserializer):

def __init__(self, param_or_header, style, deserializer_callable):
super().__init__(param_or_header, style)
self.deserializer_callable = deserializer_callable

self.aslist = get_aslist(self.param_or_header)
self.explode = get_explode(self.param_or_header)
self.style = get_style(self.param_or_header)

def __call__(self, value):
# if "in" not defined then it's a Header
Expand All @@ -29,12 +45,12 @@ def __call__(self, value):
location_name = self.param_or_header.getkey('in', 'header')
if (location_name == 'query' and value == "" and
not allow_empty_values):
name = self.param_or_header.getkey('name', 'header')
raise EmptyParameterValue(value, self.style, name)
name = self.param_or_header['name']
raise EmptyQueryParameterValue(name)

if not self.aslist or self.explode:
return value
try:
return self.deserializer_callable(value)
except (ValueError, TypeError, AttributeError):
raise DeserializeError(value, self.style)
raise DeserializeError(location_name, self.style, value)
28 changes: 26 additions & 2 deletions openapi_core/deserializing/parameters/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,32 @@


@dataclass
class EmptyParameterValue(DeserializeError):
class BaseParameterDeserializeError(DeserializeError):
"""Base parameter deserialize operation error"""
location: str


@dataclass
class ParameterDeserializeError(BaseParameterDeserializeError):
"""Parameter deserialize operation error"""
style: str
value: str

def __str__(self):
return (
"Failed to deserialize value "
"of {location} parameter with style {style}: {value}"
).format(location=self.location, style=self.style, value=self.value)


@dataclass(init=False)
class EmptyQueryParameterValue(BaseParameterDeserializeError):
name: str

def __init__(self, name):
super().__init__(location='query')
self.name = name

def __str__(self):
return "Value of parameter cannot be empty: {0}".format(self.name)
return "Value of {name} {location} parameter cannot be empty".format(
name=self.name, location=self.location)
23 changes: 15 additions & 8 deletions openapi_core/deserializing/parameters/factories.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
from functools import partial

from openapi_core.deserializing.parameters.deserializers import (
PrimitiveDeserializer,
CallableParameterDeserializer, UnsupportedStyleDeserializer,
)
from openapi_core.deserializing.parameters.util import split
from openapi_core.schema.parameters import get_style


class ParameterDeserializersFactory:

PARAMETER_STYLE_DESERIALIZERS = {
'form': lambda x: x.split(','),
'simple': lambda x: x.split(','),
'spaceDelimited': lambda x: x.split(' '),
'pipeDelimited': lambda x: x.split('|'),
'form': partial(split, separator=','),
'simple': partial(split, separator=','),
'spaceDelimited': partial(split, separator=' '),
'pipeDelimited': partial(split, separator='|'),
}

def create(self, param):
style = get_style(param)
def create(self, param_or_header):
style = get_style(param_or_header)

if style not in self.PARAMETER_STYLE_DESERIALIZERS:
return UnsupportedStyleDeserializer(param_or_header, style)

deserialize_callable = self.PARAMETER_STYLE_DESERIALIZERS[style]
return PrimitiveDeserializer(param, deserialize_callable)
return CallableParameterDeserializer(
param_or_header, style, deserialize_callable)
2 changes: 2 additions & 0 deletions openapi_core/deserializing/parameters/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def split(value, separator=','):
return value.split(separator)
4 changes: 2 additions & 2 deletions tests/integration/validation/test_petstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.deserializing.parameters.exceptions import (
EmptyParameterValue,
EmptyQueryParameterValue,
)
from openapi_core.extensions.models.models import BaseModel
from openapi_core.exceptions import (
Expand Down Expand Up @@ -375,7 +375,7 @@ def test_get_pets_empty_value(self, spec):
path_pattern=path_pattern, args=query_params,
)

with pytest.raises(EmptyParameterValue):
with pytest.raises(EmptyQueryParameterValue):
spec_validate_parameters(spec, request)
body = spec_validate_body(spec, request)

Expand Down
6 changes: 4 additions & 2 deletions tests/integration/validation/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import pytest

from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.deserializing.media_types.exceptions import (
MediaTypeDeserializeError,
)
from openapi_core.extensions.models.models import BaseModel
from openapi_core.exceptions import (
MissingRequiredParameter, MissingRequiredRequestBody,
Expand Down Expand Up @@ -572,7 +574,7 @@ def test_invalid_media_type(self, validator):
result = validator.validate(request, response)

assert len(result.errors) == 1
assert type(result.errors[0]) == DeserializeError
assert type(result.errors[0]) == MediaTypeDeserializeError
assert result.data is None
assert result.headers == {}

Expand Down
9 changes: 9 additions & 0 deletions tests/unit/deserializing/test_media_types_deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ def create_deserializer(media_type, custom_deserializers=None):
custom_deserializers=custom_deserializers).create(media_type)
return create_deserializer

def test_unsupported(self, deserializer_factory):
mimetype = 'application/unsupported'
value = ''

with pytest.warns(UserWarning):
result = deserializer_factory(mimetype)(value)

assert result == value

def test_json_empty(self, deserializer_factory):
mimetype = 'application/json'
value = ''
Expand Down
18 changes: 16 additions & 2 deletions tests/unit/deserializing/test_parameters_deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
ParameterDeserializersFactory,
)
from openapi_core.deserializing.parameters.exceptions import (
EmptyParameterValue,
EmptyQueryParameterValue,
)
from openapi_core.spec.paths import SpecPath

Expand All @@ -17,6 +17,20 @@ def create_deserializer(param):
return ParameterDeserializersFactory().create(param)
return create_deserializer

def test_unsupported(self, deserializer_factory):
spec = {
'name': 'param',
'in': 'header',
'style': 'unsupported'
}
param = SpecPath.from_spec(spec)
value = ''

with pytest.warns(UserWarning):
result = deserializer_factory(param)(value)

assert result == value

def test_query_empty(self, deserializer_factory):
spec = {
'name': 'param',
Expand All @@ -25,7 +39,7 @@ def test_query_empty(self, deserializer_factory):
param = SpecPath.from_spec(spec)
value = ''

with pytest.raises(EmptyParameterValue):
with pytest.raises(EmptyQueryParameterValue):
deserializer_factory(param)(value)

def test_query_valid(self, deserializer_factory):
Expand Down