Skip to content

Commit 7a17349

Browse files
authored
Merge pull request #677 from python-openapi/refactor/parameter-and-header-get-value-refactor
Parameter and header get value refactor
2 parents 0da2a38 + 890ae99 commit 7a17349

File tree

8 files changed

+153
-93
lines changed

8 files changed

+153
-93
lines changed

Diff for: openapi_core/schema/parameters.py

-32
Original file line numberDiff line numberDiff line change
@@ -45,38 +45,6 @@ def get_explode(param_or_header: Spec) -> bool:
4545
return style == "form"
4646

4747

48-
def get_value(
49-
param_or_header: Spec,
50-
location: Mapping[str, Any],
51-
name: Optional[str] = None,
52-
) -> Any:
53-
"""Returns parameter/header value from specific location"""
54-
name = name or param_or_header["name"]
55-
style = get_style(param_or_header)
56-
57-
if name not in location:
58-
# Only check if the name is not in the location if the style of
59-
# the param is deepObject,this is because deepObjects will never be found
60-
# as their key also includes the properties of the object already.
61-
if style != "deepObject":
62-
raise KeyError
63-
keys_str = " ".join(location.keys())
64-
if not re.search(rf"{name}\[\w+\]", keys_str):
65-
raise KeyError
66-
67-
aslist = get_aslist(param_or_header)
68-
explode = get_explode(param_or_header)
69-
if aslist and explode:
70-
if style == "deepObject":
71-
return get_deep_object_value(location, name)
72-
if isinstance(location, SuportsGetAll):
73-
return location.getall(name)
74-
if isinstance(location, SuportsGetList):
75-
return location.getlist(name)
76-
77-
return location[name]
78-
79-
8048
def get_deep_object_value(
8149
location: Mapping[str, Any],
8250
name: Optional[str] = None,

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

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class MediaTypeFinder:
1010
def __init__(self, content: Spec):
1111
self.content = content
1212

13+
def get_first(self) -> MediaType:
14+
mimetype, media_type = next(self.content.items())
15+
return MediaType(media_type, mimetype)
16+
1317
def find(self, mimetype: str) -> MediaType:
1418
if mimetype in self.content:
1519
return MediaType(self.content / mimetype, mimetype)

Diff for: openapi_core/unmarshalling/response/unmarshallers.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _unmarshal(
5656
operation: Spec,
5757
) -> ResponseUnmarshalResult:
5858
try:
59-
operation_response = self._get_operation_response(
59+
operation_response = self._find_operation_response(
6060
response.status_code, operation
6161
)
6262
# don't process if operation errors
@@ -96,7 +96,7 @@ def _unmarshal_data(
9696
operation: Spec,
9797
) -> ResponseUnmarshalResult:
9898
try:
99-
operation_response = self._get_operation_response(
99+
operation_response = self._find_operation_response(
100100
response.status_code, operation
101101
)
102102
# don't process if operation errors
@@ -124,7 +124,7 @@ def _unmarshal_headers(
124124
operation: Spec,
125125
) -> ResponseUnmarshalResult:
126126
try:
127-
operation_response = self._get_operation_response(
127+
operation_response = self._find_operation_response(
128128
response.status_code, operation
129129
)
130130
# don't process if operation errors

Diff for: openapi_core/unmarshalling/unmarshallers.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -87,24 +87,23 @@ def _unmarshal_schema(self, schema: Spec, value: Any) -> Any:
8787
)
8888
return unmarshaller.unmarshal(value)
8989

90-
def _get_param_or_header_value(
90+
def _convert_schema_style_value(
9191
self,
92+
raw: Any,
9293
param_or_header: Spec,
93-
location: Mapping[str, Any],
94-
name: Optional[str] = None,
9594
) -> Any:
96-
casted, schema = self._get_param_or_header_value_and_schema(
97-
param_or_header, location, name
95+
casted, schema = self._convert_schema_style_value_and_schema(
96+
raw, param_or_header
9897
)
9998
if schema is None:
10099
return casted
101100
return self._unmarshal_schema(schema, casted)
102101

103-
def _get_content_value(
104-
self, raw: Any, mimetype: str, content: Spec
102+
def _convert_content_schema_value(
103+
self, raw: Any, content: Spec, mimetype: Optional[str] = None
105104
) -> Any:
106-
casted, schema = self._get_content_value_and_schema(
107-
raw, mimetype, content
105+
casted, schema = self._convert_content_schema_value_and_schema(
106+
raw, content, mimetype
108107
)
109108
if schema is None:
110109
return casted

Diff for: openapi_core/validation/request/validators.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ def _get_parameter(
189189

190190
param_location = param["in"]
191191
location = parameters[param_location]
192+
192193
try:
193-
return self._get_param_or_header_value(param, location)
194+
return self._get_param_or_header(param, location, name=name)
194195
except KeyError:
195196
required = param.getkey("required", False)
196197
if required:
@@ -248,7 +249,7 @@ def _get_body(
248249
content = request_body / "content"
249250

250251
raw_body = self._get_body_value(body, request_body)
251-
return self._get_content_value(raw_body, mimetype, content)
252+
return self._convert_content_schema_value(raw_body, content, mimetype)
252253

253254
def _get_body_value(self, body: Optional[str], request_body: Spec) -> Any:
254255
if not body:

Diff for: openapi_core/validation/response/validators.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def _iter_errors(
4242
operation: Spec,
4343
) -> Iterator[Exception]:
4444
try:
45-
operation_response = self._get_operation_response(
45+
operation_response = self._find_operation_response(
4646
status_code, operation
4747
)
4848
# don't process if operation errors
@@ -64,7 +64,7 @@ def _iter_data_errors(
6464
self, status_code: int, data: str, mimetype: str, operation: Spec
6565
) -> Iterator[Exception]:
6666
try:
67-
operation_response = self._get_operation_response(
67+
operation_response = self._find_operation_response(
6868
status_code, operation
6969
)
7070
# don't process if operation errors
@@ -81,7 +81,7 @@ def _iter_headers_errors(
8181
self, status_code: int, headers: Mapping[str, Any], operation: Spec
8282
) -> Iterator[Exception]:
8383
try:
84-
operation_response = self._get_operation_response(
84+
operation_response = self._find_operation_response(
8585
status_code, operation
8686
)
8787
# don't process if operation errors
@@ -94,7 +94,7 @@ def _iter_headers_errors(
9494
except HeadersError as exc:
9595
yield from exc.context
9696

97-
def _get_operation_response(
97+
def _find_operation_response(
9898
self,
9999
status_code: int,
100100
operation: Spec,
@@ -114,7 +114,7 @@ def _get_data(
114114
content = operation_response / "content"
115115

116116
raw_data = self._get_data_value(data)
117-
return self._get_content_value(raw_data, mimetype, content)
117+
return self._convert_content_schema_value(raw_data, content, mimetype)
118118

119119
def _get_data_value(self, data: str) -> Any:
120120
if not data:
@@ -163,7 +163,7 @@ def _get_header(
163163
)
164164

165165
try:
166-
return self._get_param_or_header_value(header, headers, name=name)
166+
return self._get_param_or_header(header, headers, name=name)
167167
except KeyError:
168168
required = header.getkey("required", False)
169169
if required:

Diff for: openapi_core/validation/validators.py

+114-41
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""OpenAPI core validation validators module"""
2+
import re
23
from functools import cached_property
34
from typing import Any
45
from typing import Mapping
@@ -23,7 +24,12 @@
2324
)
2425
from openapi_core.protocols import Request
2526
from openapi_core.protocols import WebhookRequest
26-
from openapi_core.schema.parameters import get_value
27+
from openapi_core.schema.parameters import get_aslist
28+
from openapi_core.schema.parameters import get_deep_object_value
29+
from openapi_core.schema.parameters import get_explode
30+
from openapi_core.schema.parameters import get_style
31+
from openapi_core.schema.protocols import SuportsGetAll
32+
from openapi_core.schema.protocols import SuportsGetList
2733
from openapi_core.spec import Spec
2834
from openapi_core.templating.media_types.datatypes import MediaType
2935
from openapi_core.templating.paths.datatypes import PathOperationServer
@@ -70,10 +76,14 @@ def __init__(
7076
self.extra_format_validators = extra_format_validators
7177
self.extra_media_type_deserializers = extra_media_type_deserializers
7278

73-
def _get_media_type(self, content: Spec, mimetype: str) -> MediaType:
79+
def _find_media_type(
80+
self, content: Spec, mimetype: Optional[str] = None
81+
) -> MediaType:
7482
from openapi_core.templating.media_types.finders import MediaTypeFinder
7583

7684
finder = MediaTypeFinder(content)
85+
if mimetype is None:
86+
return finder.get_first()
7787
return finder.find(mimetype)
7888

7989
def _deserialise_media_type(self, mimetype: str, value: Any) -> Any:
@@ -99,69 +109,93 @@ def _validate_schema(self, schema: Spec, value: Any) -> None:
99109
)
100110
validator.validate(value)
101111

102-
def _get_param_or_header_value(
112+
def _get_param_or_header(
103113
self,
104114
param_or_header: Spec,
105115
location: Mapping[str, Any],
106116
name: Optional[str] = None,
107117
) -> Any:
108-
casted, schema = self._get_param_or_header_value_and_schema(
109-
param_or_header, location, name
118+
# Simple scenario
119+
if "content" not in param_or_header:
120+
return self._get_simple_param_or_header(
121+
param_or_header, location, name=name
122+
)
123+
124+
# Complex scenario
125+
return self._get_complex_param_or_header(
126+
param_or_header, location, name=name
127+
)
128+
129+
def _get_simple_param_or_header(
130+
self,
131+
param_or_header: Spec,
132+
location: Mapping[str, Any],
133+
name: Optional[str] = None,
134+
) -> Any:
135+
try:
136+
raw = self._get_style_value(param_or_header, location, name=name)
137+
except KeyError:
138+
# in simple scenrios schema always exist
139+
schema = param_or_header / "schema"
140+
if "default" not in schema:
141+
raise
142+
raw = schema["default"]
143+
return self._convert_schema_style_value(raw, param_or_header)
144+
145+
def _get_complex_param_or_header(
146+
self,
147+
param_or_header: Spec,
148+
location: Mapping[str, Any],
149+
name: Optional[str] = None,
150+
) -> Any:
151+
content = param_or_header / "content"
152+
# no point to catch KetError
153+
# in complex scenrios schema doesn't exist
154+
raw = self._get_media_type_value(param_or_header, location, name=name)
155+
return self._convert_content_schema_value(raw, content)
156+
157+
def _convert_schema_style_value(
158+
self,
159+
raw: Any,
160+
param_or_header: Spec,
161+
) -> Any:
162+
casted, schema = self._convert_schema_style_value_and_schema(
163+
raw, param_or_header
110164
)
111165
if schema is None:
112166
return casted
113167
self._validate_schema(schema, casted)
114168
return casted
115169

116-
def _get_content_value(
117-
self, raw: Any, mimetype: str, content: Spec
170+
def _convert_content_schema_value(
171+
self, raw: Any, content: Spec, mimetype: Optional[str] = None
118172
) -> Any:
119-
casted, schema = self._get_content_value_and_schema(
120-
raw, mimetype, content
173+
casted, schema = self._convert_content_schema_value_and_schema(
174+
raw, content, mimetype
121175
)
122176
if schema is None:
123177
return casted
124178
self._validate_schema(schema, casted)
125179
return casted
126180

127-
def _get_param_or_header_value_and_schema(
181+
def _convert_schema_style_value_and_schema(
128182
self,
183+
raw: Any,
129184
param_or_header: Spec,
130-
location: Mapping[str, Any],
131-
name: Optional[str] = None,
132185
) -> Tuple[Any, Spec]:
133-
try:
134-
raw_value = get_value(param_or_header, location, name=name)
135-
except KeyError:
136-
if "schema" not in param_or_header:
137-
raise
138-
schema = param_or_header / "schema"
139-
if "default" not in schema:
140-
raise
141-
casted = schema["default"]
142-
else:
143-
# Simple scenario
144-
if "content" not in param_or_header:
145-
deserialised = self._deserialise_style(
146-
param_or_header, raw_value
147-
)
148-
schema = param_or_header / "schema"
149-
# Complex scenario
150-
else:
151-
content = param_or_header / "content"
152-
mimetype, media_type = next(content.items())
153-
deserialised = self._deserialise_media_type(
154-
mimetype, raw_value
155-
)
156-
schema = media_type / "schema"
157-
casted = self._cast(schema, deserialised)
186+
deserialised = self._deserialise_style(param_or_header, raw)
187+
schema = param_or_header / "schema"
188+
casted = self._cast(schema, deserialised)
158189
return casted, schema
159190

160-
def _get_content_value_and_schema(
161-
self, raw: Any, mimetype: str, content: Spec
191+
def _convert_content_schema_value_and_schema(
192+
self,
193+
raw: Any,
194+
content: Spec,
195+
mimetype: Optional[str] = None,
162196
) -> Tuple[Any, Optional[Spec]]:
163-
media_type, mimetype = self._get_media_type(content, mimetype)
164-
deserialised = self._deserialise_media_type(mimetype, raw)
197+
media_type, mime_type = self._find_media_type(content, mimetype)
198+
deserialised = self._deserialise_media_type(mime_type, raw)
165199
casted = self._cast(media_type, deserialised)
166200

167201
if "schema" not in media_type:
@@ -170,6 +204,45 @@ def _get_content_value_and_schema(
170204
schema = media_type / "schema"
171205
return casted, schema
172206

207+
def _get_style_value(
208+
self,
209+
param_or_header: Spec,
210+
location: Mapping[str, Any],
211+
name: Optional[str] = None,
212+
) -> Any:
213+
name = name or param_or_header["name"]
214+
style = get_style(param_or_header)
215+
if name not in location:
216+
# Only check if the name is not in the location if the style of
217+
# the param is deepObject,this is because deepObjects will never be found
218+
# as their key also includes the properties of the object already.
219+
if style != "deepObject":
220+
raise KeyError
221+
keys_str = " ".join(location.keys())
222+
if not re.search(rf"{name}\[\w+\]", keys_str):
223+
raise KeyError
224+
225+
aslist = get_aslist(param_or_header)
226+
explode = get_explode(param_or_header)
227+
if aslist and explode:
228+
if style == "deepObject":
229+
return get_deep_object_value(location, name)
230+
if isinstance(location, SuportsGetAll):
231+
return location.getall(name)
232+
if isinstance(location, SuportsGetList):
233+
return location.getlist(name)
234+
235+
return location[name]
236+
237+
def _get_media_type_value(
238+
self,
239+
param_or_header: Spec,
240+
location: Mapping[str, Any],
241+
name: Optional[str] = None,
242+
) -> Any:
243+
name = name or param_or_header["name"]
244+
return location[name]
245+
173246

174247
class BaseAPICallValidator(BaseValidator):
175248
@cached_property

0 commit comments

Comments
 (0)