Skip to content

Commit fd99117

Browse files
authored
Merge pull request #167 from p1c2u/feature/django-support
Django OpenAPI request/response factories
2 parents 9be404e + eb25305 commit fd99117

File tree

10 files changed

+415
-25
lines changed

10 files changed

+415
-25
lines changed

README.rst

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
************
12
openapi-core
23
************
34

@@ -15,13 +16,13 @@ openapi-core
1516
:target: https://pypi.python.org/pypi/openapi-core
1617

1718
About
18-
=====
19+
#####
1920

2021
Openapi-core is a Python library that adds client-side and server-side support
2122
for the `OpenAPI Specification v3.0.0 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md>`__.
2223

2324
Installation
24-
============
25+
############
2526

2627
Recommended way (via pip):
2728

@@ -37,7 +38,7 @@ Alternatively you can download the code and install from the repository:
3738
3839
3940
Usage
40-
=====
41+
#####
4142

4243
Firstly create your specification:
4344

@@ -47,6 +48,9 @@ Firstly create your specification:
4748
4849
spec = create_spec(spec_dict)
4950
51+
Request
52+
*******
53+
5054
Now you can use it to validate requests
5155

5256
.. code-block:: python
@@ -83,14 +87,61 @@ or use shortcuts for simple validation
8387
validated_params = validate_parameters(spec, request)
8488
validated_body = validate_body(spec, request)
8589
86-
Request object should be instance of OpenAPIRequest class. You can use FlaskOpenAPIRequest a Flask/Werkzeug request factory:
90+
Request object should be instance of OpenAPIRequest class (See `Integrations`_).
91+
92+
Response
93+
********
94+
95+
You can also validate responses
96+
97+
.. code-block:: python
98+
99+
from openapi_core.shortcuts import ResponseValidator
100+
101+
validator = ResponseValidator(spec)
102+
result = validator.validate(request, response)
103+
104+
# raise errors if response invalid
105+
result.raise_for_errors()
106+
107+
# get list of errors
108+
errors = result.errors
109+
110+
and unmarshal response data from validation result
111+
112+
.. code-block:: python
113+
114+
# get headers
115+
validated_headers = result.headers
116+
117+
# get data
118+
validated_data = result.data
119+
120+
or use shortcuts for simple validation
121+
122+
.. code-block:: python
123+
124+
from openapi_core import validate_data
125+
126+
validated_data = validate_data(spec, request, response)
127+
128+
Response object should be instance of OpenAPIResponse class (See `Integrations`_).
129+
130+
131+
Integrations
132+
############
133+
134+
Django
135+
******
136+
137+
For Django 2.2 you can use DjangoOpenAPIRequest a Django request factory:
87138

88139
.. code-block:: python
89140
90141
from openapi_core.shortcuts import RequestValidator
91-
from openapi_core.contrib.flask import FlaskOpenAPIRequest
142+
from openapi_core.contrib.django import DjangoOpenAPIRequest
92143
93-
openapi_request = FlaskOpenAPIRequest(flask_request)
144+
openapi_request = DjangoOpenAPIRequest(django_request)
94145
validator = RequestValidator(spec)
95146
result = validator.validate(openapi_request)
96147
@@ -101,44 +152,58 @@ or simply specify request factory for shortcuts
101152
from openapi_core import validate_parameters, validate_body
102153
103154
validated_params = validate_parameters(
104-
spec, request, request_factory=FlaskOpenAPIRequest)
155+
spec, request, request_factory=DjangoOpenAPIRequest)
105156
validated_body = validate_body(
106-
spec, request, request_factory=FlaskOpenAPIRequest)
157+
spec, request, request_factory=DjangoOpenAPIRequest)
107158
108-
You can also validate responses
159+
You can use DjangoOpenAPIResponse as a Django response factory:
109160

110161
.. code-block:: python
111162
112163
from openapi_core.shortcuts import ResponseValidator
164+
from openapi_core.contrib.django import DjangoOpenAPIResponse
113165
166+
openapi_response = DjangoOpenAPIResponse(django_response)
114167
validator = ResponseValidator(spec)
115-
result = validator.validate(request, response)
168+
result = validator.validate(openapi_request, openapi_response)
116169
117-
# raise errors if response invalid
118-
result.raise_for_errors()
170+
or simply specify response factory for shortcuts
119171

120-
# get list of errors
121-
errors = result.errors
172+
.. code-block:: python
122173
123-
and unmarshal response data from validation result
174+
from openapi_core import validate_parameters, validate_body
175+
176+
validated_data = validate_data(
177+
spec, request, response,
178+
request_factory=DjangoOpenAPIRequest,
179+
response_factory=DjangoOpenAPIResponse)
180+
181+
Flask
182+
*****
183+
184+
You can use FlaskOpenAPIRequest a Flask/Werkzeug request factory:
124185

125186
.. code-block:: python
126187
127-
# get headers
128-
validated_headers = result.headers
188+
from openapi_core.shortcuts import RequestValidator
189+
from openapi_core.contrib.flask import FlaskOpenAPIRequest
129190
130-
# get data
131-
validated_data = result.data
191+
openapi_request = FlaskOpenAPIRequest(flask_request)
192+
validator = RequestValidator(spec)
193+
result = validator.validate(openapi_request)
132194
133-
or use shortcuts for simple validation
195+
or simply specify request factory for shortcuts
134196

