Flask factories; Get rid of wrappers

This commit is contained in:
p1c2u 2019-10-19 13:35:48 +01:00
parent cffc47c60a
commit 0bbf787423
10 changed files with 123 additions and 184 deletions

View file

@ -81,7 +81,7 @@ or use shortcuts for simple validation
validated_params = validate_parameters(spec, request) validated_params = validate_parameters(spec, request)
validated_body = validate_body(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 .. code-block:: python
@ -92,16 +92,16 @@ Request object should implement BaseOpenAPIRequest interface. You can use FlaskO
validator = RequestValidator(spec) validator = RequestValidator(spec)
result = validator.validate(openapi_request) result = validator.validate(openapi_request)
or specify request wrapper class for shortcuts or simply specify request factory for shortcuts
.. code-block:: python .. code-block:: python
from openapi_core import validate_parameters, validate_body from openapi_core import validate_parameters, validate_body
validated_params = validate_parameters( validated_params = validate_parameters(
spec, request, wrapper_class=FlaskOpenAPIRequest) spec, request, request_factory=FlaskOpenAPIRequest)
validated_body = validate_body( validated_body = validate_body(
spec, request, wrapper_class=FlaskOpenAPIRequest) spec, request, request_factory=FlaskOpenAPIRequest)
You can also validate responses You can also validate responses
@ -136,7 +136,7 @@ or use shortcuts for simple validation
validated_data = validate_data(spec, request, response) 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 .. code-block:: python
@ -147,14 +147,14 @@ Response object should implement BaseOpenAPIResponse interface. You can use Flas
validator = ResponseValidator(spec) validator = ResponseValidator(spec)
result = validator.validate(openapi_request, openapi_response) result = validator.validate(openapi_request, openapi_response)
or specify response wrapper class for shortcuts or simply specify response factory for shortcuts
.. code-block:: python .. code-block:: python
from openapi_core import validate_parameters, validate_body from openapi_core import validate_parameters, validate_body
validated_data = validate_data( validated_data = validate_data(
spec, request, response, response_wrapper_class=FlaskOpenAPIResponse) spec, request, response, response_factory=FlaskOpenAPIResponse)
Related projects Related projects
================ ================

View file

@ -1,4 +1,11 @@
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest from openapi_core.contrib.flask.requests import FlaskOpenAPIRequestFactory
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse from openapi_core.contrib.flask.responses import FlaskOpenAPIResponseFactory
__all__ = ['FlaskOpenAPIRequest', 'FlaskOpenAPIResponse'] # backward compatibility
FlaskOpenAPIRequest = FlaskOpenAPIRequestFactory.create
FlaskOpenAPIResponse = FlaskOpenAPIResponseFactory.create
__all__ = [
'FlaskOpenAPIRequestFactory', 'FlaskOpenAPIResponseFactory',
'FlaskOpenAPIRequest', 'FlaskOpenAPIResponse',
]

View file

@ -1,51 +1,39 @@
"""OpenAPI core contrib flask requests module""" """OpenAPI core contrib flask requests module"""
import re 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 # http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>' PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>'
class FlaskOpenAPIRequest(BaseOpenAPIRequest): class FlaskOpenAPIRequestFactory(object):
path_regex = re.compile(PATH_PARAMETER_PATTERN) path_regex = re.compile(PATH_PARAMETER_PATTERN)
def __init__(self, request): @classmethod
self.request = request def create(cls, request):
method = request.method.lower()
@property if request.url_rule is None:
def host_url(self): path_pattern = request.path
return self.request.host_url else:
path_pattern = cls.path_regex.sub(r'{\1}', request.url_rule.rule)
@property parameters = RequestParameters(
def path(self): path=request.view_args,
return self.request.path query=request.args,
header=request.headers,
@property cookie=request.cookies,
def method(self): )
return self.request.method.lower() return OpenAPIRequest(
host_url=request.host_url,
@property path=request.path,
def path_pattern(self): path_pattern=path_pattern,
if self.request.url_rule is None: method=method,
return self.path parameters=parameters,
body=request.data,
return self.path_regex.sub(r'{\1}', self.request.url_rule.rule) mimetype=request.mimetype,
)
@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

View file

@ -1,22 +1,15 @@
"""OpenAPI core contrib flask responses module""" """OpenAPI core contrib flask responses module"""
import re 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): @classmethod
self.response = response def create(cls, response):
return OpenAPIResponse(
@property data=response.data,
def data(self): status_code=response._status_code,
return self.response.data mimetype=response.mimetype,
)
@property
def status_code(self):
return self.response._status_code
@property
def mimetype(self):
return self.response.mimetype

