Skip to content

Commit 2c12d12

Browse files
committed
Parameter and header get value refactor
1 parent 0da2a38 commit 2c12d12

File tree

7 files changed

+133
-77
lines changed

7 files changed

+133
-77
lines changed

openapi_core/schema/parameters.py

Lines changed: 0 additions & 32 deletions
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,

openapi_core/templating/media_types/finders.py

Lines changed: 4 additions & 0 deletions
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)

openapi_core/unmarshalling/unmarshallers.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,21 @@ def _unmarshal_schema(self, schema: Spec, value: Any) -> Any:
8989

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

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

openapi_core/validation/request/validators.py

Lines changed: 3 additions & 2 deletions
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._get_content_value(raw_body, content, mimetype)
252253

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

openapi_core/validation/response/validators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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._get_content_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:

openapi_core/validation/validators.py

Lines changed: 105 additions & 36 deletions
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 _get_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,25 +109,65 @@ 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(
113+
self,
114+
param_or_header: Spec,
115+
location: Mapping[str, Any],
116+
name: Optional[str] = None,
117+
) -> Any:
118+
# Simple scenario
119+
if "content" not in param_or_header:
120+
return self._get_simple_value(param_or_header, location, name=name)
121+
122+
# Complex scenario
123+
return self._get_complex(param_or_header, location, name=name)
124+
125+
def _get_simple_value(
126+
self,
127+
param_or_header: Spec,
128+
location: Mapping[str, Any],
129+
name: Optional[str] = None,
130+
) -> Any:
131+
try:
132+
raw = self._get_style_value(param_or_header, location, name=name)
133+
except KeyError:
134+
# in simple scenrios schema always exist
135+
schema = param_or_header / "schema"
136+
if "default" not in schema:
137+
raise
138+
raw = schema["default"]
139+
return self._get_param_or_header_value(raw, param_or_header)
140+
141+
def _get_complex(
103142
self,
104143
param_or_header: Spec,
105144
location: Mapping[str, Any],
106145
name: Optional[str] = None,
146+
) -> Any:
147+
content = param_or_header / "content"
148+
# no point to catch KetError
149+
# in complex scenrios schema doesn't exist
150+
raw = self._get_media_type_value(param_or_header, location, name=name)
151+
return self._get_content_value(raw, content)
152+
153+
def _get_param_or_header_value(
154+
self,
155+
raw: Any,
156+
param_or_header: Spec,
107157
) -> Any:
108158
casted, schema = self._get_param_or_header_value_and_schema(
109-
param_or_header, location, name
159+
raw, param_or_header
110160
)
111161
if schema is None:
112162
return casted
113163
self._validate_schema(schema, casted)
114164
return casted
115165

116166
def _get_content_value(
117-
self, raw: Any, mimetype: str, content: Spec
167+
self, raw: Any, content: Spec, mimetype: Optional[str] = None
118168
) -> Any:
119169
casted, schema = self._get_content_value_and_schema(
120-
raw, mimetype, content
170+
raw, content, mimetype
121171
)
122172
if schema is None:
123173
return casted
@@ -126,42 +176,22 @@ def _get_content_value(
126176

127177
def _get_param_or_header_value_and_schema(
128178
self,
179+
raw: Any,
129180
param_or_header: Spec,
130-
location: Mapping[str, Any],
131-
name: Optional[str] = None,
132181
) -> 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)
182+
deserialised = self._deserialise_style(param_or_header, raw)
183+
schema = param_or_header / "schema"
184+
casted = self._cast(schema, deserialised)
158185
return casted, schema
159186

160187
def _get_content_value_and_schema(
161-
self, raw: Any, mimetype: str, content: Spec
188+
self,
189+
raw: Any,
190+
content: Spec,
191+
mimetype: Optional[str] = None,
162192
) -> Tuple[Any, Optional[Spec]]:
163-
media_type, mimetype = self._get_media_type(content, mimetype)
164-
deserialised = self._deserialise_media_type(mimetype, raw)
193+
media_type, mime_type = self._get_media_type(content, mimetype)
194+
deserialised = self._deserialise_media_type(mime_type, raw)
165195
casted = self._cast(media_type, deserialised)
166196

167197
if "schema" not in media_type:
@@ -170,6 +200,45 @@ def _get_content_value_and_schema(
170200
schema = media_type / "schema"
171201
return casted, schema
172202

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

174243
class BaseAPICallValidator(BaseValidator):
175244
@cached_property

tests/integration/data/v3.0/petstore.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ paths:
8282
application/json:
8383
schema:
8484
$ref: "#/components/schemas/Coordinates"
85+
- name: color
86+
in: query
87+
description: RGB color
88+
style: deepObject
89+
required: false
90+
explode: true
91+
schema:
92+
type: object
93+
properties:
94+
R:
95+
type: integer
96+
G:
97+
type: integer
98+
B:
99+
type: integer
85100
responses:
86101
'200':
87102
$ref: "#/components/responses/PetsResponse"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy