Skip to content

Django2 support drop #358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion docs/integrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,29 @@ Django
------

This section describes integration with `Django <https://www.djangoproject.com>`__ web framework.
The integration supports Django from version 3.0 and above.

For Django 2.2 you can use DjangoOpenAPIRequest a Django request factory:
Middleware
~~~~~~~~~~

Django can be integrated by middleware. Add `DjangoOpenAPIMiddleware` to your `MIDDLEWARE` list and define `OPENAPI_SPEC`

.. code-block:: python

# settings.py
from openapi_core import create_spec

MIDDLEWARE = [
# ...
'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware',
]

OPENAPI_SPEC = create_spec(spec_dict)

Low level
~~~~~~~~~

For Django you can use DjangoOpenAPIRequest a Django request factory:

.. code-block:: python

Expand Down
6 changes: 3 additions & 3 deletions openapi_core/contrib/django/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""OpenAPI core contrib django module"""
from openapi_core.contrib.django.requests import DjangoOpenAPIRequestFactory
from openapi_core.contrib.django.responses import DjangoOpenAPIResponseFactory

# backward compatibility
DjangoOpenAPIRequest = DjangoOpenAPIRequestFactory.create
DjangoOpenAPIResponse = DjangoOpenAPIResponseFactory.create
DjangoOpenAPIRequest = DjangoOpenAPIRequestFactory().create
DjangoOpenAPIResponse = DjangoOpenAPIResponseFactory().create

__all__ = [
'DjangoOpenAPIRequestFactory', 'DjangoOpenAPIResponseFactory',
Expand Down
27 changes: 0 additions & 27 deletions openapi_core/contrib/django/backports.py

This file was deleted.

22 changes: 0 additions & 22 deletions openapi_core/contrib/django/compat.py

This file was deleted.

45 changes: 45 additions & 0 deletions openapi_core/contrib/django/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""OpenAPI core contrib django handlers module"""
from django.http import JsonResponse

from openapi_core.exceptions import MissingRequiredParameter
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import (
ServerNotFound, OperationNotFound, PathNotFound,
)
from openapi_core.validation.exceptions import InvalidSecurity


class DjangoOpenAPIErrorsHandler:

OPENAPI_ERROR_STATUS = {
MissingRequiredParameter: 400,
ServerNotFound: 400,
InvalidSecurity: 403,
OperationNotFound: 405,
PathNotFound: 404,
MediaTypeNotFound: 415,
}

@classmethod
def handle(cls, errors, req, resp=None):
data_errors = [
cls.format_openapi_error(err)
for err in errors
]
data = {
'errors': data_errors,
}
data_error_max = max(data_errors, key=cls.get_error_status)
return JsonResponse(data, status=data_error_max['status'])

@classmethod
def format_openapi_error(cls, error):
return {
'title': str(error),
'status': cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
'class': str(type(error)),
}

