Skip to content

Commit 859242c

Browse files
committed
Allow unmarshalling kwargs for unmarshalling processors
1 parent 84b3b2e commit 859242c

File tree

9 files changed

+120
-41
lines changed

9 files changed

+120
-41
lines changed

docs/integrations.rst

+48
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,23 @@ The Falcon API can be integrated by ``FalconOpenAPIMiddleware`` middleware.
129129
middleware=[openapi_middleware],
130130
)
131131
132+
Additional customization parameters can be passed to the middleware.
133+
134+
.. code-block:: python
135+
:emphasize-lines: 5
136+
137+
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
138+
139+
openapi_middleware = FalconOpenAPIMiddleware.from_spec(
140+
spec,
141+
extra_format_validators=extra_format_validators,
142+
)
143+
144+
app = falcon.App(
145+
# ...
146+
middleware=[openapi_middleware],
147+
)
148+
132149
After that you will have access to validation result object with all validated request data from Falcon view through request context.
133150

134151
.. code-block:: python
@@ -192,6 +209,18 @@ Flask views can be integrated by ``FlaskOpenAPIViewDecorator`` decorator.
192209
def home():
193210
return "Welcome home"
194211
212+
Additional customization parameters can be passed to the decorator.
213+
214+
.. code-block:: python
215+
:emphasize-lines: 5
216+
217+
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
218+
219+
openapi = FlaskOpenAPIViewDecorator.from_spec(
220+
spec,
221+
extra_format_validators=extra_format_validators,
222+
)
223+
195224
If you want to decorate class based view you can use the decorators attribute:
196225

197226
.. code-block:: python
@@ -224,6 +253,25 @@ As an alternative to the decorator-based integration, a Flask method based views
224253
view_func=MyView.as_view('home', spec),
225254
)
226255
256+
Additional customization parameters can be passed to the view.
257+
258+
.. code-block:: python
259+
:emphasize-lines: 10
260+
261+
from openapi_core.contrib.flask.views import FlaskOpenAPIView
262+
263+
class MyView(FlaskOpenAPIView):
264+
def get(self):
265+
return "Welcome home"
266+
267+
app.add_url_rule(
268+
'/home',
269+
view_func=MyView.as_view(
270+
'home', spec,
271+
extra_format_validators=extra_format_validators,
272+
),
273+
)
274+
227275
Request parameters
228276
~~~~~~~~~~~~~~~~~~
229277

openapi_core/contrib/falcon/middlewares.py

