Skip to content

Commit 7817a64

Browse files
committed
Django2 support drop
1 parent 972c975 commit 7817a64

File tree

26 files changed

+696
-279
lines changed

26 files changed

+696
-279
lines changed

docs/integrations.rst

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,29 @@ Django
1111
------
1212

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

15-
For Django 2.2 you can use DjangoOpenAPIRequest a Django request factory:
16+
Middleware
17+
~~~~~~~~~~
18+
19+
Django can be integrated by middleware. Add `DjangoOpenAPIMiddleware` to your `MIDDLEWARE` list and define `OPENAPI_SPEC`
20+
21+
.. code-block:: python
22+
23+
# settings.py
24+
from openapi_core import create_spec
25+
26+
MIDDLEWARE = [
27+
# ...
28+
'openapi_core.contrib.django.middlewares.DjangoOpenAPIMiddleware',
29+
]
30+
31+
OPENAPI_SPEC = create_spec(spec_dict)
32+
33+
Low level
34+
~~~~~~~~~
35+
36+
For Django you can use DjangoOpenAPIRequest a Django request factory:
1637

1738
.. code-block:: python
1839

openapi_core/contrib/django/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
"""OpenAPI core contrib django module"""
12
from openapi_core.contrib.django.requests import DjangoOpenAPIRequestFactory
23
from openapi_core.contrib.django.responses import DjangoOpenAPIResponseFactory
34

4-
# backward compatibility
5-
DjangoOpenAPIRequest = DjangoOpenAPIRequestFactory.create
6-
DjangoOpenAPIResponse = DjangoOpenAPIResponseFactory.create
5+
DjangoOpenAPIRequest = DjangoOpenAPIRequestFactory().create
6+
DjangoOpenAPIResponse = DjangoOpenAPIResponseFactory().create
77

