Merge pull request #185 from p1c2u/feature/flask-openapi-request-parameters

Flask OpenAPI request parameters
This commit is contained in:
A 2020-01-27 11:30:13 +00:00 committed by GitHub
commit 09bff35e3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 35 deletions

View file

@ -218,6 +218,21 @@ As an alternative to the decorator-based integration, Flask method based views c
app.add_url_rule('/home', view_func=MyView.as_view('home', spec))
Request parameters
==================
In Flask, all unmarshalled request data are provided as Flask request object's openapi.parameters attribute
.. code-block:: python
from flask.globals import request
@app.route('/browse/<id>/')
@openapi
def home():
browse_id = request.openapi.parameters.path['id']
page = request.openapi.parameters.query.get('page', 1)
Low level
=========

View file

@ -25,6 +25,12 @@ class FlaskOpenAPIViewDecorator(OpenAPIDecorator):
request_provider, openapi_errors_handler,
)
def _handle_request_view(self, request_result, view, *args, **kwargs):
request = self._get_request(*args, **kwargs)
request.openapi = request_result
return super(FlaskOpenAPIViewDecorator, self)._handle_request_view(
request_result, view, *args, **kwargs)
@classmethod
def from_spec(
cls,

View file

@ -27,22 +27,30 @@ class OpenAPIDecorator(OpenAPIProcessor):
def decorated(*args, **kwargs):
request = self._get_request(*args, **kwargs)
openapi_request = self._get_openapi_request(request)
errors = self.process_request(openapi_request)
if errors:
return self._handle_openapi_errors(errors)
response = view(*args, **kwargs)
request_result = self.process_request(openapi_request)
if request_result.errors:
return self._handle_request_errors(request_result)
response = self._handle_request_view(
request_result, view, *args, **kwargs)
openapi_response = self._get_openapi_response(response)
errors = self.process_response(openapi_request, openapi_response)
if errors:
return self._handle_openapi_errors(errors)
response_result = self.process_response(
openapi_request, openapi_response)
if response_result.errors:
return self._handle_response_errors(response_result)
return response
return decorated
def _get_request(self, *args, **kwargs):
return self.request_provider.provide(*args, **kwargs)
def _handle_openapi_errors(self, errors):
return self.openapi_errors_handler.handle(errors)
def _handle_request_view(self, request_result, view, *args, **kwargs):
return view(*args, **kwargs)
def _handle_request_errors(self, request_result):
return self.openapi_errors_handler.handle(request_result.errors)
def _handle_response_errors(self, response_result):
return self.openapi_errors_handler.handle(response_result.errors)
def _get_openapi_request(self, request):
return self.request_factory.create(request)

View file

@ -1,6 +1,4 @@
"""OpenAPI core validation processors module"""
from openapi_core.schema.servers.exceptions import InvalidServer
from openapi_core.schema.exceptions import OpenAPIMappingError
class OpenAPIProcessor(object):
@ -10,22 +8,7 @@ class OpenAPIProcessor(object):
self.response_validator = response_validator
def process_request(self, request):
request_result = self.request_validator.validate(request)
try:
request_result.raise_for_errors()
# return instantly on server error
except InvalidServer as exc:
return [exc, ]
except OpenAPIMappingError:
return request_result.errors
else:
return
return self.request_validator.validate(request)
def process_response(self, request, response):
response_result = self.response_validator.validate(request, response)
try:
response_result.raise_for_errors()
except OpenAPIMappingError:
return response_result.errors
else:
return
return self.response_validator.validate(request, response)

View file

@ -3,11 +3,12 @@ import pytest
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator
from openapi_core.shortcuts import create_spec
from openapi_core.validation.request.datatypes import RequestParameters
class TestFlaskOpenAPIDecorator(object):
view_response = None
view_response_callable = None
@pytest.fixture
def spec(self, factory):
@ -31,17 +32,30 @@ class TestFlaskOpenAPIDecorator(object):
with app.app_context():
yield client
@pytest.fixture
def view_response(self):
def view_response(*args, **kwargs):
return self.view_response_callable(*args, **kwargs)
return view_response
@pytest.fixture(autouse=True)
def view(self, app, decorator):
def view(self, app, decorator, view_response):
@app.route("/browse/<id>/")
@decorator
def browse_details(id):
return self.view_response
def browse_details(*args, **kwargs):
return view_response(*args, **kwargs)
return browse_details
def test_invalid_content_type(self, client):
self.view_response = make_response('success', 200)
def view_response_callable(*args, **kwargs):
from flask.globals import request
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters == RequestParameters(path={
'id': 12,
})
return make_response('success', 200)
self.view_response_callable = view_response_callable
result = client.get('/browse/12/')
assert result.json == {
@ -101,7 +115,15 @@ class TestFlaskOpenAPIDecorator(object):
assert result.json == expected_data
def test_valid(self, client):
self.view_response = jsonify(data='data')
def view_response_callable(*args, **kwargs):
from flask.globals import request
assert request.openapi
assert not request.openapi.errors
assert request.openapi.parameters == RequestParameters(path={
'id': 12,
})
return jsonify(data='data')
self.view_response_callable = view_response_callable
result = client.get('/browse/12/')