+4
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ def __init__(
3232
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
3333
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
3434
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
35+
**unmarshaller_kwargs: Any,
3536
):
3637
super().__init__(
3738
spec,
3839
request_unmarshaller_cls=request_unmarshaller_cls,
3940
response_unmarshaller_cls=response_unmarshaller_cls,
41+
**unmarshaller_kwargs,
4042
)
4143
self.request_class = request_class or self.request_class
4244
self.response_class = response_class or self.response_class
@@ -51,6 +53,7 @@ def from_spec(
5153
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
5254
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
5355
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
56+
**unmarshaller_kwargs: Any,
5457
) -> "FalconOpenAPIMiddleware":
5558
return cls(
5659
spec,
@@ -59,6 +62,7 @@ def from_spec(
5962
request_class=request_class,
6063
response_class=response_class,
6164
errors_handler=errors_handler,
65+
**unmarshaller_kwargs,
6266
)
6367

6468
def process_request(self, req: Request, resp: Response) -> None: # type: ignore

openapi_core/contrib/flask/decorators.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ def __init__(
3636
openapi_errors_handler: Type[
3737
FlaskOpenAPIErrorsHandler
3838
] = FlaskOpenAPIErrorsHandler,
39+
**unmarshaller_kwargs: Any,
3940
):
4041
super().__init__(
4142
spec,
4243
request_unmarshaller_cls=request_unmarshaller_cls,
4344
response_unmarshaller_cls=response_unmarshaller_cls,
45+
**unmarshaller_kwargs,
4446
)
4547
self.request_class = request_class
4648
self.response_class = response_class
@@ -73,7 +75,7 @@ def _handle_request_view(
7375
request_result: RequestUnmarshalResult,
7476
view: Callable[[Any], Response],
7577
*args: Any,
76-
**kwargs: Any
78+
**kwargs: Any,
7779
) -> Response:
7880
request = self._get_request()
7981
request.openapi = request_result # type: ignore
@@ -113,6 +115,7 @@ def from_spec(
113115
openapi_errors_handler: Type[
114116
FlaskOpenAPIErrorsHandler
115117
] = FlaskOpenAPIErrorsHandler,
118+
**unmarshaller_kwargs: Any,
116119
) -> "FlaskOpenAPIViewDecorator":
117120
return cls(
118121
spec,
@@ -122,4 +125,5 @@ def from_spec(
122125
response_class=response_class,
123126
request_provider=request_provider,
124127
openapi_errors_handler=openapi_errors_handler,
128+
**unmarshaller_kwargs,
125129
)

openapi_core/contrib/flask/views.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ class FlaskOpenAPIView(MethodView):
1313

1414
openapi_errors_handler = FlaskOpenAPIErrorsHandler
1515

16-
def __init__(self, spec: Spec):
16+
def __init__(self, spec: Spec, **unmarshaller_kwargs: Any):
1717
super().__init__()
1818
self.spec = spec
1919

20-
def dispatch_request(self, *args: Any, **kwargs: Any) -> Any:
21-
decorator = FlaskOpenAPIViewDecorator(
20+
self.decorator = FlaskOpenAPIViewDecorator(
2221
self.spec,
2322
openapi_errors_handler=self.openapi_errors_handler,
23+
**unmarshaller_kwargs,
2424
)
25-
return decorator(super().dispatch_request)(*args, **kwargs)
25+
26+
def dispatch_request(self, *args: Any, **kwargs: Any) -> Any:
27+
return self.decorator(super().dispatch_request)(*args, **kwargs)

openapi_core/unmarshalling/processors.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""OpenAPI core unmarshalling processors module"""
2+
from typing import Any
23
from typing import Optional
34
from typing import Type
45

@@ -20,6 +21,7 @@ def __init__(
2021
spec: Spec,
2122
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
2223
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
24+
**unmarshaller_kwargs: Any,
2325
):
2426
self.spec = spec
2527
if (
@@ -31,8 +33,12 @@ def __init__(
3133
request_unmarshaller_cls = classes.request_unmarshaller_cls
3234
if response_unmarshaller_cls is None:
3335
response_unmarshaller_cls = classes.response_unmarshaller_cls
34-
self.request_unmarshaller = request_unmarshaller_cls(self.spec)
35-
self.response_unmarshaller = response_unmarshaller_cls(self.spec)
36+
self.request_unmarshaller = request_unmarshaller_cls(
37+
self.spec, **unmarshaller_kwargs
38+
)
39+
self.response_unmarshaller = response_unmarshaller_cls(
40+
self.spec, **unmarshaller_kwargs
41+
)
3642

3743
def process_request(self, request: Request) -> RequestUnmarshalResult:
3844
return self.request_unmarshaller.unmarshal(request)

openapi_core/validation/processors.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""OpenAPI core validation processors module"""
2+
from typing import Any
23
from typing import Optional
34

45
from openapi_core.protocols import Request
@@ -15,6 +16,7 @@ def __init__(
1516
spec: Spec,
1617
request_validator_cls: Optional[RequestValidatorType] = None,
1718
response_validator_cls: Optional[ResponseValidatorType] = None,
19+
**unmarshaller_kwargs: Any,
1820
):
1921
self.spec = spec
2022
if request_validator_cls is None or response_validator_cls is None:
@@ -23,8 +25,12 @@ def __init__(
2325
request_validator_cls = classes.request_validator_cls
2426
if response_validator_cls is None:
2527
response_validator_cls = classes.response_validator_cls
26-
self.request_validator = request_validator_cls(self.spec)
27-
self.response_validator = response_validator_cls(self.spec)
28+
self.request_validator = request_validator_cls(
29+
self.spec, **unmarshaller_kwargs
30+
)
31+
self.response_validator = response_validator_cls(
32+
self.spec, **unmarshaller_kwargs
33+
)
2834

2935
def process_request(self, request: Request) -> None:
3036
self.request_validator.validate(request)

tests/integration/contrib/falcon/data/v3.0/falconproject/openapi.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
openapi_spec_path = Path("tests/integration/data/v3.0/petstore.yaml")
99
spec_dict = yaml.load(openapi_spec_path.read_text(), yaml.Loader)
1010
spec = Spec.from_dict(spec_dict)
11-
openapi_middleware = FalconOpenAPIMiddleware.from_spec(spec)
11+
openapi_middleware = FalconOpenAPIMiddleware.from_spec(
12+
spec,
13+
extra_media_type_deserializers={},
14+
)

tests/integration/contrib/flask/test_flask_decorator.py

+31-29
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def test_endpoint_error(self, client):
173173
}
174174
assert result.json == expected_data
175175

176-
def test_valid_response_object(self, client):
176+
def test_response_object_valid(self, client):
177177
def view_response_callable(*args, **kwargs):
178178
from flask.globals import request
179179

@@ -197,7 +197,28 @@ def view_response_callable(*args, **kwargs):
197197
"data": "data",
198198
}
199199

200-
def test_valid_tuple_str(self, client):
200+
@pytest.mark.parametrize(
201+
"response,expected_status,expected_headers",
202+
[
203+
# ((body, status, headers)) response tuple
204+
(
205+
("Not found", 404, {"X-Rate-Limit": "12"}),
206+
404,
207+
{"X-Rate-Limit": "12"},
208+
),
209+
# (body, status) response tuple
210+
(("Not found", 404), 404, {}),
211+
# (body, headers) response tuple
212+
(
213+
({"data": "data"}, {"X-Rate-Limit": "12"}),
214+
200,
215+
{"X-Rate-Limit": "12"},
216+
),
217+
],
218+
)
219+
def test_tuple_valid(
220+
self, client, response, expected_status, expected_headers
221+
):
201222
def view_response_callable(*args, **kwargs):
202223
from flask.globals import request
203224

@@ -208,35 +229,16 @@ def view_response_callable(*args, **kwargs):
208229
"id": 12,
209230
}
210231
)
211-
return ("Not found", 404)
232+
return response
212233