88
__all__ = [
99
'DjangoOpenAPIRequestFactory', 'DjangoOpenAPIResponseFactory',

openapi_core/contrib/django/backports.py

Lines changed: 0 additions & 27 deletions
This file was deleted.

openapi_core/contrib/django/compat.py

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""OpenAPI core contrib django handlers module"""
2+
from django.http import JsonResponse
3+
4+
from openapi_core.exceptions import MissingRequiredParameter
5+
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
6+
from openapi_core.templating.paths.exceptions import (
7+
ServerNotFound, OperationNotFound, PathNotFound,
8+
)
9+
from openapi_core.validation.exceptions import InvalidSecurity
10+
11+
12+
class DjangoOpenAPIErrorsHandler:
13+
14+
OPENAPI_ERROR_STATUS = {
15+
MissingRequiredParameter: 400,
16+
ServerNotFound: 400,
17+
InvalidSecurity: 403,
18+
OperationNotFound: 405,
19+
PathNotFound: 404,
20+
MediaTypeNotFound: 415,
21+
}
22+
23+
@classmethod
24+
def handle(cls, errors, req, resp=None):
25+
data_errors = [
26+
cls.format_openapi_error(err)
27+
for err in errors
28+
]
29+
data = {
30+
'errors': data_errors,
31+
}
32+
data_error_max = max(data_errors, key=cls.get_error_status)
33+
return JsonResponse(data, status=data_error_max['status'])
34+
35+
@classmethod
36+
def format_openapi_error(cls, error):
37+
return {
38+
'title': str(error),
39+
'status': cls.OPENAPI_ERROR_STATUS.get(error.__class__, 400),
40+
'class': str(type(error)),
41+
}
42+
43+
@classmethod
44+
def get_error_status(cls, error):
45+
return error['status']
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""OpenAPI core contrib django middlewares module"""
2+
from django.conf import settings
3+
from django.core.exceptions import ImproperlyConfigured
4+
5+
from openapi_core.contrib.django.handlers import DjangoOpenAPIErrorsHandler
6+
from openapi_core.contrib.django.requests import DjangoOpenAPIRequestFactory
7+
from openapi_core.contrib.django.responses import DjangoOpenAPIResponseFactory
8+
from openapi_core.validation.processors import OpenAPIProcessor
9+
from openapi_core.validation.request.validators import RequestValidator
10+
from openapi_core.validation.response.validators import ResponseValidator
11+
12+
13+
class DjangoOpenAPIMiddleware:
14+
15+
request_factory = DjangoOpenAPIRequestFactory()
16+
response_factory = DjangoOpenAPIResponseFactory()
17+
errors_handler = DjangoOpenAPIErrorsHandler()
18+
19+
def __init__(self, get_response):
20+
self.get_response = get_response
21+
22+
if not hasattr(settings, 'OPENAPI_SPEC'):
23+
raise ImproperlyConfigured("OPENAPI_SPEC not defined in settings")
24+
25+
request_validator = RequestValidator(settings.OPENAPI_SPEC)
26+
response_validator = ResponseValidator(settings.OPENAPI_SPEC)
27+
self.validation_processor = OpenAPIProcessor(
28+
request_validator, response_validator)
29+
30+
def __call__(self, request):
31+
openapi_request = self._get_openapi_request(request)
32+
req_result = self.validation_processor.process_request(openapi_request)
33+
if req_result.errors:
34+
return self._handle_request_errors(req_result, request)
35+
36+
request.openapi = req_result
37+
38+
response = self.get_response(request)
39+
40+
openapi_response = self._get_openapi_response(response)
41+
resp_result = self.validation_processor.process_response(
42+
openapi_request, openapi_response)
43+
if resp_result.errors:
44+
return self._handle_response_errors(resp_result, request, response)
45+
46+
return response
47+
48+
def _handle_request_errors(self, request_result, req):
49+
return self.errors_handler.handle(
50+
request_result.errors, req, None)
51+
52+
def _handle_response_errors(self, response_result, req, resp):
53+
return self.errors_handler.handle(
54+
response_result.errors, req, resp)
55+
56+
def _get_openapi_request(self, request):
57+
return self.request_factory.create(request)
58+
59+
def _get_openapi_response(self, response):
60+
return self.response_factory.create(response)

openapi_core/contrib/django/requests.py

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
from werkzeug.datastructures import ImmutableMultiDict, Headers
66

7-
from openapi_core.contrib.django.compat import (
8-
get_request_headers, get_current_scheme_host,
9-
)
107
from openapi_core.validation.request.datatypes import (
118
RequestParameters, OpenAPIRequest,
129
)
@@ -28,14 +25,40 @@ class DjangoOpenAPIRequestFactory:
2825

2926
path_regex = re.compile(PATH_PARAMETER_PATTERN)
3027

31-
@classmethod
32-
def create(cls, request):
33-
method = request.method.lower()
28+
def create(self, request):
29+
return OpenAPIRequest(
30+
full_url_pattern=self._get_full_url_pattern(request),
31+
method=self._get_method(request),
32+
parameters=self._get_parameters(request),
33+
body=self._get_body(request),
34+
mimetype=self._get_mimetype(request),
35+
)
36+
37+
def _get_parameters(self, request):
38+
return RequestParameters(
39+
path=self._get_path(request),
40+
query=self._get_query(request),
41+
header=self._get_header(request),
42+
cookie=self._get_cookie(request),
43+
)
44+
45+
def _get_path(self, request):
46+
return request.resolver_match and request.resolver_match.kwargs or {}
47+
48+
def _get_query(self, request):
49+
return ImmutableMultiDict(request.GET)
50+
51+
def _get_header(self, request):
52+
return Headers(request.headers.items())
3453

54+
def _get_cookie(self, request):
55+
return ImmutableMultiDict(dict(request.COOKIES))
56+
57+
def _get_full_url_pattern(self, request):
3558
if request.resolver_match is None:
3659
path_pattern = request.path
3760
else:
38-
route = cls.path_regex.sub(
61+
route = self.path_regex.sub(
3962
r'{\1}', request.resolver_match.route)
4063
# Delete start and end marker to allow concatenation.
4164
if route[:1] == "^":
@@ -44,23 +67,14 @@ def create(cls, request):
4467
route = route[:-1]
4568
path_pattern = '/' + route
4669

47-
request_headers = get_request_headers(request)
48-
path = request.resolver_match and request.resolver_match.kwargs or {}
49-
query = ImmutableMultiDict(request.GET)
50-
header = Headers(request_headers.items())
51-
cookie = ImmutableMultiDict(dict(request.COOKIES))
52-
parameters = RequestParameters(
53-
path=path,
54-
query=query,
55-
header=header,
56-
cookie=cookie,
57-
)
58-
current_scheme_host = get_current_scheme_host(request)
59-
full_url_pattern = urljoin(current_scheme_host, path_pattern)
60-
return OpenAPIRequest(
61-
full_url_pattern=full_url_pattern,
62-
method=method,
63-
parameters=parameters,
64-
body=request.body,
65-
mimetype=request.content_type,
66-
)
70+
current_scheme_host = request._current_scheme_host
71+
return urljoin(current_scheme_host, path_pattern)
72+
73+
def _get_method(self, request):
74+
return request.method.lower()
75+
76+
def _get_body(self, request):
77+
return request.body
78+
79+
def _get_mimetype(self, request):
80+
return request.content_type
Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
"""OpenAPI core contrib django responses module"""
22
from werkzeug.datastructures import Headers
33

4-
from openapi_core.contrib.django.compat import get_response_headers
54
from openapi_core.validation.response.datatypes import OpenAPIResponse
65

76

87
class DjangoOpenAPIResponseFactory:
98

10-
@classmethod
11-
def create(cls, response):
12-
mimetype = response["Content-Type"]
13-
headers = get_response_headers(response)
14-
header = Headers(headers.items())
9+
def create(self, response):
1510
return OpenAPIResponse(
16-
data=response.content,
17-
status_code=response.status_code,
18-
headers=header,
19-
mimetype=mimetype,
11+
data=self._get_data(response),
12+
status_code=self._get_status_code(response),
13+
headers=self._get_header(response),
14+
mimetype=self._get_mimetype(response),
2015
)
16+
17+
def _get_data(self, response):
18+
return response.content
19+
20+
def _get_status_code(self, response):
21+
return response.status_code
22+
23+
def _get_header(self, response):
24+
return Headers(response.headers.items())
25+
26+
def _get_mimetype(self, response):
27+
return response["Content-Type"]

requirements_dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pytest-flake8
33
pytest-cov==2.5.1
44
falcon==3.0.1
55
flask
6-
django==2.2.24
6+
django==3.2.4
77
djangorestframework==3.11.2
88
requests==2.22.0
99
responses==0.10.12

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ tests_require =
3737
pytest>=5.0.0
3838
pytest-flake8
3939
pytest-cov
40+
django>=3.0
4041
falcon>=3.0
4142
flask
4243
responses
@@ -47,7 +48,7 @@ exclude =
4748
tests
4849

4950
[options.extras_require]
50-
django = django>=2.2
51+
django = django>=3.0
5152
falcon = falcon>=3.0
5253
flask = flask
5354
requests = requests

tests/integration/contrib/django/conftest.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

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