View file

@ -20,9 +20,9 @@ def create_spec(spec_dict, spec_url=''):
return spec_factory.create(spec_dict, spec_url=spec_url) return spec_factory.create(spec_dict, spec_url=spec_url)
def validate_parameters(spec, request, wrapper_class=None): def validate_parameters(spec, request, request_factory=None):
if wrapper_class is not None: if request_factory is not None:
request = wrapper_class(request) request = request_factory(request)
validator = RequestValidator(spec) validator = RequestValidator(spec)
result = validator.validate(request) result = validator.validate(request)
@ -38,9 +38,9 @@ def validate_parameters(spec, request, wrapper_class=None):
return result.parameters return result.parameters
def validate_body(spec, request, wrapper_class=None): def validate_body(spec, request, request_factory=None):
if wrapper_class is not None: if request_factory is not None:
request = wrapper_class(request) request = request_factory(request)
validator = RequestValidator(spec) validator = RequestValidator(spec)
result = validator.validate(request) result = validator.validate(request)
@ -55,13 +55,13 @@ def validate_body(spec, request, wrapper_class=None):
def validate_data( def validate_data(
spec, request, response, spec, request, response,
request_wrapper_class=None, request_factory=None,
response_wrapper_class=None): response_factory=None):
if request_wrapper_class is not None: if request_factory is not None:
request = request_wrapper_class(request) request = request_factory(request)
if response_wrapper_class is not None: if response_factory is not None:
response = response_wrapper_class(response) response = response_factory(response)
validator = ResponseValidator(spec) validator = ResponseValidator(spec)
result = validator.validate(request, response) result = validator.validate(request, response)

View file

@ -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

View file

