From 0bbf787423875e9ec34dead7f31c2d1733d04b97 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 19 Oct 2019 13:35:48 +0100 Subject: [PATCH] Flask factories; Get rid of wrappers --- README.rst | 14 ++-- openapi_core/contrib/flask/__init__.py | 13 +++- openapi_core/contrib/flask/requests.py | 64 +++++++---------- openapi_core/contrib/flask/responses.py | 25 +++---- openapi_core/shortcuts.py | 24 +++---- openapi_core/wrappers/__init__.py | 0 openapi_core/wrappers/base.py | 49 ------------- tests/integration/contrib/test_flask.py | 46 ++++++------ ...{flask_wrapper.yaml => flask_factory.yaml} | 2 +- tests/unit/test_shortcuts.py | 70 +++++++++---------- 10 files changed, 123 insertions(+), 184 deletions(-) delete mode 100644 openapi_core/wrappers/__init__.py delete mode 100644 openapi_core/wrappers/base.py rename tests/integration/data/v3.0/{flask_wrapper.yaml => flask_factory.yaml} (80%) diff --git a/README.rst b/README.rst index 30aa179..629aa18 100644 --- a/README.rst +++ b/README.rst @@ -81,7 +81,7 @@ or use shortcuts for simple validation validated_params = validate_parameters(spec, request) validated_body = validate_body(spec, request) -Request object should implement BaseOpenAPIRequest interface. You can use FlaskOpenAPIRequest a Flask/Werkzeug request wrapper implementation: +Request object should be instance of OpenAPIRequest class. You can use FlaskOpenAPIRequest a Flask/Werkzeug request factory: .. code-block:: python @@ -92,16 +92,16 @@ Request object should implement BaseOpenAPIRequest interface. You can use FlaskO validator = RequestValidator(spec) result = validator.validate(openapi_request) -or specify request wrapper class for shortcuts +or simply specify request factory for shortcuts .. code-block:: python from openapi_core import validate_parameters, validate_body validated_params = validate_parameters( - spec, request, wrapper_class=FlaskOpenAPIRequest) + spec, request, request_factory=FlaskOpenAPIRequest) validated_body = validate_body( - spec, request, wrapper_class=FlaskOpenAPIRequest) + spec, request, request_factory=FlaskOpenAPIRequest) You can also validate responses @@ -136,7 +136,7 @@ or use shortcuts for simple validation validated_data = validate_data(spec, request, response) -Response object should implement BaseOpenAPIResponse interface. You can use FlaskOpenAPIResponse a Flask/Werkzeug response wrapper implementation: +Response object should be instance of OpenAPIResponse class. You can use FlaskOpenAPIResponse a Flask/Werkzeug response factory: .. code-block:: python @@ -147,14 +147,14 @@ Response object should implement BaseOpenAPIResponse interface. You can use Flas validator = ResponseValidator(spec) result = validator.validate(openapi_request, openapi_response) -or specify response wrapper class for shortcuts +or simply specify response factory for shortcuts .. code-block:: python from openapi_core import validate_parameters, validate_body validated_data = validate_data( - spec, request, response, response_wrapper_class=FlaskOpenAPIResponse) + spec, request, response, response_factory=FlaskOpenAPIResponse) Related projects ================ diff --git a/openapi_core/contrib/flask/__init__.py b/openapi_core/contrib/flask/__init__.py index 1c332e5..415b74c 100644 --- a/openapi_core/contrib/flask/__init__.py +++ b/openapi_core/contrib/flask/__init__.py @@ -1,4 +1,11 @@ -from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest -from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse +from openapi_core.contrib.flask.requests import FlaskOpenAPIRequestFactory +from openapi_core.contrib.flask.responses import FlaskOpenAPIResponseFactory -__all__ = ['FlaskOpenAPIRequest', 'FlaskOpenAPIResponse'] +# backward compatibility +FlaskOpenAPIRequest = FlaskOpenAPIRequestFactory.create +FlaskOpenAPIResponse = FlaskOpenAPIResponseFactory.create + +__all__ = [ + 'FlaskOpenAPIRequestFactory', 'FlaskOpenAPIResponseFactory', + 'FlaskOpenAPIRequest', 'FlaskOpenAPIResponse', +] diff --git a/openapi_core/contrib/flask/requests.py b/openapi_core/contrib/flask/requests.py index e5d260b..93e6538 100644 --- a/openapi_core/contrib/flask/requests.py +++ b/openapi_core/contrib/flask/requests.py @@ -1,51 +1,39 @@ """OpenAPI core contrib flask requests module""" import re -from openapi_core.wrappers.base import BaseOpenAPIRequest +from openapi_core.validation.request.datatypes import ( + RequestParameters, OpenAPIRequest, +) # http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>' -class FlaskOpenAPIRequest(BaseOpenAPIRequest): +class FlaskOpenAPIRequestFactory(object): path_regex = re.compile(PATH_PARAMETER_PATTERN) - def __init__(self, request): - self.request = request + @classmethod + def create(cls, request): + method = request.method.lower() - @property - def host_url(self): - return self.request.host_url + if request.url_rule is None: + path_pattern = request.path + else: + path_pattern = cls.path_regex.sub(r'{\1}', request.url_rule.rule) - @property - def path(self): - return self.request.path - - @property - def method(self): - return self.request.method.lower() - - @property - def path_pattern(self): - if self.request.url_rule is None: - return self.path - - return self.path_regex.sub(r'{\1}', self.request.url_rule.rule) - - @property - def parameters(self): - return { - 'path': self.request.view_args, - 'query': self.request.args, - 'header': self.request.headers, - 'cookie': self.request.cookies, - } - - @property - def body(self): - return self.request.data - - @property - def mimetype(self): - return self.request.mimetype + parameters = RequestParameters( + path=request.view_args, + query=request.args, + header=request.headers, + cookie=request.cookies, + ) + return OpenAPIRequest( + host_url=request.host_url, + path=request.path, + path_pattern=path_pattern, + method=method, + parameters=parameters, + body=request.data, + mimetype=request.mimetype, + ) diff --git a/openapi_core/contrib/flask/responses.py b/openapi_core/contrib/flask/responses.py index c2922a1..6e1f3e2 100644 --- a/openapi_core/contrib/flask/responses.py +++ b/openapi_core/contrib/flask/responses.py @@ -1,22 +1,15 @@ """OpenAPI core contrib flask responses module""" import re -from openapi_core.wrappers.base import BaseOpenAPIResponse +from openapi_core.validation.response.datatypes import OpenAPIResponse -class FlaskOpenAPIResponse(BaseOpenAPIResponse): +class FlaskOpenAPIResponseFactory(object): - def __init__(self, response): - self.response = response - - @property - def data(self): - return self.response.data - - @property - def status_code(self): - return self.response._status_code - - @property - def mimetype(self): - return self.response.mimetype + @classmethod + def create(cls, response): + return OpenAPIResponse( + data=response.data, + status_code=response._status_code, + mimetype=response.mimetype, + ) diff --git a/openapi_core/shortcuts.py b/openapi_core/shortcuts.py index 02df1c1..e55006d 100644 --- a/openapi_core/shortcuts.py +++ b/openapi_core/shortcuts.py @@ -20,9 +20,9 @@ def create_spec(spec_dict, spec_url=''): return spec_factory.create(spec_dict, spec_url=spec_url) -def validate_parameters(spec, request, wrapper_class=None): - if wrapper_class is not None: - request = wrapper_class(request) +def validate_parameters(spec, request, request_factory=None): + if request_factory is not None: + request = request_factory(request) validator = RequestValidator(spec) result = validator.validate(request) @@ -38,9 +38,9 @@ def validate_parameters(spec, request, wrapper_class=None): return result.parameters -def validate_body(spec, request, wrapper_class=None): - if wrapper_class is not None: - request = wrapper_class(request) +def validate_body(spec, request, request_factory=None): + if request_factory is not None: + request = request_factory(request) validator = RequestValidator(spec) result = validator.validate(request) @@ -55,13 +55,13 @@ def validate_body(spec, request, wrapper_class=None): def validate_data( spec, request, response, - request_wrapper_class=None, - response_wrapper_class=None): - if request_wrapper_class is not None: - request = request_wrapper_class(request) + request_factory=None, + response_factory=None): + if request_factory is not None: + request = request_factory(request) - if response_wrapper_class is not None: - response = response_wrapper_class(response) + if response_factory is not None: + response = response_factory(response) validator = ResponseValidator(spec) result = validator.validate(request, response) diff --git a/openapi_core/wrappers/__init__.py b/openapi_core/wrappers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/openapi_core/wrappers/base.py b/openapi_core/wrappers/base.py deleted file mode 100644 index 395d16e..0000000 --- a/openapi_core/wrappers/base.py +++ /dev/null @@ -1,49 +0,0 @@ -"""OpenAPI core wrappers module""" -import warnings - -from six.moves.urllib.parse import urljoin - - -class BaseOpenAPIRequest(object): - - host_url = NotImplemented - path = NotImplemented - path_pattern = NotImplemented - method = NotImplemented - - parameters = NotImplemented - body = NotImplemented - - mimetype = NotImplemented - - @property - def full_url_pattern(self): - return urljoin(self.host_url, self.path_pattern) - - def get_body(self, spec): - warnings.warn( - "`get_body` method is deprecated. " - "Use RequestValidator instead.", - DeprecationWarning, - ) - # backward compatibility - from openapi_core.shortcuts import validate_body - return validate_body(spec, self, wrapper_class=None) - - def get_parameters(self, spec): - warnings.warn( - "`get_parameters` method is deprecated. " - "Use RequestValidator instead.", - DeprecationWarning, - ) - # backward compatibility - from openapi_core.shortcuts import validate_parameters - return validate_parameters(spec, self, wrapper_class=None) - - -class BaseOpenAPIResponse(object): - - body = NotImplemented - status_code = NotImplemented - - mimetype = NotImplemented diff --git a/tests/integration/contrib/test_flask.py b/tests/integration/contrib/test_flask.py index 15a9149..c058543 100644 --- a/tests/integration/contrib/test_flask.py +++ b/tests/integration/contrib/test_flask.py @@ -4,12 +4,13 @@ from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict from werkzeug.routing import Map, Rule, Subdomain from werkzeug.test import create_environ -from openapi_core.shortcuts import create_spec -from openapi_core.validation.response.validators import ResponseValidator -from openapi_core.validation.request.validators import RequestValidator from openapi_core.contrib.flask import ( FlaskOpenAPIRequest, FlaskOpenAPIResponse, ) +from openapi_core.shortcuts import create_spec +from openapi_core.validation.request.datatypes import RequestParameters +from openapi_core.validation.request.validators import RequestValidator +from openapi_core.validation.response.validators import ResponseValidator @pytest.fixture @@ -67,12 +68,12 @@ class TestFlaskOpenAPIRequest(object): query = ImmutableMultiDict([]) headers = EnvironHeaders(request.environ) cookies = {} - assert openapi_request.parameters == { - 'path': path, - 'query': query, - 'header': headers, - 'cookie': cookies, - } + assert openapi_request.parameters == RequestParameters( + path=path, + query=query, + header=headers, + cookie=cookies, + ) assert openapi_request.host_url == request.host_url assert openapi_request.path == request.path assert openapi_request.method == request.method.lower() @@ -92,12 +93,12 @@ class TestFlaskOpenAPIRequest(object): ]) headers = EnvironHeaders(request.environ) cookies = {} - assert openapi_request.parameters == { - 'path': path, - 'query': query, - 'header': headers, - 'cookie': cookies, - } + assert openapi_request.parameters == RequestParameters( + path=path, + query=query, + header=headers, + cookie=cookies, + ) assert openapi_request.host_url == request.host_url assert openapi_request.path == request.path assert openapi_request.method == request.method.lower() @@ -114,12 +115,12 @@ class TestFlaskOpenAPIRequest(object): query = ImmutableMultiDict([]) headers = EnvironHeaders(request.environ) cookies = {} - assert openapi_request.parameters == { - 'path': path, - 'query': query, - 'header': headers, - 'cookie': cookies, - } + assert openapi_request.parameters == RequestParameters( + path=path, + query=query, + header=headers, + cookie=cookies, + ) assert openapi_request.host_url == request.host_url assert openapi_request.path == request.path assert openapi_request.method == request.method.lower() @@ -135,7 +136,6 @@ class TestFlaskOpenAPIResponse(object): openapi_response = FlaskOpenAPIResponse(response) - assert openapi_response.response == response assert openapi_response.data == response.data assert openapi_response.status_code == response._status_code assert openapi_response.mimetype == response.mimetype @@ -145,7 +145,7 @@ class TestFlaskOpenAPIValidation(object): @pytest.fixture def flask_spec(self, factory): - specfile = 'data/v3.0/flask_wrapper.yaml' + specfile = 'data/v3.0/flask_factory.yaml' return create_spec(factory.spec_from_file(specfile)) def test_response_validator_path_pattern(self, diff --git a/tests/integration/data/v3.0/flask_wrapper.yaml b/tests/integration/data/v3.0/flask_factory.yaml similarity index 80% rename from tests/integration/data/v3.0/flask_wrapper.yaml rename to tests/integration/data/v3.0/flask_factory.yaml index 0dcfdd3..6b01b8b 100644 --- a/tests/integration/data/v3.0/flask_wrapper.yaml +++ b/tests/integration/data/v3.0/flask_factory.yaml @@ -1,6 +1,6 @@ openapi: "3.0.0" info: - title: Basic OpenAPI specification used with test_wrappers.TestFlaskOpenAPIIValidation + title: Basic OpenAPI specification used with test_flask.TestFlaskOpenAPIIValidation version: "0.1" servers: - url: 'http://localhost' diff --git a/tests/unit/test_shortcuts.py b/tests/unit/test_shortcuts.py index dc2caca..c5f1a7d 100644 --- a/tests/unit/test_shortcuts.py +++ b/tests/unit/test_shortcuts.py @@ -27,7 +27,7 @@ class ResultMock(object): return self.data -class WrapperClassMock(object): +class FactoryClassMock(object): _instances = {} @@ -43,7 +43,7 @@ class WrapperClassMock(object): class TestValidateParameters(object): @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_no_wrapper(self, mock_validate): + def test_no_request_factory(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request parameters = mock.sentinel.parameters @@ -55,7 +55,7 @@ class TestValidateParameters(object): mock_validate.aasert_called_once_with(request) @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_no_wrapper_error(self, mock_validate): + def test_no_request_factory_error(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request mock_validate.return_value = ResultMock(error_to_raise=ValueError) @@ -66,39 +66,39 @@ class TestValidateParameters(object): mock_validate.aasert_called_once_with(request) @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_wrapper(self, mock_validate): + def test_request_factory(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request parameters = mock.sentinel.parameters mock_validate.return_value = ResultMock(parameters=parameters) - request_wrapper_class = WrapperClassMock + request_factory = FactoryClassMock - result = validate_parameters(spec, request, request_wrapper_class) + result = validate_parameters(spec, request, request_factory) assert result == parameters mock_validate.assert_called_once_with( - WrapperClassMock(request), + FactoryClassMock(request), ) @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_wrapper_error(self, mock_validate): + def test_request_factory_error(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request mock_validate.return_value = ResultMock(error_to_raise=ValueError) - request_wrapper_class = WrapperClassMock + request_factory = FactoryClassMock with pytest.raises(ValueError): - validate_parameters(spec, request, request_wrapper_class) + validate_parameters(spec, request, request_factory) mock_validate.assert_called_once_with( - WrapperClassMock(request), + FactoryClassMock(request), ) class TestValidateBody(object): @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_no_wrapper(self, mock_validate): + def test_no_request_factory(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request body = mock.sentinel.body @@ -110,7 +110,7 @@ class TestValidateBody(object): mock_validate.aasert_called_once_with(request) @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_no_wrapper_error(self, mock_validate): + def test_no_request_factory_error(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request mock_validate.return_value = ResultMock(error_to_raise=ValueError) @@ -121,39 +121,39 @@ class TestValidateBody(object): mock_validate.aasert_called_once_with(request) @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_wrapper(self, mock_validate): + def test_request_factory(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request body = mock.sentinel.body mock_validate.return_value = ResultMock(body=body) - request_wrapper_class = WrapperClassMock + request_factory = FactoryClassMock - result = validate_body(spec, request, request_wrapper_class) + result = validate_body(spec, request, request_factory) assert result == body mock_validate.assert_called_once_with( - WrapperClassMock(request), + FactoryClassMock(request), ) @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_wrapper_error(self, mock_validate): + def test_request_factory_error(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request mock_validate.return_value = ResultMock(error_to_raise=ValueError) - request_wrapper_class = WrapperClassMock + request_factory = FactoryClassMock with pytest.raises(ValueError): - validate_body(spec, request, request_wrapper_class) + validate_body(spec, request, request_factory) mock_validate.assert_called_once_with( - WrapperClassMock(request), + FactoryClassMock(request), ) class TestvalidateData(object): @mock.patch('openapi_core.shortcuts.ResponseValidator.validate') - def test_no_wrappers(self, mock_validate): + def test_no_factories(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request response = mock.sentinel.response @@ -166,7 +166,7 @@ class TestvalidateData(object): mock_validate.aasert_called_once_with(request, response) @mock.patch('openapi_core.shortcuts.ResponseValidator.validate') - def test_no_wrappers_error(self, mock_validate): + def test_no_factories_error(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request response = mock.sentinel.response @@ -178,42 +178,42 @@ class TestvalidateData(object): mock_validate.aasert_called_once_with(request, response) @mock.patch('openapi_core.shortcuts.ResponseValidator.validate') - def test_wrappers(self, mock_validate): + def test_factories(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request response = mock.sentinel.response data = mock.sentinel.data mock_validate.return_value = ResultMock(data=data) - request_wrapper_class = WrapperClassMock - response_wrapper_class = WrapperClassMock + request_factory = FactoryClassMock + response_factory = FactoryClassMock result = validate_data( spec, request, response, - request_wrapper_class, response_wrapper_class, + request_factory, response_factory, ) assert result == data mock_validate.assert_called_once_with( - WrapperClassMock(request), - WrapperClassMock(response), + FactoryClassMock(request), + FactoryClassMock(response), ) @mock.patch('openapi_core.shortcuts.ResponseValidator.validate') - def test_wrappers_error(self, mock_validate): + def test_factories_error(self, mock_validate): spec = mock.sentinel.spec request = mock.sentinel.request response = mock.sentinel.response mock_validate.return_value = ResultMock(error_to_raise=ValueError) - request_wrapper_class = WrapperClassMock - response_wrapper_class = WrapperClassMock + request_factory = FactoryClassMock + response_factory = FactoryClassMock with pytest.raises(ValueError): validate_data( spec, request, response, - request_wrapper_class, response_wrapper_class, + request_factory, response_factory, ) mock_validate.assert_called_once_with( - WrapperClassMock(request), - WrapperClassMock(response), + FactoryClassMock(request), + FactoryClassMock(response), )