diff --git a/openapi_core/schema/shortcuts.py b/openapi_core/schema/shortcuts.py new file mode 100644 index 00000000..5f5ad012 --- /dev/null +++ b/openapi_core/schema/shortcuts.py @@ -0,0 +1,12 @@ +"""OpenAPI core schema shortcuts module""" +from jsonschema.validators import RefResolver +from openapi_spec_validator import default_handlers + +from openapi_core.schema.specs.factories import SpecFactory + + +def create_spec(spec_dict, spec_url='', handlers=default_handlers): + spec_resolver = RefResolver( + spec_url, spec_dict, handlers=handlers) + spec_factory = SpecFactory(spec_resolver) + return spec_factory.create(spec_dict, spec_url=spec_url) diff --git a/openapi_core/shortcuts.py b/openapi_core/shortcuts.py index e55006dc..dc479f85 100644 --- a/openapi_core/shortcuts.py +++ b/openapi_core/shortcuts.py @@ -1,71 +1,14 @@ """OpenAPI core shortcuts module""" -from jsonschema.validators import RefResolver -from openapi_spec_validator import default_handlers - -from openapi_core.schema.media_types.exceptions import OpenAPIMediaTypeError -from openapi_core.schema.parameters.exceptions import OpenAPIParameterError -from openapi_core.schema.request_bodies.exceptions import ( - OpenAPIRequestBodyError, +# backward compatibility +from openapi_core.schema.shortcuts import create_spec +from openapi_core.validation.request.shortcuts import ( + spec_validate_body as validate_body, + spec_validate_parameters as validate_parameters, +) +from openapi_core.validation.response.shortcuts import ( + spec_validate_data as validate_data ) -from openapi_core.schema.schemas.exceptions import OpenAPISchemaError -from openapi_core.schema.specs.factories import SpecFactory -from openapi_core.validation.request.validators import RequestValidator -from openapi_core.validation.response.validators import ResponseValidator - - -def create_spec(spec_dict, spec_url=''): - spec_resolver = RefResolver( - spec_url, spec_dict, handlers=default_handlers) - spec_factory = SpecFactory(spec_resolver) - return spec_factory.create(spec_dict, spec_url=spec_url) - - -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) - - try: - result.raise_for_errors() - except ( - OpenAPIRequestBodyError, OpenAPIMediaTypeError, - OpenAPISchemaError, - ): - return result.parameters - else: - return result.parameters - - -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) - - try: - result.raise_for_errors() - except OpenAPIParameterError: - return result.body - else: - return result.body - - -def validate_data( - spec, request, response, - request_factory=None, - response_factory=None): - if request_factory is not None: - request = request_factory(request) - - if response_factory is not None: - response = response_factory(response) - - validator = ResponseValidator(spec) - result = validator.validate(request, response) - - result.raise_for_errors() - return result.data +__all__ = [ + 'create_spec', 'validate_body', 'validate_parameters', 'validate_data', +] diff --git a/openapi_core/testing/datatypes.py b/openapi_core/testing/datatypes.py new file mode 100644 index 00000000..963ea118 --- /dev/null +++ b/openapi_core/testing/datatypes.py @@ -0,0 +1,18 @@ +class ResultMock(object): + + def __init__( + self, body=None, parameters=None, data=None, error_to_raise=None): + self.body = body + self.parameters = parameters + self.data = data + self.error_to_raise = error_to_raise + + def raise_for_errors(self): + if self.error_to_raise is not None: + raise self.error_to_raise + + if self.parameters is not None: + return self.parameters + + if self.data is not None: + return self.data diff --git a/openapi_core/testing/factories.py b/openapi_core/testing/factories.py new file mode 100644 index 00000000..7ac561e8 --- /dev/null +++ b/openapi_core/testing/factories.py @@ -0,0 +1,11 @@ +class FactoryClassMock(object): + + _instances = {} + + def __new__(cls, obj): + if obj not in cls._instances: + cls._instances[obj] = object.__new__(cls) + return cls._instances[obj] + + def __init__(self, obj): + self.obj = obj diff --git a/openapi_core/testing/mock.py b/openapi_core/testing/mock.py index 4ff05338..d305f444 100644 --- a/openapi_core/testing/mock.py +++ b/openapi_core/testing/mock.py @@ -1,45 +1,6 @@ """OpenAPI core testing mock module""" -from werkzeug.datastructures import ImmutableMultiDict +# backward compatibility +from openapi_core.testing.requests import MockRequestFactory +from openapi_core.testing.responses import MockResponseFactory -from openapi_core.validation.request.datatypes import ( - RequestParameters, OpenAPIRequest, -) -from openapi_core.validation.response.datatypes import OpenAPIResponse - - -class MockRequestFactory(object): - - @classmethod - def create( - cls, host_url, method, path, path_pattern=None, args=None, - view_args=None, headers=None, cookies=None, data=None, - mimetype='application/json'): - parameters = RequestParameters( - path=view_args or {}, - query=ImmutableMultiDict(args or []), - header=headers or {}, - cookie=cookies or {}, - ) - path_pattern = path_pattern or path - method = method.lower() - body = data or '' - return OpenAPIRequest( - host_url=host_url, - path=path, - path_pattern=path_pattern, - method=method, - parameters=parameters, - body=body, - mimetype=mimetype, - ) - - -class MockResponseFactory(object): - - @classmethod - def create(cls, data, status_code=200, mimetype='application/json'): - return OpenAPIResponse( - data=data, - status_code=status_code, - mimetype=mimetype, - ) +__all__ = ['MockRequestFactory', 'MockResponseFactory'] diff --git a/openapi_core/testing/requests.py b/openapi_core/testing/requests.py new file mode 100644 index 00000000..d2a1b858 --- /dev/null +++ b/openapi_core/testing/requests.py @@ -0,0 +1,33 @@ +"""OpenAPI core testing requests module""" +from werkzeug.datastructures import ImmutableMultiDict + +from openapi_core.validation.request.datatypes import ( + RequestParameters, OpenAPIRequest, +) + + +class MockRequestFactory(object): + + @classmethod + def create( + cls, host_url, method, path, path_pattern=None, args=None, + view_args=None, headers=None, cookies=None, data=None, + mimetype='application/json'): + parameters = RequestParameters( + path=view_args or {}, + query=ImmutableMultiDict(args or []), + header=headers or {}, + cookie=cookies or {}, + ) + path_pattern = path_pattern or path + method = method.lower() + body = data or '' + return OpenAPIRequest( + host_url=host_url, + path=path, + path_pattern=path_pattern, + method=method, + parameters=parameters, + body=body, + mimetype=mimetype, + ) diff --git a/openapi_core/testing/responses.py b/openapi_core/testing/responses.py new file mode 100644 index 00000000..af96d0b0 --- /dev/null +++ b/openapi_core/testing/responses.py @@ -0,0 +1,13 @@ +"""OpenAPI core testing responses module""" +from openapi_core.validation.response.datatypes import OpenAPIResponse + + +class MockResponseFactory(object): + + @classmethod + def create(cls, data, status_code=200, mimetype='application/json'): + return OpenAPIResponse( + data=data, + status_code=status_code, + mimetype=mimetype, + ) diff --git a/openapi_core/validation/request/shortcuts.py b/openapi_core/validation/request/shortcuts.py new file mode 100644 index 00000000..2f77d119 --- /dev/null +++ b/openapi_core/validation/request/shortcuts.py @@ -0,0 +1,52 @@ +"""OpenAPI core validation request shortcuts module""" +from functools import partial + +from openapi_core.schema.media_types.exceptions import OpenAPIMediaTypeError +from openapi_core.schema.parameters.exceptions import OpenAPIParameterError +from openapi_core.schema.request_bodies.exceptions import ( + OpenAPIRequestBodyError, +) +from openapi_core.schema.schemas.exceptions import OpenAPISchemaError +from openapi_core.validation.request.validators import RequestValidator + +ERRORS_BODY = ( + OpenAPIRequestBodyError, OpenAPIMediaTypeError, OpenAPISchemaError, +) +ERRORS_PARAMETERS = ( + OpenAPIParameterError, +) + + +def validate_request(validator, request, failsafe=None): + if failsafe is None: + failsafe = () + result = validator.validate(request) + try: + result.raise_for_errors() + except failsafe: + pass + return result + + +validate_parameters = partial(validate_request, failsafe=ERRORS_BODY) +validate_body = partial(validate_request, failsafe=ERRORS_PARAMETERS) + + +def spec_validate_parameters(spec, request, request_factory=None): + if request_factory is not None: + request = request_factory(request) + + validator = RequestValidator(spec) + result = validate_parameters(validator, request) + + return result.parameters + + +def spec_validate_body(spec, request, request_factory=None): + if request_factory is not None: + request = request_factory(request) + + validator = RequestValidator(spec) + result = validate_body(validator, request) + + return result.body diff --git a/openapi_core/validation/response/shortcuts.py b/openapi_core/validation/response/shortcuts.py new file mode 100644 index 00000000..39bf0815 --- /dev/null +++ b/openapi_core/validation/response/shortcuts.py @@ -0,0 +1,23 @@ +"""OpenAPI core validation response shortcuts module""" +from openapi_core.validation.response.validators import ResponseValidator + + +def validate_response(validator, request, response): + result = validator.validate(request, response) + result.raise_for_errors() + return result + + +def spec_validate_data( + spec, request, response, + request_factory=None, + response_factory=None): + if request_factory is not None: + request = request_factory(request) + if response_factory is not None: + response = response_factory(response) + + validator = ResponseValidator(spec) + result = validate_response(validator, request, response) + + return result.data diff --git a/tests/unit/test_shortcuts.py b/tests/unit/test_shortcuts.py deleted file mode 100644 index c5f1a7d8..00000000 --- a/tests/unit/test_shortcuts.py +++ /dev/null @@ -1,219 +0,0 @@ -import mock - -import pytest - -from openapi_core.shortcuts import ( - validate_parameters, validate_body, validate_data, -) - - -class ResultMock(object): - - def __init__( - self, body=None, parameters=None, data=None, error_to_raise=None): - self.body = body - self.parameters = parameters - self.data = data - self.error_to_raise = error_to_raise - - def raise_for_errors(self): - if self.error_to_raise is not None: - raise self.error_to_raise - - if self.parameters is not None: - return self.parameters - - if self.data is not None: - return self.data - - -class FactoryClassMock(object): - - _instances = {} - - def __new__(cls, obj): - if obj not in cls._instances: - cls._instances[obj] = object.__new__(cls) - return cls._instances[obj] - - def __init__(self, obj): - self.obj = obj - - -class TestValidateParameters(object): - - @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_no_request_factory(self, mock_validate): - spec = mock.sentinel.spec - request = mock.sentinel.request - parameters = mock.sentinel.parameters - mock_validate.return_value = ResultMock(parameters=parameters) - - result = validate_parameters(spec, request) - - assert result == parameters - mock_validate.aasert_called_once_with(request) - - @mock.patch('openapi_core.shortcuts.RequestValidator.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) - - with pytest.raises(ValueError): - validate_parameters(spec, request) - - mock_validate.aasert_called_once_with(request) - - @mock.patch('openapi_core.shortcuts.RequestValidator.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_factory = FactoryClassMock - - result = validate_parameters(spec, request, request_factory) - - assert result == parameters - mock_validate.assert_called_once_with( - FactoryClassMock(request), - ) - - @mock.patch('openapi_core.shortcuts.RequestValidator.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_factory = FactoryClassMock - - with pytest.raises(ValueError): - validate_parameters(spec, request, request_factory) - - mock_validate.assert_called_once_with( - FactoryClassMock(request), - ) - - -class TestValidateBody(object): - - @mock.patch('openapi_core.shortcuts.RequestValidator.validate') - def test_no_request_factory(self, mock_validate): - spec = mock.sentinel.spec - request = mock.sentinel.request - body = mock.sentinel.body - mock_validate.return_value = ResultMock(body=body) - - result = validate_body(spec, request) - - assert result == body - mock_validate.aasert_called_once_with(request) - - @mock.patch('openapi_core.shortcuts.RequestValidator.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) - - with pytest.raises(ValueError): - validate_body(spec, request) - - mock_validate.aasert_called_once_with(request) - - @mock.patch('openapi_core.shortcuts.RequestValidator.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_factory = FactoryClassMock - - result = validate_body(spec, request, request_factory) - - assert result == body - mock_validate.assert_called_once_with( - FactoryClassMock(request), - ) - - @mock.patch('openapi_core.shortcuts.RequestValidator.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_factory = FactoryClassMock - - with pytest.raises(ValueError): - validate_body(spec, request, request_factory) - - mock_validate.assert_called_once_with( - FactoryClassMock(request), - ) - - -class TestvalidateData(object): - - @mock.patch('openapi_core.shortcuts.ResponseValidator.validate') - def test_no_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) - - result = validate_data(spec, request, response) - - assert result == data - mock_validate.aasert_called_once_with(request, response) - - @mock.patch('openapi_core.shortcuts.ResponseValidator.validate') - def test_no_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) - - with pytest.raises(ValueError): - validate_data(spec, request, response) - - mock_validate.aasert_called_once_with(request, response) - - @mock.patch('openapi_core.shortcuts.ResponseValidator.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_factory = FactoryClassMock - response_factory = FactoryClassMock - - result = validate_data( - spec, request, response, - request_factory, response_factory, - ) - - assert result == data - mock_validate.assert_called_once_with( - FactoryClassMock(request), - FactoryClassMock(response), - ) - - @mock.patch('openapi_core.shortcuts.ResponseValidator.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_factory = FactoryClassMock - response_factory = FactoryClassMock - - with pytest.raises(ValueError): - validate_data( - spec, request, response, - request_factory, response_factory, - ) - - mock_validate.assert_called_once_with( - FactoryClassMock(request), - FactoryClassMock(response), - ) diff --git a/tests/unit/validation/test_request_shortcuts.py b/tests/unit/validation/test_request_shortcuts.py new file mode 100644 index 00000000..7d3f4eb5 --- /dev/null +++ b/tests/unit/validation/test_request_shortcuts.py @@ -0,0 +1,135 @@ +import mock + +import pytest + +from openapi_core.testing.datatypes import ResultMock +from openapi_core.testing.factories import FactoryClassMock +from openapi_core.validation.request.shortcuts import ( + spec_validate_parameters, spec_validate_body, +) + + +class TestSpecValidateParameters(object): + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.validate' + ) + def test_no_request_factory(self, mock_validate): + spec = mock.sentinel.spec + request = mock.sentinel.request + parameters = mock.sentinel.parameters + mock_validate.return_value = ResultMock(parameters=parameters) + + result = spec_validate_parameters(spec, request) + + assert result == parameters + mock_validate.aasert_called_once_with(request) + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.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) + + with pytest.raises(ValueError): + spec_validate_parameters(spec, request) + + mock_validate.aasert_called_once_with(request) + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.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_factory = FactoryClassMock + + result = spec_validate_parameters(spec, request, request_factory) + + assert result == parameters + mock_validate.assert_called_once_with( + FactoryClassMock(request), + ) + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.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_factory = FactoryClassMock + + with pytest.raises(ValueError): + spec_validate_parameters(spec, request, request_factory) + + mock_validate.assert_called_once_with( + FactoryClassMock(request), + ) + + +class TestSpecValidateBody(object): + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.validate' + ) + def test_no_request_factory(self, mock_validate): + spec = mock.sentinel.spec + request = mock.sentinel.request + body = mock.sentinel.body + mock_validate.return_value = ResultMock(body=body) + + result = spec_validate_body(spec, request) + + assert result == body + mock_validate.aasert_called_once_with(request) + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.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) + + with pytest.raises(ValueError): + spec_validate_body(spec, request) + + mock_validate.aasert_called_once_with(request) + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.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_factory = FactoryClassMock + + result = spec_validate_body(spec, request, request_factory) + + assert result == body + mock_validate.assert_called_once_with( + FactoryClassMock(request), + ) + + @mock.patch( + 'openapi_core.validation.request.shortcuts.RequestValidator.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_factory = FactoryClassMock + + with pytest.raises(ValueError): + spec_validate_body(spec, request, request_factory) + + mock_validate.assert_called_once_with( + FactoryClassMock(request), + ) diff --git a/tests/unit/validation/test_response_shortcuts.py b/tests/unit/validation/test_response_shortcuts.py new file mode 100644 index 00000000..9f2ded39 --- /dev/null +++ b/tests/unit/validation/test_response_shortcuts.py @@ -0,0 +1,84 @@ +import mock + +import pytest + +from openapi_core.testing.datatypes import ResultMock +from openapi_core.testing.factories import FactoryClassMock +from openapi_core.validation.response.shortcuts import spec_validate_data + + +class TestSpecValidateData(object): + + @mock.patch( + 'openapi_core.validation.response.shortcuts.ResponseValidator.validate' + ) + def test_no_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) + + result = spec_validate_data(spec, request, response) + + assert result == data + mock_validate.aasert_called_once_with(request, response) + + @mock.patch( + 'openapi_core.validation.response.shortcuts.ResponseValidator.validate' + ) + def test_no_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) + + with pytest.raises(ValueError): + spec_validate_data(spec, request, response) + + mock_validate.aasert_called_once_with(request, response) + + @mock.patch( + 'openapi_core.validation.response.shortcuts.ResponseValidator.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_factory = FactoryClassMock + response_factory = FactoryClassMock + + result = spec_validate_data( + spec, request, response, + request_factory, response_factory, + ) + + assert result == data + mock_validate.assert_called_once_with( + FactoryClassMock(request), + FactoryClassMock(response), + ) + + @mock.patch( + 'openapi_core.validation.response.shortcuts.ResponseValidator.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_factory = FactoryClassMock + response_factory = FactoryClassMock + + with pytest.raises(ValueError): + spec_validate_data( + spec, request, response, + request_factory, response_factory, + ) + + mock_validate.assert_called_once_with( + FactoryClassMock(request), + FactoryClassMock(response), + )