Skip to content

Commit a863e8f

Browse files
committed
Skip response validation option
1 parent 1b688bb commit a863e8f

File tree

14 files changed

+419
-169
lines changed

14 files changed

+419
-169
lines changed

docs/integrations.rst

+45
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ Django can be integrated by middleware. Add ``DjangoOpenAPIMiddleware`` to your
6363
6464
OPENAPI_SPEC = Spec.from_dict(spec_dict)
6565
66+
You can skip response validation process: by setting ``OPENAPI_RESPONSE_CLS`` to ``None``
67+
68+
.. code-block:: python
69+
:emphasize-lines: 10
70+
71+
# settings.py
72+
from openapi_core import Spec
73+
74+
MIDDLEWARE = [
75+
# ...
76+
'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware',
77+
]
78+
79+
OPENAPI_SPEC = Spec.from_dict(spec_dict)
80+
OPENAPI_RESPONSE_CLS = None
81+
6682
After that you have access to unmarshal result object with all validated request data from Django view through request object.
6783

6884
.. code-block:: python
@@ -146,6 +162,23 @@ Additional customization parameters can be passed to the middleware.
146162
middleware=[openapi_middleware],
147163
)
148164
165+
You can skip response validation process: by setting ``response_cls`` to ``None``
166+
167+
.. code-block:: python
168+
:emphasize-lines: 5
169+
170+
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
171+
172+
openapi_middleware = FalconOpenAPIMiddleware.from_spec(
173+
spec,
174+
response_cls=None,
175+
)
176+
177+
app = falcon.App(
178+
# ...
179+
middleware=[openapi_middleware],
180+
)
181+
149182
After that you will have access to validation result object with all validated request data from Falcon view through request context.
150183

151184
.. code-block:: python
@@ -221,6 +254,18 @@ Additional customization parameters can be passed to the decorator.
221254
extra_format_validators=extra_format_validators,
222255
)
223256
257+
You can skip response validation process: by setting ``response_cls`` to ``None``
258+
259+
.. code-block:: python
260+
:emphasize-lines: 5
261+
262+
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
263+
264+
openapi = FlaskOpenAPIViewDecorator.from_spec(
265+
spec,
266+
response_cls=None,
267+
)
268+
224269
If you want to decorate class based view you can use the decorators attribute:
225270

226271
.. code-block:: python

openapi_core/contrib/django/middlewares.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919

2020
class DjangoOpenAPIMiddleware:
21-
request_class = DjangoOpenAPIRequest
22-
response_class = DjangoOpenAPIResponse
21+
request_cls = DjangoOpenAPIRequest
22+
response_cls = DjangoOpenAPIResponse
2323
errors_handler = DjangoOpenAPIErrorsHandler()
2424

2525
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
@@ -28,6 +28,9 @@ def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
2828
if not hasattr(settings, "OPENAPI_SPEC"):
2929
raise ImproperlyConfigured("OPENAPI_SPEC not defined in settings")
3030

31+
if hasattr(settings, "OPENAPI_RESPONSE_CLS"):
32+
self.response_cls = settings.OPENAPI_RESPONSE_CLS
33+
3134
self.processor = UnmarshallingProcessor(settings.OPENAPI_SPEC)
3235

3336
def __call__(self, request: HttpRequest) -> HttpResponse:
@@ -39,6 +42,8 @@ def __call__(self, request: HttpRequest) -> HttpResponse:
3942
request.openapi = req_result
4043
response = self.get_response(request)
4144