@classmethod
def get_error_status(cls, error):
return error['status']
60 changes: 60 additions & 0 deletions openapi_core/contrib/django/middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""OpenAPI core contrib django middlewares module"""
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler
from openapi_core.contrib.django.requests import DjangoOpenAPIRequestFactory
from openapi_core.contrib.django.responses import DjangoOpenAPIResponseFactory
from openapi_core.validation.processors import OpenAPIProcessor
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator


class DjangoOpenAPIMiddleware:

request_factory = DjangoOpenAPIRequestFactory()
response_factory = DjangoOpenAPIResponseFactory()
errors_handler = DjangoOpenAPIErrorsHandler()

def __init__(self, get_response):
self.get_response = get_response

if not hasattr(settings, 'OPENAPI_SPEC'):
raise ImproperlyConfigured("OPENAPI_SPEC not defined in settings")

request_validator = RequestValidator(settings.OPENAPI_SPEC)
response_validator = ResponseValidator(settings.OPENAPI_SPEC)
self.validation_processor = OpenAPIProcessor(
request_validator, response_validator)

def __call__(self, request):
openapi_request = self._get_openapi_request(request)
req_result = self.validation_processor.process_request(openapi_request)
if req_result.errors:
return self._handle_request_errors(req_result, request)

request.openapi = req_result

response = self.get_response(request)

openapi_response = self._get_openapi_response(response)
resp_result = self.validation_processor.process_response(
openapi_request, openapi_response)
if resp_result.errors:
return self._handle_response_errors(resp_result, request, response)

return response

def _handle_request_errors(self, request_result, req):
return self.errors_handler.handle(
request_result.errors, req, None)

def _handle_response_errors(self, response_result, req, resp):
return self.errors_handler.handle(
response_result.errors, req, resp)

def _get_openapi_request(self, request):
return self.request_factory.create(request)

def _get_openapi_response(self, response):
return self.response_factory.create(response)
68 changes: 41 additions & 27 deletions openapi_core/contrib/django/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

from werkzeug.datastructures import ImmutableMultiDict, Headers

from openapi_core.contrib.django.compat import (
get_request_headers, get_current_scheme_host,
)
from openapi_core.validation.request.datatypes import (
RequestParameters, OpenAPIRequest,
)
Expand All @@ -28,14 +25,40 @@ class DjangoOpenAPIRequestFactory:

path_regex = re.compile(PATH_PARAMETER_PATTERN)

@classmethod
def create(cls, request):
method = request.method.lower()
def create(self, request):
return OpenAPIRequest(
full_url_pattern=self._get_full_url_pattern(request),
method=self._get_method(request),
parameters=self._get_parameters(request),
body=self._get_body(request),
mimetype=self._get_mimetype(request),
)

def _get_parameters(self, request):
return RequestParameters(
path=self._get_path(request),
query=self._get_query(request),
header=self._get_header(request),
cookie=self._get_cookie(request),
)

def _get_path(self, request):
return request.resolver_match and request.resolver_match.kwargs or {}

def _get_query(self, request):
return ImmutableMultiDict(request.GET)

def _get_header(self, request):
return Headers(request.headers.items())

def _get_cookie(self, request):
return ImmutableMultiDict(dict(request.COOKIES))

def _get_full_url_pattern(self, request):
if request.resolver_match is None:
path_pattern = request.path
else:
route = cls.path_regex.sub(
route = self.path_regex.sub(
r'{\1}', request.resolver_match.route)
# Delete start and end marker to allow concatenation.
if route[:1] == "^":
Expand All @@ -44,23 +67,14 @@ def create(cls, request):
route = route[:-1]
path_pattern = '/' + route

request_headers = get_request_headers(request)
path = request.resolver_match and request.resolver_match.kwargs or {}
query = ImmutableMultiDict(request.GET)
header = Headers(request_headers.items())
cookie = ImmutableMultiDict(dict(request.COOKIES))
parameters = RequestParameters(
path=path,
query=query,
header=header,
cookie=cookie,
)
current_scheme_host = get_current_scheme_host(request)
full_url_pattern = urljoin(current_scheme_host, path_pattern)
return OpenAPIRequest(
full_url_pattern=full_url_pattern,
method=method,
parameters=parameters,
body=request.body,
mimetype=request.content_type,
)
current_scheme_host = request._current_scheme_host
return urljoin(current_scheme_host, path_pattern)

def _get_method(self, request):
return request.method.lower()

def _get_body(self, request):
return request.body

def _get_mimetype(self, request):
return request.content_type
27 changes: 17 additions & 10 deletions openapi_core/contrib/django/responses.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
"""OpenAPI core contrib django responses module"""
from werkzeug.datastructures import Headers

from openapi_core.contrib.django.compat import get_response_headers
from openapi_core.validation.response.datatypes import OpenAPIResponse


class DjangoOpenAPIResponseFactory:

@classmethod
def create(cls, response):
mimetype = response["Content-Type"]
headers = get_response_headers(response)
header = Headers(headers.items())
def create(self, response):
return OpenAPIResponse(
data=response.content,
status_code=response.status_code,
headers=header,
mimetype=mimetype,
data=self._get_data(response),
status_code=self._get_status_code(response),
headers=self._get_header(response),
mimetype=self._get_mimetype(response),
)

def _get_data(self, response):
return response.content

def _get_status_code(self, response):
return response.status_code

def _get_header(self, response):
return Headers(response.headers.items())

def _get_mimetype(self, response):
return response["Content-Type"]
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pytest-flake8
pytest-cov==2.5.1
falcon==3.0.1
flask
django==2.2.24
django==3.2.4
djangorestframework==3.11.2
requests==2.22.0
responses==0.10.12
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tests_require =
pytest>=5.0.0
pytest-flake8
pytest-cov
django>=3.0
falcon>=3.0
flask
responses
Expand All @@ -47,7 +48,7 @@ exclude =
tests

[options.extras_require]
django = django>=2.2
django = django>=3.0
falcon = falcon>=3.0
flask = flask
requests = requests
Expand Down
22 changes: 0 additions & 22 deletions tests/integration/contrib/django/conftest.py

This file was deleted.

Loading