Skip to content

Commit 49b50b6

Browse files
committed
More media types supported
1 parent efdc5be commit 49b50b6

File tree

7 files changed

+84
-12
lines changed

7 files changed

+84
-12
lines changed

docs/customizations.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ If you know you have a valid specification already, disabling the validator can
2121
Media type deserializers
2222
------------------------
2323

24-
Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function:
24+
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``.
25+
26+
You can also define your own ones. Pass custom defined media type deserializers dictionary with supported mimetypes as a key to `unmarshal_response` function:
2527

2628
.. code-block:: python
2729
:emphasize-lines: 13

openapi_core/deserializing/media_types/__init__.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from json import loads
1+
from json import loads as json_loads
2+
from xml.etree.ElementTree import fromstring as xml_loads
23

34
from openapi_core.deserializing.media_types.datatypes import (
45
MediaTypeDeserializersDict,
@@ -7,12 +8,18 @@
78
MediaTypeDeserializersFactory,
89
)
910
from openapi_core.deserializing.media_types.util import data_form_loads
11+
from openapi_core.deserializing.media_types.util import plain_loads
1012
from openapi_core.deserializing.media_types.util import urlencoded_form_loads
1113

1214
__all__ = ["media_type_deserializers_factory"]
1315

1416
media_type_deserializers: MediaTypeDeserializersDict = {
15-
"application/json": loads,
17+
"text/html": plain_loads,
18+
"text/plain": plain_loads,
19+
"application/json": json_loads,
20+
"application/vnd.api+json": json_loads,
21+
"application/xml": xml_loads,
22+
"application/xhtml+xml": xml_loads,
1623
"application/x-www-form-urlencoded": urlencoded_form_loads,
1724
"multipart/form-data": data_form_loads,
1825
}

openapi_core/deserializing/media_types/deserializers.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import warnings
22
from typing import Any
33
from typing import Optional
4+
from xml.etree.ElementTree import ParseError
45

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

2728
try:
2829
return self.deserializer_callable(value)
29-
except (ValueError, TypeError, AttributeError):
30+
except (ParseError, ValueError, TypeError, AttributeError):
3031
raise MediaTypeDeserializeError(self.mimetype, value)

openapi_core/deserializing/media_types/util.py

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
from urllib.parse import parse_qsl
66

77

8+
def plain_loads(value: Union[str, bytes]) -> str:
9+
if isinstance(value, bytes):
10+
value = value.decode("ASCII", errors="surrogateescape")
11+
return value
12+
13+
814
def urlencoded_form_loads(value: Any) -> Dict[str, Any]:
915
return dict(parse_qsl(value))
1016

tests/integration/test_petstore.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,7 @@ def test_get_pets_response_no_schema(self, spec):
233233
data = "<html></html>"
234234
response = MockResponse(data, status_code=404, mimetype="text/html")
235235

236-
with pytest.warns(UserWarning):
237-
response_result = unmarshal_response(request, response, spec=spec)
236+
response_result = unmarshal_response(request, response, spec=spec)
238237

239238
assert response_result.errors == []
240239
assert response_result.data == data

tests/integration/unmarshalling/test_request_unmarshaller.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,7 @@ def test_post_pets_plain_no_schema(self, request_unmarshaller):
352352
mimetype="text/plain",
353353
)
354354

355-
with pytest.warns(UserWarning):
356-
result = request_unmarshaller.unmarshal(request)
355+
result = request_unmarshaller.unmarshal(request)
357356

358357
assert result.errors == []
359358
assert result.parameters == Parameters(

tests/unit/deserializing/test_media_types_deserializers.py

+62-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from xml.etree.ElementTree import Element
2+
13
import pytest
24

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

4749
assert result == value
4850

49-
def test_json_empty(self, deserializer_factory):
50-
mimetype = "application/json"
51+
@pytest.mark.parametrize(
52+
"mimetype",
53+
[
54+
"text/plain",
55+
"text/html",
56+
],
57+
)
58+
def test_plain_valid(self, deserializer_factory, mimetype):
59+
deserializer = deserializer_factory(mimetype)
60+
value = "somestr"
61+
62+
result = deserializer.deserialize(value)
63+
64+
assert result == value
65+
66+
@pytest.mark.parametrize(
67+
"mimetype",
68+
[
69+
"application/json",
70+
"application/vnd.api+json",
71+
],
72+
)
73+
def test_json_empty(self, deserializer_factory, mimetype):
5174
deserializer = deserializer_factory(mimetype)
5275
value = ""
5376

5477
with pytest.raises(DeserializeError):
5578
deserializer.deserialize(value)
5679

57-
def test_json_empty_object(self, deserializer_factory):
58-
mimetype = "application/json"
80+
@pytest.mark.parametrize(
81+
"mimetype",
82+
[
83+
"application/json",
84+
"application/vnd.api+json",
85+
],
86+
)
87+
def test_json_empty_object(self, deserializer_factory, mimetype):
5988
deserializer = deserializer_factory(mimetype)
6089
value = "{}"
6190

6291
result = deserializer.deserialize(value)
6392

6493
assert result == {}
6594

95+
@pytest.mark.parametrize(
96+
"mimetype",
97+
[
98+
"application/xml",
99+
"application/xhtml+xml",
100+
],
101+
)
102+
def test_xml_empty(self, deserializer_factory, mimetype):
103+
deserializer = deserializer_factory(mimetype)
104+
value = ""
105+
106+
with pytest.raises(DeserializeError):
107+
deserializer.deserialize(value)
108+
109+
@pytest.mark.parametrize(
110+
"mimetype",
111+
[
112+
"application/xml",
113+
"application/xhtml+xml",
114+
],
115+
)
116+
def test_xml_valid(self, deserializer_factory, mimetype):
117+
deserializer = deserializer_factory(mimetype)
118+
value = "<obj>text</obj>"
119+
120+
result = deserializer.deserialize(value)
121+
122+
assert type(result) is Element
123+
66124
def test_urlencoded_form_empty(self, deserializer_factory):
67125
mimetype = "application/x-www-form-urlencoded"
68126
deserializer = deserializer_factory(mimetype)

0 commit comments

Comments
 (0)