Skip to content

Commit 9be404e

Browse files
authored
Merge pull request #166 from p1c2u/feature/wrappers-restructure
OpenAPI request/response factories introduction
2 parents 0604145 + 76d5081 commit 9be404e

File tree

20 files changed

+310
-304
lines changed

20 files changed

+310
-304
lines changed

README.rst

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ and unmarshal request data from validation result
6666

6767
.. code-block:: python
6868
69-
# get parameters dictionary with path, query, cookies and headers parameters
69+
# get parameters object with path, query, cookies and headers parameters
7070
validated_params = result.parameters
71+
# or specific parameters
72+
validated_path_params = result.parameters.path
7173
7274
# get body
7375
validated_body = result.body
@@ -81,27 +83,27 @@ or use shortcuts for simple validation
8183
validated_params = validate_parameters(spec, request)
8284
validated_body = validate_body(spec, request)
8385
84-
Request object should implement BaseOpenAPIRequest interface. You can use FlaskOpenAPIRequest a Flask/Werkzeug request wrapper implementation:
86+
Request object should be instance of OpenAPIRequest class. You can use FlaskOpenAPIRequest a Flask/Werkzeug request factory:
8587

8688
.. code-block:: python
8789
8890
from openapi_core.shortcuts import RequestValidator
89-
from openapi_core.wrappers.flask import FlaskOpenAPIRequest
91+
from openapi_core.contrib.flask import FlaskOpenAPIRequest
9092
9193
openapi_request = FlaskOpenAPIRequest(flask_request)
9294
validator = RequestValidator(spec)
9395
result = validator.validate(openapi_request)
9496
95-
or specify request wrapper class for shortcuts
97+
or simply specify request factory for shortcuts
9698

9799
.. code-block:: python
98100
99101
from openapi_core import validate_parameters, validate_body
100102
101103
validated_params = validate_parameters(
102-
spec, request, wrapper_class=FlaskOpenAPIRequest)
104+
spec, request, request_factory=FlaskOpenAPIRequest)
103105
validated_body = validate_body(
104-
spec, request, wrapper_class=FlaskOpenAPIRequest)
106+
spec, request, request_factory=FlaskOpenAPIRequest)
105107
106108
You can also validate responses
107109

@@ -136,25 +138,27 @@ or use shortcuts for simple validation
136138
137139
validated_data = validate_data(spec, request, response)
138140
139-
Response object should implement BaseOpenAPIResponse interface. You can use FlaskOpenAPIResponse a Flask/Werkzeug response wrapper implementation:
141+
Response object should be instance of OpenAPIResponse class. You can use FlaskOpenAPIResponse a Flask/Werkzeug response factory:
140142

141143
.. code-block:: python
142144
143145
from openapi_core.shortcuts import ResponseValidator
144-
from openapi_core.wrappers.flask import FlaskOpenAPIResponse
146+
from openapi_core.contrib.flask import FlaskOpenAPIResponse
145147
146148
openapi_response = FlaskOpenAPIResponse(flask_response)
147149
validator = ResponseValidator(spec)
148150
result = validator.validate(openapi_request, openapi_response)
149151
150-
or specify response wrapper class for shortcuts
152+
or simply specify response factory for shortcuts
151153

