Skip to content

Commit 09bff35

Browse files
authored
Merge pull request #185 from p1c2u/feature/flask-openapi-request-parameters
Flask OpenAPI request parameters
2 parents 17b7956 + 5059f05 commit 09bff35

File tree

5 files changed

+69
-35
lines changed

5 files changed

+69
-35
lines changed

README.rst

+15
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@ As an alternative to the decorator-based integration, Flask method based views c
218218
219219
app.add_url_rule('/home', view_func=MyView.as_view('home', spec))
220220
221+
Request parameters
222+
==================
223+
224+
In Flask, all unmarshalled request data are provided as Flask request object's openapi.parameters attribute
225+
226+
.. code-block:: python
227+
228+
from flask.globals import request
229+
230+
@app.route('/browse/<id>/')
231+
@openapi
232+
def home():
233+
browse_id = request.openapi.parameters.path['id']
234+
page = request.openapi.parameters.query.get('page', 1)
235+
221236
Low level
222237
=========
223238

openapi_core/contrib/flask/decorators.py

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ def __init__(
2525
request_provider, openapi_errors_handler,
2626
)
2727

28+
def _handle_request_view(self, request_result, view, *args, **kwargs):
29+
request = self._get_request(*args, **kwargs)
30+
request.openapi = request_result
31+
return super(FlaskOpenAPIViewDecorator, self)._handle_request_view(
32+
request_result, view, *args, **kwargs)
33+
2834
@classmethod
2935
def from_spec(
3036
cls,

openapi_core/validation/decorators.py

+17-9
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,30 @@ def __call__(self, view):
2727
def decorated(*args, **kwargs):
2828
request = self._get_request(*args, **kwargs)
2929
openapi_request = self._get_openapi_request(request)
30-
errors = self.process_request(openapi_request)
31-
if errors:
32-
return self._handle_openapi_errors(errors)
33-
response = view(*args, **kwargs)
30+
request_result = self.process_request(openapi_request)
31+
if request_result.errors:
32+
return self._handle_request_errors(request_result)
33+
response = self._handle_request_view(
34+
request_result, view, *args, **kwargs)
3435
openapi_response = self._get_openapi_response(response)
35-
errors = self.process_response(openapi_request, openapi_response)
36-
if errors:
37-
return self._handle_openapi_errors(errors)
36+
response_result = self.process_response(
37+
openapi_request, openapi_response)
38+
if response_result.errors:
39+
return self._handle_response_errors(response_result)
3840
return response
3941
return decorated
4042

4143
def _get_request(self, *args, **kwargs):
4244
return self.request_provider.provide(*args, **kwargs)
4345

44-
def _handle_openapi_errors(self, errors):
45-
return self.openapi_errors_handler.handle(errors)
46+
def _handle_request_view(self, request_result, view, *args, **kwargs):
47+
return view(*args, **kwargs)
48+
49+
def _handle_request_errors(self, request_result):
50+
return self.openapi_errors_handler.handle(request_result.errors)
51+
52+
def _handle_response_errors(self, response_result):
53+
return self.openapi_errors_handler.handle(response_result.errors)
4654

4755
def _get_openapi_request(self, request):
4856
return self.request_factory.create(request)

openapi_core/validation/processors.py

+2-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
"""OpenAPI core validation processors module"""
2-
from openapi_core.schema.servers.exceptions import InvalidServer
3-
from openapi_core.schema.exceptions import OpenAPIMappingError
42

53

64
class OpenAPIProcessor(object):
@@ -10,22 +8,7 @@ def __init__(self, request_validator, response_validator):
108
self.response_validator = response_validator
119

1210
def process_request(self, request):
13-
request_result = self.request_validator.validate(request)
14-
try:
15-
request_result.raise_for_errors()
16-
# return instantly on server error
17-
except InvalidServer as exc:
18-
return [exc, ]
19-
except OpenAPIMappingError:
20-
return request_result.errors
21-
else:
22-
return
11+
return self.request_validator.validate(request)
2312

2413
def process_response(self, request, response):
25-
response_result = self.response_validator.validate(request, response)
26-
try:
27-
response_result.raise_for_errors()
28-
except OpenAPIMappingError:
29-
return response_result.errors
30-
else:
31-
return
14+
return self.response_validator.validate(request, response)

tests/integration/contrib/flask/test_flask_decorator.py

+29-7
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
55
from openapi_core.shortcuts import create_spec
6+
from openapi_core.validation.request.datatypes import RequestParameters
67

78

89
class TestFlaskOpenAPIDecorator(object):
910

10-
view_response = None
11+
view_response_callable = None
1112

1213
@pytest.fixture
1314
def spec(self, factory):
@@ -31,17 +32,30 @@ def client(self, app):
3132
with app.app_context():
3233
yield client
3334

35+
@pytest.fixture
36+
def view_response(self):
37+
def view_response(*args, **kwargs):
38+
return self.view_response_callable(*args, **kwargs)
39+
return view_response
40+
3441
@pytest.fixture(autouse=True)
35-
def view(self, app, decorator):
42+
def view(self, app, decorator, view_response):
3643
@app.route("/browse/<id>/")
3744
@decorator
38-
def browse_details(id):
39-
return self.view_response
45+
def browse_details(*args, **kwargs):
46+
return view_response(*args, **kwargs)
4047
return browse_details
4148

4249
def test_invalid_content_type(self, client):
43-
self.view_response = make_response('success', 200)
44-
50+
def view_response_callable(*args, **kwargs):
51+
from flask.globals import request
52+
assert request.openapi
53+
assert not request.openapi.errors
54+
assert request.openapi.parameters == RequestParameters(path={
55+
'id': 12,
56+
})
57+
return make_response('success', 200)
58+
self.view_response_callable = view_response_callable
4559
result = client.get('/browse/12/')
4660

4761
assert result.json == {
@@ -101,7 +115,15 @@ def test_endpoint_error(self, client):
101115
assert result.json == expected_data
102116

103117
def test_valid(self, client):
104-
self.view_response = jsonify(data='data')
118+
def view_response_callable(*args, **kwargs):
119+
from flask.globals import request
120+
assert request.openapi
121+
assert not request.openapi.errors
122+
assert request.openapi.parameters == RequestParameters(path={
123+
'id': 12,
124+
})
125+
return jsonify(data='data')
126+
self.view_response_callable = view_response_callable
105127

106128
result = client.get('/browse/12/')
107129

0 commit comments

Comments
 (0)