Skip to content

Commit 1f07fa2

Browse files
authoredSep 28, 2022
Merge pull request #426 from p1c2u/feature/separate-werkzeug-contrib
Separate werkzeug contrib
2 parents 14f0a5b + e1fedbd commit 1f07fa2

File tree

7 files changed

+196
-65
lines changed

7 files changed

+196
-65
lines changed
 

‎docs/integrations.rst

+32-10
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ In Flask, all unmarshalled request data are provided as Flask request object's `
192192
Low level
193193
~~~~~~~~~
194194

195-
You can use `FlaskOpenAPIRequest` as a Flask/Werkzeug request factory:
195+
You can use `FlaskOpenAPIRequest` as a Flask request factory:
196196

197197
.. code-block:: python
198198
@@ -202,15 +202,7 @@ You can use `FlaskOpenAPIRequest` as a Flask/Werkzeug request factory:
202202
openapi_request = FlaskOpenAPIRequest(flask_request)
203203
result = openapi_request_validator.validate(spec, openapi_request)
204204
205-
You can use `FlaskOpenAPIResponse` as a Flask/Werkzeug response factory:
206-
207-
.. code-block:: python
208-
209-
from openapi_core.validation.response import openapi_response_validator
210-
from openapi_core.contrib.flask import FlaskOpenAPIResponse
211-
212-
openapi_response = FlaskOpenAPIResponse(flask_response)
213-
result = openapi_response_validator.validate(spec, openapi_request, openapi_response)
205+
For response factory see `Werkzeug`_ integration.
214206

215207

216208
Pyramid
@@ -247,7 +239,37 @@ You can use `RequestsOpenAPIResponse` as a Requests response factory:
247239
openapi_response = RequestsOpenAPIResponse(requests_response)
248240
result = openapi_respose_validator.validate(spec, openapi_request, openapi_response)
249241
242+
250243
Tornado
251244
-------
252245

253246
See `tornado-openapi3 <https://github.com/correl/tornado-openapi3>`_ project.
247+
248+
249+
Werkzeug
250+
--------
251+
252+
This section describes integration with `Werkzeug <https://werkzeug.palletsprojects.com>`__ a WSGI web application library.
253+
254+
Low level
255+
~~~~~~~~~
256+
257+
You can use `WerkzeugOpenAPIRequest` as a Werkzeug request factory:
258+
259+
.. code-block:: python
260+
261+
from openapi_core.validation.request import openapi_request_validator
262+
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest
263+
264+
openapi_request = WerkzeugOpenAPIRequest(werkzeug_request)
265+
result = openapi_request_validator.validate(spec, openapi_request)
266+
267+
You can use `WerkzeugOpenAPIResponse` as a Werkzeug response factory:
268+
269+
.. code-block:: python
270+
271+
from openapi_core.validation.response import openapi_response_validator
272+
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse
273+
274+
openapi_response = WerkzeugOpenAPIResponse(werkzeug_response)
275+
result = openapi_response_validator.validate(spec, openapi_request, openapi_response)
+4-32
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
"""OpenAPI core contrib flask requests module"""
2-
import re
3-
from typing import Optional
4-
52
from flask.wrappers import Request
63
from werkzeug.datastructures import Headers
74
from werkzeug.datastructures import ImmutableMultiDict
85

6+
from openapi_core.contrib.werkzeug.requests import WerkzeugOpenAPIRequest
97
from openapi_core.validation.request.datatypes import RequestParameters
108

11-
# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
12-
PATH_PARAMETER_PATTERN = r"<(?:(?:string|int|float|path|uuid):)?(\w+)>"
13-
14-
15-
class FlaskOpenAPIRequest:
16-
17-
path_regex = re.compile(PATH_PARAMETER_PATTERN)
189

10+
class FlaskOpenAPIRequest(WerkzeugOpenAPIRequest):
1911
def __init__(self, request: Request):
20-
self.request = request
12+
self.request: Request = request
2113

2214
self.parameters = RequestParameters(
2315
path=self.request.view_args or {},
@@ -26,29 +18,9 @@ def __init__(self, request: Request):
2618
cookie=self.request.cookies,
2719
)
2820

29-
@property
30-
def host_url(self) -> str:
31-
return self.request.host_url
32-
33-
@property
34-
def path(self) -> str:
35-
return self.request.path
36-
3721
@property
3822
def path_pattern(self) -> str:
3923
if self.request.url_rule is None:
4024
return self.request.path
41-
else:
42-
return self.path_regex.sub(r"{\1}", self.request.url_rule.rule)
43-
44-
@property
45-
def method(self) -> str:
46-
return self.request.method.lower()
4725

48-
@property
49-
def body(self) -> Optional[str]:
50-
return self.request.get_data(as_text=True)
51-
52-
@property
53-
def mimetype(self) -> str:
54-
return self.request.mimetype
26+
return self.path_regex.sub(r"{\1}", self.request.url_rule.rule)
+4-23
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
1-
"""OpenAPI core contrib flask responses module"""
2-
from flask.wrappers import Response
3-
from werkzeug.datastructures import Headers
1+
from openapi_core.contrib.werkzeug.responses import (
2+
WerkzeugOpenAPIResponse as FlaskOpenAPIResponse,
3+
)
44

