Skip to content

Commit

Permalink
Merge pull request #622 from python-openapi/feature/more-media-types-…
Browse files Browse the repository at this point in the history
…supported

More media types supported
  • Loading branch information
p1c2u authored Jul 19, 2023
2 parents efdc5be + 49b50b6 commit 84b3b2e
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 12 deletions.
4 changes: 3 additions & 1 deletion docs/customizations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ If you know you have a valid specification already, disabling the validator can
Media type deserializers
------------------------

Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function:
OpenAPI comes with a set of built-in media type deserializers such as: ``application/json``, ``application/xml``, ``application/x-www-form-urlencoded`` or ``multipart/form-data``.

You can also define your own ones. Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function:

.. code-block:: python
:emphasize-lines: 13
Expand Down
11 changes: 9 additions & 2 deletions openapi_core/deserializing/media_types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from json import loads
from json import loads as json_loads
from xml.etree.ElementTree import fromstring as xml_loads

from openapi_core.deserializing.media_types.datatypes import (
MediaTypeDeserializersDict,
Expand All @@ -7,12 +8,18 @@
MediaTypeDeserializersFactory,
)
from openapi_core.deserializing.media_types.util import data_form_loads
from openapi_core.deserializing.media_types.util import plain_loads
from openapi_core.deserializing.media_types.util import urlencoded_form_loads

__all__ = ["media_type_deserializers_factory"]

media_type_deserializers: MediaTypeDeserializersDict = {
"application/json": loads,
"text/html": plain_loads,
"text/plain": plain_loads,
"application/json": json_loads,
"application/vnd.api+json": json_loads,
"application/xml": xml_loads,
"application/xhtml+xml": xml_loads,
"application/x-www-form-urlencoded": urlencoded_form_loads,
"multipart/form-data": data_form_loads,
}
Expand Down
3 changes: 2 additions & 1 deletion openapi_core/deserializing/media_types/deserializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import warnings
from typing import Any
from typing import Optional
from xml.etree.ElementTree import ParseError

from openapi_core.deserializing.media_types.datatypes import (
DeserializerCallable,
Expand All @@ -26,5 +27,5 @@ def deserialize(self, value: Any) -> Any:

try:
return self.deserializer_callable(value)
except (ValueError, TypeError, AttributeError):
except (ParseError, ValueError, TypeError, AttributeError):
raise MediaTypeDeserializeError(self.mimetype, value)
6 changes: 6 additions & 0 deletions openapi_core/deserializing/media_types/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
from urllib.parse import parse_qsl


def plain_loads(value: Union[str, bytes]) -> str:
if isinstance(value, bytes):
value = value.decode("ASCII", errors="surrogateescape")
return value


def urlencoded_form_loads(value: Any) -> Dict[str, Any]:
return dict(parse_qsl(value))

Expand Down
3 changes: 1 addition & 2 deletions tests/integration/test_petstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,7 @@ def test_get_pets_response_no_schema(self, spec):
data = "<html></html>"
response = MockResponse(data, status_code=404, mimetype="text/html")

with pytest.warns(UserWarning):
response_result = unmarshal_response(request, response, spec=spec)
response_result = unmarshal_response(request, response, spec=spec)

assert response_result.errors == []
assert response_result.data == data
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/unmarshalling/test_request_unmarshaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,7 @@ def test_post_pets_plain_no_schema(self, request_unmarshaller):
mimetype="text/plain",
)

with pytest.warns(UserWarning):
result = request_unmarshaller.unmarshal(request)
result = request_unmarshaller.unmarshal(request)

assert result.errors == []
assert result.parameters == Parameters(
Expand Down
66 changes: 62 additions & 4 deletions tests/unit/deserializing/test_media_types_deserializers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from xml.etree.ElementTree import Element

import pytest

from openapi_core.deserializing.exceptions import DeserializeError
Expand Down Expand Up @@ -46,23 +48,79 @@ def test_no_deserializer(self, deserializer_factory):

assert result == value

def test_json_empty(self, deserializer_factory):
mimetype = "application/json"
@pytest.mark.parametrize(
"mimetype",
[
"text/plain",
"text/html",
],
)
def test_plain_valid(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = "somestr"

result = deserializer.deserialize(value)

assert result == value

@pytest.mark.parametrize(
"mimetype",
[
"application/json",
"application/vnd.api+json",
],
)
def test_json_empty(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = ""

with pytest.raises(DeserializeError):
deserializer.deserialize(value)

def test_json_empty_object(self, deserializer_factory):
mimetype = "application/json"
@pytest.mark.parametrize(
"mimetype",
[
"application/json",
"application/vnd.api+json",
],
)
def test_json_empty_object(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = "{}"

result = deserializer.deserialize(value)

assert result == {}

@pytest.mark.parametrize(
"mimetype",
[
"application/xml",
"application/xhtml+xml",
],
)
def test_xml_empty(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = ""

with pytest.raises(DeserializeError):
deserializer.deserialize(value)

@pytest.mark.parametrize(
"mimetype",
[
"application/xml",
"application/xhtml+xml",
],
)
def test_xml_valid(self, deserializer_factory, mimetype):
deserializer = deserializer_factory(mimetype)
value = "<obj>text</obj>"

result = deserializer.deserialize(value)

assert type(result) is Element

def test_urlencoded_form_empty(self, deserializer_factory):
mimetype = "application/x-www-form-urlencoded"
deserializer = deserializer_factory(mimetype)
Expand Down

0 comments on commit 84b3b2e

Please sign in to comment.