@ -4,12 +4,13 @@ from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict
from werkzeug.routing import Map, Rule, Subdomain from werkzeug.routing import Map, Rule, Subdomain
from werkzeug.test import create_environ 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 ( from openapi_core.contrib.flask import (
FlaskOpenAPIRequest, FlaskOpenAPIResponse, 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 @pytest.fixture
@ -67,12 +68,12 @@ class TestFlaskOpenAPIRequest(object):
query = ImmutableMultiDict([]) query = ImmutableMultiDict([])
headers = EnvironHeaders(request.environ) headers = EnvironHeaders(request.environ)
cookies = {} cookies = {}
assert openapi_request.parameters == { assert openapi_request.parameters == RequestParameters(
'path': path, path=path,
'query': query, query=query,
'header': headers, header=headers,
'cookie': cookies, cookie=cookies,
} )
assert openapi_request.host_url == request.host_url assert openapi_request.host_url == request.host_url
assert openapi_request.path == request.path assert openapi_request.path == request.path
assert openapi_request.method == request.method.lower() assert openapi_request.method == request.method.lower()
@ -92,12 +93,12 @@ class TestFlaskOpenAPIRequest(object):
]) ])
headers = EnvironHeaders(request.environ) headers = EnvironHeaders(request.environ)
cookies = {} cookies = {}
assert openapi_request.parameters == { assert openapi_request.parameters == RequestParameters(
'path': path, path=path,
'query': query, query=query,
'header': headers, header=headers,
'cookie': cookies, cookie=cookies,
} )
assert openapi_request.host_url == request.host_url assert openapi_request.host_url == request.host_url
assert openapi_request.path == request.path assert openapi_request.path == request.path
assert openapi_request.method == request.method.lower() assert openapi_request.method == request.method.lower()
@ -114,12 +115,12 @@ class TestFlaskOpenAPIRequest(object):
query = ImmutableMultiDict([]) query = ImmutableMultiDict([])
headers = EnvironHeaders(request.environ) headers = EnvironHeaders(request.environ)
cookies = {} cookies = {}
assert openapi_request.parameters == { assert openapi_request.parameters == RequestParameters(
'path': path, path=path,
'query': query, query=query,
'header': headers, header=headers,
'cookie': cookies, cookie=cookies,
} )
assert openapi_request.host_url == request.host_url assert openapi_request.host_url == request.host_url
assert openapi_request.path == request.path assert openapi_request.path == request.path
assert openapi_request.method == request.method.lower() assert openapi_request.method == request.method.lower()
@ -135,7 +136,6 @@ class TestFlaskOpenAPIResponse(object):
openapi_response = FlaskOpenAPIResponse(response) openapi_response = FlaskOpenAPIResponse(response)
assert openapi_response.response == response
assert openapi_response.data == response.data assert openapi_response.data == response.data
assert openapi_response.status_code == response._status_code assert openapi_response.status_code == response._status_code
assert openapi_response.mimetype == response.mimetype assert openapi_response.mimetype == response.mimetype
@ -145,7 +145,7 @@ class TestFlaskOpenAPIValidation(object):
@pytest.fixture @pytest.fixture
def flask_spec(self, factory): 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)) return create_spec(factory.spec_from_file(specfile))
def test_response_validator_path_pattern(self, def test_response_validator_path_pattern(self,

View file

@ -1,6 +1,6 @@
openapi: "3.0.0" openapi: "3.0.0"
info: info:
title: Basic OpenAPI specification used with test_wrappers.TestFlaskOpenAPIIValidation title: Basic OpenAPI specification used with test_flask.TestFlaskOpenAPIIValidation
version: "0.1" version: "0.1"
servers: servers:
- url: 'http://localhost' - url: 'http://localhost'

View file

@ -27,7 +27,7 @@ class ResultMock(object):
return self.data return self.data
class WrapperClassMock(object): class FactoryClassMock(object):
_instances = {} _instances = {}
@ -43,7 +43,7 @@ class WrapperClassMock(object):
class TestValidateParameters(object): class TestValidateParameters(object):
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
parameters = mock.sentinel.parameters parameters = mock.sentinel.parameters
@ -55,7 +55,7 @@ class TestValidateParameters(object):
mock_validate.aasert_called_once_with(request) mock_validate.aasert_called_once_with(request)
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
mock_validate.return_value = ResultMock(error_to_raise=ValueError) mock_validate.return_value = ResultMock(error_to_raise=ValueError)
@ -66,39 +66,39 @@ class TestValidateParameters(object):
mock_validate.aasert_called_once_with(request) mock_validate.aasert_called_once_with(request)
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @mock.patch('openapi_core.shortcuts.RequestValidator.validate')
def test_wrapper(self, mock_validate): def test_request_factory(self, mock_validate):
spec = mock.sentinel.spec spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
parameters = mock.sentinel.parameters parameters = mock.sentinel.parameters
mock_validate.return_value = ResultMock(parameters=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 assert result == parameters
mock_validate.assert_called_once_with( mock_validate.assert_called_once_with(
WrapperClassMock(request), FactoryClassMock(request),
) )
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
mock_validate.return_value = ResultMock(error_to_raise=ValueError) mock_validate.return_value = ResultMock(error_to_raise=ValueError)
request_wrapper_class = WrapperClassMock request_factory = FactoryClassMock
with pytest.raises(ValueError): with pytest.raises(ValueError):
validate_parameters(spec, request, request_wrapper_class) validate_parameters(spec, request, request_factory)
mock_validate.assert_called_once_with( mock_validate.assert_called_once_with(
WrapperClassMock(request), FactoryClassMock(request),
) )
class TestValidateBody(object): class TestValidateBody(object):
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
body = mock.sentinel.body body = mock.sentinel.body
@ -110,7 +110,7 @@ class TestValidateBody(object):
mock_validate.aasert_called_once_with(request) mock_validate.aasert_called_once_with(request)
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
mock_validate.return_value = ResultMock(error_to_raise=ValueError) mock_validate.return_value = ResultMock(error_to_raise=ValueError)
@ -121,39 +121,39 @@ class TestValidateBody(object):
mock_validate.aasert_called_once_with(request) mock_validate.aasert_called_once_with(request)
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @mock.patch('openapi_core.shortcuts.RequestValidator.validate')
def test_wrapper(self, mock_validate): def test_request_factory(self, mock_validate):
spec = mock.sentinel.spec spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
body = mock.sentinel.body body = mock.sentinel.body
mock_validate.return_value = ResultMock(body=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 assert result == body
mock_validate.assert_called_once_with( mock_validate.assert_called_once_with(
WrapperClassMock(request), FactoryClassMock(request),
) )
@mock.patch('openapi_core.shortcuts.RequestValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
mock_validate.return_value = ResultMock(error_to_raise=ValueError) mock_validate.return_value = ResultMock(error_to_raise=ValueError)
request_wrapper_class = WrapperClassMock request_factory = FactoryClassMock
with pytest.raises(ValueError): with pytest.raises(ValueError):
validate_body(spec, request, request_wrapper_class) validate_body(spec, request, request_factory)
mock_validate.assert_called_once_with( mock_validate.assert_called_once_with(
WrapperClassMock(request), FactoryClassMock(request),
) )
class TestvalidateData(object): class TestvalidateData(object):
@mock.patch('openapi_core.shortcuts.ResponseValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
response = mock.sentinel.response response = mock.sentinel.response
@ -166,7 +166,7 @@ class TestvalidateData(object):
mock_validate.aasert_called_once_with(request, response) mock_validate.aasert_called_once_with(request, response)
@mock.patch('openapi_core.shortcuts.ResponseValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
response = mock.sentinel.response response = mock.sentinel.response
@ -178,42 +178,42 @@ class TestvalidateData(object):
mock_validate.aasert_called_once_with(request, response) mock_validate.aasert_called_once_with(request, response)
@mock.patch('openapi_core.shortcuts.ResponseValidator.validate') @mock.patch('openapi_core.shortcuts.ResponseValidator.validate')
def test_wrappers(self, mock_validate): def test_factories(self, mock_validate):
spec = mock.sentinel.spec spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
response = mock.sentinel.response response = mock.sentinel.response
data = mock.sentinel.data data = mock.sentinel.data
mock_validate.return_value = ResultMock(data=data) mock_validate.return_value = ResultMock(data=data)
request_wrapper_class = WrapperClassMock request_factory = FactoryClassMock
response_wrapper_class = WrapperClassMock response_factory = FactoryClassMock
result = validate_data( result = validate_data(
spec, request, response, spec, request, response,
request_wrapper_class, response_wrapper_class, request_factory, response_factory,
) )
assert result == data assert result == data
mock_validate.assert_called_once_with( mock_validate.assert_called_once_with(
WrapperClassMock(request), FactoryClassMock(request),
WrapperClassMock(response), FactoryClassMock(response),
) )
@mock.patch('openapi_core.shortcuts.ResponseValidator.validate') @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 spec = mock.sentinel.spec
request = mock.sentinel.request request = mock.sentinel.request
response = mock.sentinel.response response = mock.sentinel.response
mock_validate.return_value = ResultMock(error_to_raise=ValueError) mock_validate.return_value = ResultMock(error_to_raise=ValueError)
request_wrapper_class = WrapperClassMock request_factory = FactoryClassMock
response_wrapper_class = WrapperClassMock response_factory = FactoryClassMock
with pytest.raises(ValueError): with pytest.raises(ValueError):
validate_data( validate_data(
spec, request, response, spec, request, response,
request_wrapper_class, response_wrapper_class, request_factory, response_factory,
) )
mock_validate.assert_called_once_with( mock_validate.assert_called_once_with(
WrapperClassMock(request), FactoryClassMock(request),
WrapperClassMock(response), FactoryClassMock(response),
) )