Skip to content

More media types supported #622

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
Jul 19, 2023
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
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