152154
.. code-block:: python
153155
154156
from openapi_core import validate_parameters, validate_body
155157
156158
validated_data = validate_data(
157-
spec, request, response, response_wrapper_class=FlaskOpenAPIResponse)
159+
spec, request, response,
160+
request_factory=FlaskOpenAPIRequest,
161+
response_factory=FlaskOpenAPIResponse)
158162
159163
Related projects
160164
================
File renamed without changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequestFactory
2+
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponseFactory
3+
4+
# backward compatibility
5+
FlaskOpenAPIRequest = FlaskOpenAPIRequestFactory.create
6+
FlaskOpenAPIResponse = FlaskOpenAPIResponseFactory.create
7+
8+
__all__ = [
9+
'FlaskOpenAPIRequestFactory', 'FlaskOpenAPIResponseFactory',
10+
'FlaskOpenAPIRequest', 'FlaskOpenAPIResponse',
11+
]
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""OpenAPI core contrib flask requests module"""
2+
import re
3+
4+
from openapi_core.validation.request.datatypes import (
5+
RequestParameters, OpenAPIRequest,
6+
)
7+
8+
# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
9+
PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>'
10+
11+
12+
class FlaskOpenAPIRequestFactory(object):
13+
14+
path_regex = re.compile(PATH_PARAMETER_PATTERN)
15+
16+
@classmethod
17+
def create(cls, request):
18+
method = request.method.lower()
19+
20+
if request.url_rule is None:
21+
path_pattern = request.path
22+
else:
23+
path_pattern = cls.path_regex.sub(r'{\1}', request.url_rule.rule)
24+
25+
parameters = RequestParameters(
26+
path=request.view_args,
27+
query=request.args,
28+
header=request.headers,
29+
cookie=request.cookies,
30+
)
31+
return OpenAPIRequest(
32+
host_url=request.host_url,
33+
path=request.path,
34+
path_pattern=path_pattern,
35+
method=method,
36+
parameters=parameters,
37+
body=request.data,
38+
mimetype=request.mimetype,
39+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""OpenAPI core contrib flask responses module"""
2+
import re
3+
4+
from openapi_core.validation.response.datatypes import OpenAPIResponse
5+
6+
7+
class FlaskOpenAPIResponseFactory(object):
8+
9+
@classmethod
10+
def create(cls, response):
11+
return OpenAPIResponse(
12+
data=response.data,
13+
status_code=response._status_code,
14+
mimetype=response.mimetype,
15+
)

openapi_core/shortcuts.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def create_spec(spec_dict, spec_url=''):
2020
return spec_factory.create(spec_dict, spec_url=spec_url)
2121

2222

23-
def validate_parameters(spec, request, wrapper_class=None):
24-
if wrapper_class is not None:
25-
request = wrapper_class(request)
23+
def validate_parameters(spec, request, request_factory=None):
24+
if request_factory is not None:
25+
request = request_factory(request)
2626

2727
validator = RequestValidator(spec)
2828
result = validator.validate(request)
@@ -38,9 +38,9 @@ def validate_parameters(spec, request, wrapper_class=None):
3838
return result.parameters
3939

4040

41-
def validate_body(spec, request, wrapper_class=None):
42-
if wrapper_class is not None:
43-
request = wrapper_class(request)
41+
def validate_body(spec, request, request_factory=None):
42+
if request_factory is not None:
43+
request = request_factory(request)
4444

4545
validator = RequestValidator(spec)
4646
result = validator.validate(request)
@@ -55,13 +55,13 @@ def validate_body(spec, request, wrapper_class=None):
5555

5656
def validate_data(
5757
spec, request, response,
58-
request_wrapper_class=None,
59-
response_wrapper_class=None):
60-
if request_wrapper_class is not None:
61-
request = request_wrapper_class(request)
58+
request_factory=None,
59+
response_factory=None):
60+
if request_factory is not None:
61+
request = request_factory(request)
6262

63-
if response_wrapper_class is not None:
64-
response = response_wrapper_class(response)
63+
if response_factory is not None:
64+
response = response_factory(response)
6565

6666
validator = ResponseValidator(spec)
6767
result = validator.validate(request, response)

