Skip to content

Commit

Permalink
Skip response validation option
Browse files Browse the repository at this point in the history
  • Loading branch information
p1c2u committed Sep 15, 2023
1 parent 1b688bb commit a863e8f
Show file tree
Hide file tree
Showing 14 changed files with 419 additions and 169 deletions.
45 changes: 45 additions & 0 deletions docs/integrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ Django can be integrated by middleware. Add ``DjangoOpenAPIMiddleware`` to your
OPENAPI_SPEC = Spec.from_dict(spec_dict)
You can skip response validation process: by setting ``OPENAPI_RESPONSE_CLS`` to ``None``

.. code-block:: python
:emphasize-lines: 10
# settings.py
from openapi_core import Spec
MIDDLEWARE = [
# ...
'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware',
]
OPENAPI_SPEC = Spec.from_dict(spec_dict)
OPENAPI_RESPONSE_CLS = None
After that you have access to unmarshal result object with all validated request data from Django view through request object.

.. code-block:: python
Expand Down Expand Up @@ -146,6 +162,23 @@ Additional customization parameters can be passed to the middleware.
middleware=[openapi_middleware],
)
You can skip response validation process: by setting ``response_cls`` to ``None``

.. code-block:: python
:emphasize-lines: 5
from openapi_core.contrib.falcon.middlewares import FalconOpenAPIMiddleware
openapi_middleware = FalconOpenAPIMiddleware.from_spec(
spec,
response_cls=None,
)
app = falcon.App(
# ...
middleware=[openapi_middleware],
)
After that you will have access to validation result object with all validated request data from Falcon view through request context.

.. code-block:: python
Expand Down Expand Up @@ -221,6 +254,18 @@ Additional customization parameters can be passed to the decorator.
extra_format_validators=extra_format_validators,
)
You can skip response validation process: by setting ``response_cls`` to ``None``

.. code-block:: python
:emphasize-lines: 5
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
openapi = FlaskOpenAPIViewDecorator.from_spec(
spec,
response_cls=None,
)
If you want to decorate class based view you can use the decorators attribute:

.. code-block:: python
Expand Down
14 changes: 10 additions & 4 deletions openapi_core/contrib/django/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@


class DjangoOpenAPIMiddleware:
request_class = DjangoOpenAPIRequest
response_class = DjangoOpenAPIResponse
request_cls = DjangoOpenAPIRequest
response_cls = DjangoOpenAPIResponse
errors_handler = DjangoOpenAPIErrorsHandler()

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

if hasattr(settings, "OPENAPI_RESPONSE_CLS"):
self.response_cls = settings.OPENAPI_RESPONSE_CLS

self.processor = UnmarshallingProcessor(settings.OPENAPI_SPEC)

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