5-
6-
class FlaskOpenAPIResponse:
7-
def __init__(self, response: Response):
8-
self.response = response
9-
10-
@property
11-
def data(self) -> str:
12-
return self.response.get_data(as_text=True)
13-
14-
@property
15-
def status_code(self) -> int:
16-
return self.response._status_code
17-
18-
@property
19-
def mimetype(self) -> str:
20-
return str(self.response.mimetype)
21-
22-
@property
23-
def headers(self) -> Headers:
24-
return Headers(self.response.headers)
5+
__all__ = ["FlaskOpenAPIResponse"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from openapi_core.contrib.werkzeug.requests import WerkzeugOpenAPIRequest
2+
from openapi_core.contrib.werkzeug.responses import WerkzeugOpenAPIResponse
3+
4+
__all__ = [
5+
"WerkzeugOpenAPIRequest",
6+
"WerkzeugOpenAPIResponse",
7+
]
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""OpenAPI core contrib werkzeug requests module"""
2+
import re
3+
from typing import Optional
4+
5+
from werkzeug.datastructures import Headers
6+
from werkzeug.datastructures import ImmutableMultiDict
7+
from werkzeug.wrappers import Request
8+
9+
from openapi_core.validation.request.datatypes import RequestParameters
10+
11+
# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
12+
PATH_PARAMETER_PATTERN = r"<(?:(?:string|int|float|path|uuid):)?(\w+)>"
13+
14+
15+
class WerkzeugOpenAPIRequest:
16+
17+
path_regex = re.compile(PATH_PARAMETER_PATTERN)
18+
19+
def __init__(self, request: Request):
20+
self.request = request
21+
22+
self.parameters = RequestParameters(
23+
query=ImmutableMultiDict(self.request.args),
24+
header=Headers(self.request.headers),
25+
cookie=self.request.cookies,
26+
)
27+
28+
@property
29+
def host_url(self) -> str:
30+
return self.request.host_url
31+
32+
@property
33+
def path(self) -> str:
34+
return self.request.path
35+
36+
@property
37+
def method(self) -> str:
38+
return self.request.method.lower()
39+
40+
@property
41+
def body(self) -> Optional[str]:
42+
return self.request.get_data(as_text=True)
43+
44+
@property
45+
def mimetype(self) -> str:
46+
return self.request.mimetype
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""OpenAPI core contrib werkzeug responses module"""
2+
from werkzeug.datastructures import Headers
3+
from werkzeug.wrappers import Response
4+
5+
6+
class WerkzeugOpenAPIResponse:
7+
def __init__(self, response: Response):
8+
self.response = response
9+
10+
@property
11+
def data(self) -> str:
12+
return self.response.get_data(as_text=True)
13+
14+
@property
15+
def status_code(self) -> int:
16+
return self.response._status_code
17+
18+
@property
19+
def mimetype(self) -> str:
20+
return str(self.response.mimetype)
21+
22+
@property
23+
def headers(self) -> Headers:
24+
return Headers(self.response.headers)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from json import dumps
2+
3+
import pytest
4+
import requests
5+
import responses
6+
from werkzeug.test import Client
7+
from werkzeug.wrappers import Request
8+
from werkzeug.wrappers import Response
9+
10+
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest
11+
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIResponse
12+
from openapi_core.validation.request import openapi_request_validator
13+
from openapi_core.validation.response import openapi_response_validator
14+
15+
16+
class TestWerkzeugOpenAPIValidation:
17+
@pytest.fixture
18+
def spec(self, factory):
19+
specfile = "contrib/requests/data/v3.0/requests_factory.yaml"
20+
return factory.spec_from_file(specfile)
21+
22+
@pytest.fixture
23+
def app(self):
24+
def test_app(environ, start_response):
25+
req = Request(environ, populate_request=False)
26+
if req.args.get("q") == "string":
27+
response = Response(
28+
dumps({"data": "data"}),
29+
headers={"X-Rate-Limit": "12"},
30+
mimetype="application/json",
31+
status=200,
32+
)
33+
else:
34+
response = Response("Not Found", status=404)
35+
return response(environ, start_response)
36+
37+
return test_app
38+
39+
@pytest.fixture
40+
def client(self, app):
41+
return Client(app)
42+
43+
def test_request_validator_path_pattern(self, client, spec):
44+
query_string = {
45+
"q": "string",
46+
}
47+
headers = {"content-type": "application/json"}
48+
data = {"param1": 1}
49+
response = client.post(
50+
"/browse/12/",
51+
base_url="http://localhost",
52+
query_string=query_string,
53+
json=data,
54+
headers=headers,
55+
)
56+
openapi_request = WerkzeugOpenAPIRequest(response.request)
57+
result = openapi_request_validator.validate(spec, openapi_request)
58+
assert not result.errors
59+
60+
@responses.activate
61+
def test_response_validator_path_pattern(self, client, spec):
62+
query_string = {
63+
"q": "string",
64+
}
65+
headers = {"content-type": "application/json"}
66+
data = {"param1": 1}
67+
response = client.post(
68+
"/browse/12/",
69+
base_url="http://localhost",
70+
query_string=query_string,
71+
json=data,
72+
headers=headers,
73+
)
74+
openapi_request = WerkzeugOpenAPIRequest(response.request)
75+
openapi_response = WerkzeugOpenAPIResponse(response)
76+
result = openapi_response_validator.validate(
77+
spec, openapi_request, openapi_response
78+
)
79+
assert not result.errors

0 commit comments

Comments
 (0)
Please sign in to comment.