diff --git a/README.rst b/README.rst index 96368ceb..f51282ff 100644 --- a/README.rst +++ b/README.rst @@ -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//') + @openapi + def home(): + browse_id = request.openapi.parameters.path['id'] + page = request.openapi.parameters.query.get('page', 1) + Low level ========= diff --git a/openapi_core/contrib/flask/decorators.py b/openapi_core/contrib/flask/decorators.py index 10681086..6c0d1b3a 100644 --- a/openapi_core/contrib/flask/decorators.py +++ b/openapi_core/contrib/flask/decorators.py @@ -25,6 +25,12 @@ def __init__( 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, diff --git a/openapi_core/validation/decorators.py b/openapi_core/validation/decorators.py index a75e9af2..de5c2c2c 100644 --- a/openapi_core/validation/decorators.py +++ b/openapi_core/validation/decorators.py @@ -27,22 +27,30 @@ def __call__(self, view): 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) diff --git a/openapi_core/validation/processors.py b/openapi_core/validation/processors.py index 2ec2615d..3586146c 100644 --- a/openapi_core/validation/processors.py +++ b/openapi_core/validation/processors.py @@ -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 @@ def __init__(self, request_validator, response_validator): 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) diff --git a/tests/integration/contrib/flask/test_flask_decorator.py b/tests/integration/contrib/flask/test_flask_decorator.py index 010862d6..f08e6ad5 100644 --- a/tests/integration/contrib/flask/test_flask_decorator.py +++ b/tests/integration/contrib/flask/test_flask_decorator.py @@ -3,11 +3,12 @@ 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 @@ def client(self, app): 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//") @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 @@ def test_endpoint_error(self, client): 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/')