if self.response_cls is None:
return response
openapi_response = self._get_openapi_response(response)
resp_result = self.processor.process_response(
openapi_request, openapi_response
Expand All @@ -64,9 +69,10 @@ def _handle_response_errors(
def _get_openapi_request(
self, request: HttpRequest
) -> DjangoOpenAPIRequest:
return self.request_class(request)
return self.request_cls(request)

def _get_openapi_response(
self, response: HttpResponse
) -> DjangoOpenAPIResponse:
return self.response_class(response)
assert self.response_cls is not None
return self.response_cls(response)
27 changes: 15 additions & 12 deletions openapi_core/contrib/falcon/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@


class FalconOpenAPIMiddleware(UnmarshallingProcessor):
request_class = FalconOpenAPIRequest
response_class = FalconOpenAPIResponse
request_cls = FalconOpenAPIRequest
response_cls = FalconOpenAPIResponse
errors_handler = FalconOpenAPIErrorsHandler()

def __init__(
self,
spec: Spec,
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
**unmarshaller_kwargs: Any,
):
Expand All @@ -40,8 +40,8 @@ def __init__(
response_unmarshaller_cls=response_unmarshaller_cls,
**unmarshaller_kwargs,
)
self.request_class = request_class or self.request_class
self.response_class = response_class or self.response_class
self.request_cls = request_cls or self.request_cls
self.response_cls = response_cls or self.response_cls
self.errors_handler = errors_handler or self.errors_handler

@classmethod
Expand All @@ -50,17 +50,17 @@ def from_spec(
spec: Spec,
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
request_class: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_class: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
request_cls: Type[FalconOpenAPIRequest] = FalconOpenAPIRequest,
response_cls: Type[FalconOpenAPIResponse] = FalconOpenAPIResponse,
errors_handler: Optional[FalconOpenAPIErrorsHandler] = None,
**unmarshaller_kwargs: Any,
) -> "FalconOpenAPIMiddleware":
return cls(
spec,
request_unmarshaller_cls=request_unmarshaller_cls,
response_unmarshaller_cls=response_unmarshaller_cls,
request_class=request_class,
response_class=response_class,
request_cls=request_cls,
response_cls=response_cls,
errors_handler=errors_handler,
**unmarshaller_kwargs,
)
Expand All @@ -74,6 +74,8 @@ def process_request(self, req: Request, resp: Response) -> None: # type: ignore
def process_response( # type: ignore
self, req: Request, resp: Response, resource: Any, req_succeeded: bool
) -> None:
if self.response_cls is None:
return resp
openapi_req = self._get_openapi_request(req)
openapi_resp = self._get_openapi_response(resp)
resp.context.openapi = super().process_response(
Expand Down Expand Up @@ -101,9 +103,10 @@ def _handle_response_errors(
return self.errors_handler.handle(req, resp, response_result.errors)

def _get_openapi_request(self, request: Request) -> FalconOpenAPIRequest:
return self.request_class(request)
return self.request_cls(request)

def _get_openapi_response(
self, response: Response
) -> FalconOpenAPIResponse:
return self.response_class(response)
assert self.response_cls is not None
return self.response_cls(response)
2 changes: 2 additions & 0 deletions openapi_core/contrib/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse

__all__ = [
"FlaskOpenAPIViewDecorator",
"FlaskOpenAPIRequest",
"FlaskOpenAPIResponse",
]
25 changes: 15 additions & 10 deletions openapi_core/contrib/flask/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ def __init__(
spec: Spec,
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
request_class: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
response_class: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
request_cls: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
response_cls: Optional[
Type[FlaskOpenAPIResponse]
] = FlaskOpenAPIResponse,
request_provider: Type[FlaskRequestProvider] = FlaskRequestProvider,
openapi_errors_handler: Type[
FlaskOpenAPIErrorsHandler
Expand All @@ -44,8 +46,8 @@ def __init__(
response_unmarshaller_cls=response_unmarshaller_cls,
**unmarshaller_kwargs,
)
self.request_class = request_class
self.response_class = response_class
self.request_cls = request_cls
self.response_cls = response_cls
self.request_provider = request_provider
self.openapi_errors_handler = openapi_errors_handler

Expand All @@ -60,6 +62,8 @@ def decorated(*args: Any, **kwargs: Any) -> Response:
response = self._handle_request_view(
request_result, view, *args, **kwargs
)
if self.response_cls is None:
return response
openapi_response = self._get_openapi_response(response)
response_result = self.process_response(
openapi_request, openapi_response
Expand Down Expand Up @@ -96,21 +100,22 @@ def _get_request(self) -> Request:
return request

def _get_openapi_request(self, request: Request) -> FlaskOpenAPIRequest:
return self.request_class(request)
return self.request_cls(request)

def _get_openapi_response(
self, response: Response
) -> FlaskOpenAPIResponse:
return self.response_class(response)
assert self.response_cls is not None
return self.response_cls(response)

@classmethod
def from_spec(
cls,
spec: Spec,
request_unmarshaller_cls: Optional[RequestUnmarshallerType] = None,
response_unmarshaller_cls: Optional[ResponseUnmarshallerType] = None,
request_class: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
response_class: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
request_cls: Type[FlaskOpenAPIRequest] = FlaskOpenAPIRequest,
response_cls: Type[FlaskOpenAPIResponse] = FlaskOpenAPIResponse,
request_provider: Type[FlaskRequestProvider] = FlaskRequestProvider,
openapi_errors_handler: Type[
FlaskOpenAPIErrorsHandler
Expand All @@ -121,8 +126,8 @@ def from_spec(
spec,
request_unmarshaller_cls=request_unmarshaller_cls,
response_unmarshaller_cls=response_unmarshaller_cls,
request_class=request_class,
response_class=response_class,
request_cls=request_cls,
response_cls=response_cls,
request_provider=request_provider,
openapi_errors_handler=openapi_errors_handler,
**unmarshaller_kwargs,
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.http import HttpResponse
from rest_framework.views import APIView


class TagListView(APIView):
def get(self, request):
assert request.openapi
assert not request.openapi.errors
return HttpResponse("success")

@staticmethod
def get_extra_actions():
return []
13 changes: 10 additions & 3 deletions tests/integration/contrib/django/data/v3.0/djangoproject/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
from django.contrib import admin
from django.urls import include
from django.urls import path
from djangoproject.pets import views
from djangoproject.pets.views import PetDetailView
from djangoproject.pets.views import PetListView
from djangoproject.tags.views import TagListView

urlpatterns = [
path("admin/", admin.site.urls),
Expand All @@ -26,12 +28,17 @@
),
path(
"v1/pets",
views.PetListView.as_view(),
PetListView.as_view(),
name="pet_list_view",
),
path(
"v1/pets/<int:petId>",
views.PetDetailView.as_view(),
PetDetailView.as_view(),
name="pet_detail_view",
),
path(
"v1/tags",
TagListView.as_view(),
name="tag_list_view",
),
]
23 changes: 23 additions & 0 deletions tests/integration/contrib/django/test_django_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from unittest import mock

import pytest
from django.test.utils import override_settings


class BaseTestDjangoProject:
Expand Down Expand Up @@ -372,3 +373,25 @@ def test_post_valid(self, api_client):

assert response.status_code == 201
assert not response.content


class TestDRFTagListView(BaseTestDRF):
def test_get_response_invalid(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
response = client.get("/v1/tags", **headers)

assert response.status_code == 415

def test_get_skip_response_validation(self, client):
headers = {
"HTTP_AUTHORIZATION": "Basic testuser",
"HTTP_HOST": "petstore.swagger.io",
}
with override_settings(OPENAPI_RESPONSE_CLS=None):
response = client.get("/v1/tags", **headers)

assert response.status_code == 200
assert response.content == b"success"
37 changes: 37 additions & 0 deletions tests/integration/contrib/flask/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest
from flask import Flask


@pytest.fixture(scope="session")
def spec(factory):
specfile = "contrib/flask/data/v3.0/flask_factory.yaml"
return factory.spec_from_file(specfile)


@pytest.fixture
def app(app_factory):
return app_factory()


@pytest.fixture
def client(client_factory, app):
return client_factory(app)


@pytest.fixture(scope="session")
def client_factory():
def create(app):
return app.test_client()

return create


@pytest.fixture(scope="session")
def app_factory():
def create(root_path=None):
app = Flask("__main__", root_path=root_path)
app.config["DEBUG"] = True
app.config["TESTING"] = True
return app

return create
Loading

0 comments on commit a863e8f

Please sign in to comment.