45+
if self.response_cls is None:
46+
return response
4247
openapi_response = self._get_openapi_response(response)
4348
resp_result = self.processor.process_response(
4449
openapi_request, openapi_response
@@ -64,9 +69,10 @@ def _handle_response_errors(
6469
def _get_openapi_request(
6570
self, request: HttpRequest
6671
) -> DjangoOpenAPIRequest:
67-
return self.request_class(request)
72+
return self.request_cls(request)
6873

6974
def _get_openapi_response(
7075
self, response: HttpResponse
7176
) -> DjangoOpenAPIResponse:
72-
return self.response_class(response)
77+
assert self.response_cls is not None
78+
return self.response_cls(response)

openapi_core/contrib/falcon/middlewares.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@
2020

2121

2222
class FalconOpenAPIMiddleware(UnmarshallingProcessor):
23-
request_class = FalconOpenAPIRequest
24-
response_class = FalconOpenAPIResponse
23+
request_cls = FalconOpenAPIRequest
24+
response_cls = FalconOpenAPIResponse
2525
errors_handler = FalconOpenAPIErrorsHandler()
2626

2727
def __init__(
2828
self,
2929
spec: Spec,
3030
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
3131
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
32-
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
33-
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
32+
request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
33+
response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
3434
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
3535
**unmarshaller_kwargs: Any,
3636
):
@@ -40,8 +40,8 @@ def __init__(
4040
response_unmarshaller_cls=response_unmarshaller_cls,
4141
**unmarshaller_kwargs,
4242
)
43-
self.request_class = request_class or self.request_class
44-
self.response_class = response_class or self.response_class
43+
self.request_cls = request_cls or self.request_cls
44+
self.response_cls = response_cls or self.response_cls
4545
self.errors_handler = errors_handler or self.errors_handler
4646

4747
@classmethod
@@ -50,17 +50,17 @@ def from_spec(
5050
spec: Spec,
5151
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
5252
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
53-
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
54-
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
53+
request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
54+
response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
5555
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
5656
**unmarshaller_kwargs: Any,
5757
) -> "FalconOpenAPIMiddleware":
5858
return cls(
5959
spec,
6060
request_unmarshaller_cls=request_unmarshaller_cls,
6161
response_unmarshaller_cls=response_unmarshaller_cls,
62-
request_class=request_class,
63-
response_class=response_class,
62+
request_cls=request_cls,
63+
response_cls=response_cls,
6464
errors_handler=errors_handler,
6565
**unmarshaller_kwargs,
6666
)
@@ -74,6 +74,8 @@ def process_request(self, req: Request, resp: Response) -> None: # type: ignore
7474
def process_response( # type: ignore
7575
self, req: Request, resp: Response, resource: Any, req_succeeded: bool
7676
) -> None:
77+
if self.response_cls is None:
78+
return resp
7779
openapi_req = self._get_openapi_request(req)
7880
openapi_resp = self._get_openapi_response(resp)
7981
resp.context.openapi = super().process_response(
@@ -101,9 +103,10 @@ def _handle_response_errors(
101103
return self.errors_handler.handle(req, resp, response_result.errors)
102104

103105
def _get_openapi_request(self, request: Request) -> FalconOpenAPIRequest:
104-
return self.request_class(request)
106+
return self.request_cls(request)
105107

106108
def _get_openapi_response(
107109
self, response: Response
108110
) -> FalconOpenAPIResponse:
109-
return self.response_class(response)
111+
assert self.response_cls is not None
112+
return self.response_cls(response)
+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
12
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest
23
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse
34

45
__all__ = [
6+
"FlaskOpenAPIViewDecorator",
57
"FlaskOpenAPIRequest",
68
"FlaskOpenAPIResponse",
79
]

openapi_core/contrib/flask/decorators.py

+15-10
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ def __init__(
3030
spec: Spec,
3131
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
3232
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
33-
request_class: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
34-
response_class: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
33+
request_cls: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
34+
response_cls: Optional[
35+
Type[FlaskOpenAPIResponse]
36+
] = FlaskOpenAPIResponse,
3537
request_provider: Type[FlaskRequestProvider] = FlaskRequestProvider,
3638
openapi_errors_handler: Type[
3739
FlaskOpenAPIErrorsHandler
@@ -44,8 +46,8 @@ def __init__(
4446
response_unmarshaller_cls=response_unmarshaller_cls,
4547
**unmarshaller_kwargs,
4648
)
47-
self.request_class = request_class
48-
self.response_class = response_class
49+
self.request_cls = request_cls
50+
self.response_cls = response_cls
4951
self.request_provider = request_provider
5052
self.openapi_errors_handler = openapi_errors_handler
5153

@@ -60,6 +62,8 @@ def decorated(*args: Any, **kwargs: Any) -> Response:
6062
response = self._handle_request_view(
6163
request_result, view, *args, **kwargs
6264
)
65+
if self.response_cls is None:
66+
return response
6367
openapi_response = self._get_openapi_response(response)
6468
response_result = self.process_response(
6569
openapi_request, openapi_response
@@ -96,21 +100,22 @@ def _get_request(self) -> Request:
96100
return request
97101

98102
def _get_openapi_request(self, request: Request) -> FlaskOpenAPIRequest:
99-
return self.request_class(request)
103+
return self.request_cls(request)
100104

101105
def _get_openapi_response(
102106
self, response: Response
103107
) -> FlaskOpenAPIResponse:
104-
return self.response_class(response)
108+
assert self.response_cls is not None
109+
return self.response_cls(response)
105110

106111
@classmethod
107112
def from_spec(
108113
cls,
109114
spec: Spec,
110115
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
111116
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
112-
request_class: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
113-
response_class: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
117+
request_cls: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
118+
response_cls: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
114119
request_provider: Type[FlaskRequestProvider] = FlaskRequestProvider,
115120
openapi_errors_handler: Type[
116121
FlaskOpenAPIErrorsHandler
@@ -121,8 +126,8 @@ def from_spec(
121126
spec,
122127
request_unmarshaller_cls=request_unmarshaller_cls,
123128
response_unmarshaller_cls=response_unmarshaller_cls,
124-
request_class=request_class,
125-
response_class=response_class,
129+
request_cls=request_cls,
130+
response_cls=response_cls,
126131
request_provider=request_provider,
127132
openapi_errors_handler=openapi_errors_handler,
128133
**unmarshaller_kwargs,

tests/integration/contrib/django/data/v3.0/djangoproject/tags/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.http import HttpResponse
2+
from rest_framework.views import APIView
3+
4+
5+
class TagListView(APIView):
6+
def get(self, request):
7+
assert request.openapi
8+
assert not request.openapi.errors
9+
return HttpResponse("success")
10+
11+
@staticmethod
12+
def get_extra_actions():
13+
return []

tests/integration/contrib/django/data/v3.0/djangoproject/urls.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from django.contrib import admin
1717
from django.urls import include
1818
from django.urls import path
19-
from djangoproject.pets import views
19+
from djangoproject.pets.views import PetDetailView
20+
from djangoproject.pets.views import PetListView
21+
from djangoproject.tags.views import TagListView
2022

2123
urlpatterns = [
2224
path("admin/", admin.site.urls),
@@ -26,12 +28,17 @@
2628
),
2729
path(
2830
"v1/pets",
29-
views.PetListView.as_view(),
31+
PetListView.as_view(),
3032
name="pet_list_view",
3133
),
3234
path(
3335
"v1/pets/<int:petId>",
34-
views.PetDetailView.as_view(),
36+
PetDetailView.as_view(),
3537
name="pet_detail_view",
3638
),
39+
path(
40+
"v1/tags",
41+
TagListView.as_view(),
42+
name="tag_list_view",
43+
),
3744
]

tests/integration/contrib/django/test_django_project.py

+23
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from unittest import mock
66

77
import pytest
8+
from django.test.utils import override_settings
89

910

1011
class BaseTestDjangoProject:
@@ -372,3 +373,25 @@ def test_post_valid(self, api_client):
372373

373374
assert response.status_code == 201
374375
assert not response.content
376+
377+
378+
class TestDRFTagListView(BaseTestDRF):
379+
def test_get_response_invalid(self, client):
380+
headers = {
381+
"HTTP_AUTHORIZATION": "Basic testuser",
382+
"HTTP_HOST": "petstore.swagger.io",
383+
}
384+
response = client.get("/v1/tags", **headers)
385+
386+
assert response.status_code == 415
387+
388+
def test_get_skip_response_validation(self, client):
389+
headers = {
390+
"HTTP_AUTHORIZATION": "Basic testuser",
391+
"HTTP_HOST": "petstore.swagger.io",
392+
}
393+
with override_settings(OPENAPI_RESPONSE_CLS=None):
394+
response = client.get("/v1/tags", **headers)
395+
396+
assert response.status_code == 200
397+
assert response.content == b"success"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
from flask import Flask
3+
4+
5+
@pytest.fixture(scope="session")
6+
def spec(factory):
7+
specfile = "contrib/flask/data/v3.0/flask_factory.yaml"
8+
return factory.spec_from_file(specfile)
9+
10+
11+
@pytest.fixture
12+
def app(app_factory):
13+
return app_factory()
14+
15+
16+
@pytest.fixture
17+
def client(client_factory, app):
18+
return client_factory(app)
19+
20+
21+
@pytest.fixture(scope="session")
22+
def client_factory():
23+
def create(app):
24+
return app.test_client()
25+
26+
return create
27+
28+
29+
@pytest.fixture(scope="session")
30+
def app_factory():
31+
def create(root_path=None):
32+
app = Flask("__main__", root_path=root_path)
33+
app.config["DEBUG"] = True
34+
app.config["TESTING"] = True
35+
return app
36+
37+
return create

0 commit comments

Comments
 (0)