135197
.. code-block:: python
136198
137-
from openapi_core import validate_data
199+
from openapi_core import validate_parameters, validate_body
138200
139-
validated_data = validate_data(spec, request, response)
201+
validated_params = validate_parameters(
202+
spec, request, request_factory=FlaskOpenAPIRequest)
203+
validated_body = validate_body(
204+
spec, request, request_factory=FlaskOpenAPIRequest)
140205
141-
Response object should be instance of OpenAPIResponse class. You can use FlaskOpenAPIResponse a Flask/Werkzeug response factory:
206+
You can use FlaskOpenAPIResponse as a Flask/Werkzeug response factory:
142207

143208
.. code-block:: python
144209
@@ -160,7 +225,12 @@ or simply specify response factory for shortcuts
160225
request_factory=FlaskOpenAPIRequest,
161226
response_factory=FlaskOpenAPIResponse)
162227
228+
Pyramid
229+
*******
230+
231+
See `pyramid_openapi3 <https://github.com/niteoweb/pyramid_openapi3>`_ project.
232+
163233
Related projects
164-
================
234+
################
165235
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__
166236
* `pyramid_openapi3 <https://github.com/niteoweb/pyramid_openapi3>`__
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from openapi_core.contrib.django.requests import DjangoOpenAPIRequestFactory
2+
from openapi_core.contrib.django.responses import DjangoOpenAPIResponseFactory
3+
4+
# backward compatibility
5+
DjangoOpenAPIRequest = DjangoOpenAPIRequestFactory.create
6+
DjangoOpenAPIResponse = DjangoOpenAPIResponseFactory.create
7+
8+
__all__ = [
9+
'DjangoOpenAPIRequestFactory', 'DjangoOpenAPIResponseFactory',
10+
'DjangoOpenAPIRequest', 'DjangoOpenAPIResponse',
11+
]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""OpenAPI core contrib django requests module"""
2+
import re
3+
4+
from openapi_core.validation.request.datatypes import (
5+
RequestParameters, OpenAPIRequest,
6+
)
7+
8+
# https://docs.djangoproject.com/en/2.2/topics/http/urls/
9+
#
10+
# Currently unsupported are :
11+
# - nested arguments, e.g.: ^comments/(?:page-(?P<page_number>\d+)/)?$
12+
# - unnamed regex groups, e.g.: ^articles/([0-9]{4})/$
13+
# - multiple named parameters between a single pair of slashes
14+
# e.g.: <page_slug>-<page_id>/edit/
15+
#
16+
# The regex matches everything, except a "/" until "<". Than only the name
17+
# is exported, after which it matches ">" and everything until a "/".
18+
PATH_PARAMETER_PATTERN = r'(?:[^\/]*?)<(?:(?:.*?:))*?(\w+)>(?:[^\/]*)'
19+
20+
21+
class DjangoOpenAPIRequestFactory(object):
22+
23+
path_regex = re.compile(PATH_PARAMETER_PATTERN)
24+
25+
@classmethod
26+
def create(cls, request):
27+
method = request.method.lower()
28+
29+
if request.resolver_match is None:
30+
path_pattern = request.path
31+
else:
32+
route = cls.path_regex.sub(
33+
r'{\1}', request.resolver_match.route)
34+
path_pattern = '/' + route
35+
36+
path = request.resolver_match and request.resolver_match.kwargs or {}
37+
parameters = RequestParameters(
38+
path=path,
39+
query=request.GET,
40+
header=request.headers,
41+
cookie=request.COOKIES,
42+
)
43+
return OpenAPIRequest(
44+
host_url=request._current_scheme_host,
45+
path=request.path,
46+
method=method,
47+
path_pattern=path_pattern,
48+
parameters=parameters,
49+
body=request.body,
50+
mimetype=request.content_type,
51+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""OpenAPI core contrib django responses module"""
2+
from openapi_core.validation.response.datatypes import OpenAPIResponse
3+
4+
5+
class DjangoOpenAPIResponseFactory(object):
6+
7+
@classmethod
8+
def create(cls, response):
9+
mimetype = response["Content-Type"]
10+
return OpenAPIResponse(
11+
data=response.content,
12+
status_code=response.status_code,
13+
mimetype=mimetype,
14+
)

openapi_core/validation/request/datatypes.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ def __getitem__(self, location):
2121

2222
@attr.s
2323
class OpenAPIRequest(object):
24+
"""OpenAPI request dataclass.
25+
26+
Attributes:
27+
path
28+
Requested path as string.
29+
path_pattern
30+
The matched url pattern.
31+
parameters
32+
A RequestParameters object.
33+
body
34+
The request body, as string.
35+
mimetype
36+
Like content type, but without parameters (eg, without charset,
37+
type etc.) and always lowercase.
38+
For example if the content type is "text/HTML; charset=utf-8"
39+
the mimetype would be "text/html".
40+
"""
2441

2542
host_url = attr.ib()
2643
path = attr.ib()

openapi_core/validation/response/datatypes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
@attr.s
88
class OpenAPIResponse(object):
9+
"""OpenAPI request dataclass.
10+
11+
Attributes:
12+
data
13+
The response body, as string.
14+
status_code
15+
The status code as integer.
16+
mimetype
17+
Lowercase content type without charset.
18+
"""
919

1020
data = attr.ib()
1121
status_code = attr.ib()

requirements_dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ mock==2.0.0
22
pytest==3.5.0
33
pytest-flake8
44
pytest-cov==2.5.1
5-
flask
5+
flask
6+
django==2.2.6; python_version>="3.0"

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ exclude =
4343
tests
4444

4545
[options.extras_require]
46+
django = django>=2.2; python_version>="3.0"
4647
flask = werkzeug
4748

4849
[tool:pytest]

0 commit comments

Comments
 (0)