Skip to content

Commit 1f07fa2

Browse files
authored
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

Lines changed: 32 additions & 10 deletions
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)
Lines changed: 4 additions & 32 deletions
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)
Lines changed: 4 additions & 23 deletions
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"]
Lines changed: 7 additions & 0 deletions
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+
]
Lines changed: 46 additions & 0 deletions
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
Lines changed: 24 additions & 0 deletions
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)
Lines changed: 79 additions & 0 deletions
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)
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