Skip to content

Commit cffda89

Browse files
committed
Mimetype parameters handling
1 parent 7a17349 commit cffda89

File tree

7 files changed

+60
-17
lines changed

7 files changed

+60
-17
lines changed

Diff for: openapi_core/deserializing/media_types/deserializers.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ def __init__(
1616
self,
1717
mimetype: str,
1818
deserializer_callable: Optional[DeserializerCallable] = None,
19+
**parameters: str,
1920
):
2021
self.mimetype = mimetype
2122
self.deserializer_callable = deserializer_callable
23+
self.parameters = parameters
2224

2325
def deserialize(self, value: Any) -> Any:
2426
if self.deserializer_callable is None:
2527
warnings.warn(f"Unsupported {self.mimetype} mimetype")
2628
return value
2729

2830
try:
29-
return self.deserializer_callable(value)
31+
return self.deserializer_callable(value, **self.parameters)
3032
except (ParseError, ValueError, TypeError, AttributeError):
3133
raise MediaTypeDeserializeError(self.mimetype, value)

Diff for: openapi_core/deserializing/media_types/factories.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Mapping
12
from typing import Optional
23

34
from openapi_core.deserializing.media_types.datatypes import (
@@ -23,6 +24,7 @@ def __init__(
2324
def create(
2425
self,
2526
mimetype: str,
27+
parameters: Mapping[str, str],
2628
extra_media_type_deserializers: Optional[
2729
MediaTypeDeserializersDict
2830
] = None,
@@ -34,7 +36,9 @@ def create(
3436
extra_media_type_deserializers=extra_media_type_deserializers,
3537
)
3638

37-
return CallableMediaTypeDeserializer(mimetype, deserialize_callable)
39+
return CallableMediaTypeDeserializer(
40+
mimetype, deserialize_callable, **parameters
41+
)
3842

3943
def get_deserializer_callable(
4044
self,

Diff for: openapi_core/deserializing/media_types/util.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@
55
from urllib.parse import parse_qsl
66

77

8-
def plain_loads(value: Union[str, bytes]) -> str:
8+
def plain_loads(value: Union[str, bytes], **parameters: str) -> str:
9+
charset = "utf-8"
10+
if "charset" in parameters:
11+
charset = parameters["charset"]
912
if isinstance(value, bytes):
10-
value = value.decode("ASCII", errors="surrogateescape")
13+
try:
14+
return value.decode(charset)
15+
# fallback safe decode
16+
except UnicodeDecodeError:
17+
return value.decode("ASCII", errors="surrogateescape")
1118
return value
1219

1320

14-
def urlencoded_form_loads(value: Any) -> Dict[str, Any]:
21+
def urlencoded_form_loads(value: Any, **parameters: str) -> Dict[str, Any]:
1522
return dict(parse_qsl(value))
1623

1724

18-
def data_form_loads(value: Union[str, bytes]) -> Dict[str, Any]:
25+
def data_form_loads(
26+
value: Union[str, bytes], **parameters: str
27+
) -> Dict[str, Any]:
1928
if isinstance(value, bytes):
2029
value = value.decode("ASCII", errors="surrogateescape")
2130
parser = Parser()

Diff for: openapi_core/templating/media_types/datatypes.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
from collections import namedtuple
2+
from dataclasses import dataclass
3+
from typing import Mapping
4+
from typing import Optional
25

3-
MediaType = namedtuple("MediaType", ["value", "key"])
6+
MediaType = namedtuple("MediaType", ["mime_type", "parameters", "media_type"])

Diff for: openapi_core/templating/media_types/finders.py

+24-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""OpenAPI core templating media types finders module"""
22
import fnmatch
3+
from typing import Mapping
4+
from typing import Tuple
35

46
from openapi_core.spec import Spec
57
from openapi_core.templating.media_types.datatypes import MediaType
@@ -12,15 +14,31 @@ def __init__(self, content: Spec):
1214

1315
def get_first(self) -> MediaType:
1416
mimetype, media_type = next(self.content.items())
15-
return MediaType(media_type, mimetype)
17+
return MediaType(mimetype, {}, media_type)
1618

1719
def find(self, mimetype: str) -> MediaType:
18-
if mimetype in self.content:
19-
return MediaType(self.content / mimetype, mimetype)
20+
mime_type, parameters = self._parse_mimetype(mimetype)
2021

21-
if mimetype:
22+
# simple mime type
23+
for m in [mimetype, mime_type]:
24+
if m in self.content:
25+
return MediaType(mime_type, parameters, self.content / m)
26+
27+
# range mime type
28+
if mime_type:
2229
for key, value in self.content.items():
23-
if fnmatch.fnmatch(mimetype, key):
24-
return MediaType(value, key)
30+
if fnmatch.fnmatch(mime_type, key):
31+
return MediaType(key, parameters, value)
2532

2633
raise MediaTypeNotFound(mimetype, list(self.content.keys()))
34+
35+
def _parse_mimetype(self, mimetype: str) -> Tuple[str, Mapping[str, str]]:
36+
mimetype_parts = mimetype.split("; ")
37+
mime_type = mimetype_parts[0]
38+
parameters = {}
39+
if len(mimetype_parts) > 1:
40+
parameters_list = (
41+
param_str.split("=") for param_str in mimetype_parts[1:]
42+
)
43+
parameters = dict(parameters_list)
44+
return mime_type, parameters

Diff for: openapi_core/validation/validators.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,13 @@ def _find_media_type(
8686
return finder.get_first()
8787
return finder.find(mimetype)
8888

89-
def _deserialise_media_type(self, mimetype: str, value: Any) -> Any:
89+
def _deserialise_media_type(
90+
self, mimetype: str, parameters: Mapping[str, str], value: Any
91+
) -> Any:
9092
deserializer = self.media_type_deserializers_factory.create(
9193
mimetype,
9294
extra_media_type_deserializers=self.extra_media_type_deserializers,
95+
parameters=parameters,
9396
)
9497
return deserializer.deserialize(value)
9598

@@ -194,8 +197,10 @@ def _convert_content_schema_value_and_schema(
194197
content: Spec,
195198
mimetype: Optional[str] = None,
196199
) -> Tuple[Any, Optional[Spec]]:
197-
media_type, mime_type = self._find_media_type(content, mimetype)
198-
deserialised = self._deserialise_media_type(mime_type, raw)
200+
mime_type, parameters, media_type = self._find_media_type(
201+
content, mimetype
202+
)
203+
deserialised = self._deserialise_media_type(mime_type, parameters, raw)
199204
casted = self._cast(media_type, deserialised)
200205

201206
if "schema" not in media_type:

Diff for: tests/integration/test_petstore.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ def test_get_pets_response_no_schema(self, spec):
231231
assert result.body is None
232232

233233
data = "<html></html>"
234-
response = MockResponse(data, status_code=404, mimetype="text/html")
234+
response = MockResponse(
235+
data, status_code=404, mimetype="text/html; charset=utf-8"
236+
)
235237

236238
response_result = unmarshal_response(request, response, spec=spec)
237239

0 commit comments

Comments
 (0)