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
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
@@ -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
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,
@@ -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,
}
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,
@@ -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
@@ -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))

3 changes: 1 addition & 2 deletions tests/integration/test_petstore.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions tests/integration/unmarshalling/test_request_unmarshaller.py
Original file line number Diff line number Diff line change
@@ -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(
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
@@ -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)