213234
self.view_response_callable = view_response_callable
214235

215236
result = client.get("/browse/12/")
216237

217-
assert result.status_code == 404
218-
assert result.text == "Not found"
219-
220-
def test_valid_tuple_dict(self, client):
221-
def view_response_callable(*args, **kwargs):
222-
from flask.globals import request
223-
224-
assert request.openapi
225-
assert not request.openapi.errors
226-
assert request.openapi.parameters == Parameters(
227-
path={
228-
"id": 12,
229-
}
230-
)
231-
body = dict(data="data")
232-
headers = {"X-Rate-Limit": "12"}
233-
return (body, headers)
234-
235-
self.view_response_callable = view_response_callable
236-
237-
result = client.get("/browse/12/")
238-
239-
assert result.status_code == 200
240-
assert result.json == {
241-
"data": "data",
242-
}
238+
assert result.status_code == expected_status
239+
expected_body = response[0]
240+
if isinstance(expected_body, str):
241+
assert result.text == expected_body
242+
else:
243+
assert result.json == expected_body
244+
assert dict(result.headers).items() >= expected_headers.items()

tests/integration/contrib/flask/test_flask_views.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ def get(self, id):
3838
def post(self, id):
3939
return outer.view_response
4040

41-
return MyDetailsView.as_view("browse_details", spec)
41+
return MyDetailsView.as_view(
42+
"browse_details", spec, extra_media_type_deserializers={}
43+
)
4244

4345
@pytest.fixture
4446
def list_view_func(self, spec):
@@ -48,7 +50,9 @@ class MyListView(FlaskOpenAPIView):
4850
def get(self):
4951
return outer.view_response
5052

51-
return MyListView.as_view("browse_list", spec)
53+
return MyListView.as_view(
54+
"browse_list", spec, extra_format_validators={}
55+
)
5256

5357
@pytest.fixture(autouse=True)
5458
def view(self, app, details_view_func, list_view_func):

0 commit comments

Comments
 (0)