Skip to content

Commit 98c0682

Browse files
authored
Merge pull request #358 from p1c2u/refactor/django2-support-drop
Django2 support drop
2 parents 972c975 + 7817a64 commit 98c0682

26 files changed

+696
-279
lines changed

docs/integrations.rst

+22-1
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

+3-3
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

-27
This file was deleted.

openapi_core/contrib/django/compat.py

-22
This file was deleted.
+45
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']
+60
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

+41-27
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
+17-10
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

+1-1
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

+2-1
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

-22
This file was deleted.

0 commit comments

Comments
 (0)