openapi_core/testing/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""OpenAPI core testing module"""
2+
from openapi_core.testing.mock import MockRequestFactory, MockResponseFactory
3+
4+
# backward compatibility
5+
MockRequest = MockRequestFactory.create
6+
MockResponse = MockResponseFactory.create
7+
8+
__all__ = [
9+
'MockRequestFactory', 'MockResponseFactory', 'MockRequest', 'MockResponse',
10+
]

openapi_core/testing/mock.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""OpenAPI core testing mock module"""
2+
from werkzeug.datastructures import ImmutableMultiDict
3+
4+
from openapi_core.validation.request.datatypes import (
5+
RequestParameters, OpenAPIRequest,
6+
)
7+
from openapi_core.validation.response.datatypes import OpenAPIResponse
8+
9+
10+
class MockRequestFactory(object):
11+
12+
@classmethod
13+
def create(
14+
cls, host_url, method, path, path_pattern=None, args=None,
15+
view_args=None, headers=None, cookies=None, data=None,
16+
mimetype='application/json'):
17+
parameters = RequestParameters(
18+
path=view_args or {},
19+
query=ImmutableMultiDict(args or []),
20+
header=headers or {},
21+
cookie=cookies or {},
22+
)
23+
path_pattern = path_pattern or path
24+
method = method.lower()
25+
body = data or ''
26+
return OpenAPIRequest(
27+
host_url=host_url,
28+
path=path,
29+
path_pattern=path_pattern,
30+
method=method,
31+
parameters=parameters,
32+
body=body,
33+
mimetype=mimetype,
34+
)
35+
36+
37+
class MockResponseFactory(object):
38+
39+
@classmethod
40+
def create(cls, data, status_code=200, mimetype='application/json'):
41+
return OpenAPIResponse(
42+
data=data,
43+
status_code=status_code,
44+
mimetype=mimetype,
45+
)

openapi_core/validation/request/datatypes.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
11
"""OpenAPI core validation request datatypes module"""
22
import attr
3+
from werkzeug.datastructures import ImmutableMultiDict
34

45
from openapi_core.validation.datatypes import BaseValidationResult
56

67

8+
from six.moves.urllib.parse import urljoin
9+
10+
711
@attr.s
812
class RequestParameters(object):
913
path = attr.ib(factory=dict)
10-
query = attr.ib(factory=dict)
14+
query = attr.ib(factory=ImmutableMultiDict)
1115
header = attr.ib(factory=dict)
1216
cookie = attr.ib(factory=dict)
1317

1418
def __getitem__(self, location):
1519
return getattr(self, location)
1620

1721

22+
@attr.s
23+
class OpenAPIRequest(object):
24+
25+
host_url = attr.ib()
26+
path = attr.ib()
27+
path_pattern = attr.ib()
28+
method = attr.ib()
29+
30+
body = attr.ib()
31+
32+
mimetype = attr.ib()
33+
34+
parameters = attr.ib(factory=RequestParameters)
35+
36+
@property
37+
def full_url_pattern(self):
38+
return urljoin(self.host_url, self.path_pattern)
39+
40+
1841
@attr.s
1942
class RequestValidationResult(BaseValidationResult):
2043
body = attr.ib(default=None)

openapi_core/validation/request/validators.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def validate(self, request):
5050
def _get_parameters(self, request, params):
5151
errors = []
5252
seen = set()
53-
parameters = RequestParameters()
53+
locations = {}
5454
for param_name, param in params:
5555
if (param_name, param.location.value) in seen:
5656
# skip parameter already seen
@@ -79,9 +79,10 @@ def _get_parameters(self, request, params):
7979
except OpenAPIMappingError as exc:
8080
errors.append(exc)
8181
else:
82-
parameters[param.location.value][param_name] = unmarshalled
82+
locations.setdefault(param.location.value, {})
83+
locations[param.location.value][param_name] = unmarshalled
8384

84-
return parameters, errors
85+
return RequestParameters(**locations), errors
8586

8687
def _get_body(self, request, operation):
8788
errors = []

openapi_core/validation/response/datatypes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
from openapi_core.validation.datatypes import BaseValidationResult
55

66

7+
@attr.s
8+
class OpenAPIResponse(object):
9+
10+
data = attr.ib()
11+
status_code = attr.ib()
12+
13+
mimetype = attr.ib()
14+
15+
716
@attr.s
817
class ResponseValidationResult(BaseValidationResult):
918
data = attr.ib(default=None)

openapi_core/wrappers/base.py

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

0 commit comments

Comments
 (0)