From 8a614de0bba6903515960e4f1fc946ebdb13398c Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 19 Oct 2019 12:01:50 +0100 Subject: [PATCH 1/4] Restructure flask and mock --- README.rst | 4 ++-- openapi_core/contrib/__init__.py | 0 openapi_core/contrib/flask/__init__.py | 4 ++++ .../flask.py => contrib/flask/requests.py} | 22 ++----------------- openapi_core/contrib/flask/responses.py | 22 +++++++++++++++++++ openapi_core/testing/__init__.py | 0 openapi_core/{wrappers => testing}/mock.py | 0 .../test_flask.py} | 7 +++--- tests/integration/validation/test_minimal.py | 2 +- tests/integration/validation/test_petstore.py | 2 +- .../integration/validation/test_validators.py | 2 +- 11 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 openapi_core/contrib/__init__.py create mode 100644 openapi_core/contrib/flask/__init__.py rename openapi_core/{wrappers/flask.py => contrib/flask/requests.py} (70%) create mode 100644 openapi_core/contrib/flask/responses.py create mode 100644 openapi_core/testing/__init__.py rename openapi_core/{wrappers => testing}/mock.py (100%) rename tests/integration/{test_wrappers.py => contrib/test_flask.py} (97%) diff --git a/README.rst b/README.rst index 7cd3101..30aa179 100644 --- a/README.rst +++ b/README.rst @@ -86,7 +86,7 @@ Request object should implement BaseOpenAPIRequest interface. You can use FlaskO .. code-block:: python from openapi_core.shortcuts import RequestValidator - from openapi_core.wrappers.flask import FlaskOpenAPIRequest + from openapi_core.contrib.flask import FlaskOpenAPIRequest openapi_request = FlaskOpenAPIRequest(flask_request) validator = RequestValidator(spec) @@ -141,7 +141,7 @@ Response object should implement BaseOpenAPIResponse interface. You can use Flas .. code-block:: python from openapi_core.shortcuts import ResponseValidator - from openapi_core.wrappers.flask import FlaskOpenAPIResponse + from openapi_core.contrib.flask import FlaskOpenAPIResponse openapi_response = FlaskOpenAPIResponse(flask_response) validator = ResponseValidator(spec) diff --git a/openapi_core/contrib/__init__.py b/openapi_core/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openapi_core/contrib/flask/__init__.py b/openapi_core/contrib/flask/__init__.py new file mode 100644 index 0000000..1c332e5 --- /dev/null +++ b/openapi_core/contrib/flask/__init__.py @@ -0,0 +1,4 @@ +from openapi_core.contrib.flask.requests import FlaskOpenAPIRequest +from openapi_core.contrib.flask.responses import FlaskOpenAPIResponse + +__all__ = ['FlaskOpenAPIRequest', 'FlaskOpenAPIResponse'] diff --git a/openapi_core/wrappers/flask.py b/openapi_core/contrib/flask/requests.py similarity index 70% rename from openapi_core/wrappers/flask.py rename to openapi_core/contrib/flask/requests.py index 14c330b..e5d260b 100644 --- a/openapi_core/wrappers/flask.py +++ b/openapi_core/contrib/flask/requests.py @@ -1,7 +1,7 @@ -"""OpenAPI core wrappers module""" +"""OpenAPI core contrib flask requests module""" import re -from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse +from openapi_core.wrappers.base import BaseOpenAPIRequest # http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>' @@ -49,21 +49,3 @@ class FlaskOpenAPIRequest(BaseOpenAPIRequest): @property def mimetype(self): return self.request.mimetype - - -class FlaskOpenAPIResponse(BaseOpenAPIResponse): - - 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 diff --git a/openapi_core/contrib/flask/responses.py b/openapi_core/contrib/flask/responses.py new file mode 100644 index 0000000..c2922a1 --- /dev/null +++ b/openapi_core/contrib/flask/responses.py @@ -0,0 +1,22 @@ +"""OpenAPI core contrib flask responses module""" +import re + +from openapi_core.wrappers.base import BaseOpenAPIResponse + + +class FlaskOpenAPIResponse(BaseOpenAPIResponse): + + 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 diff --git a/openapi_core/testing/__init__.py b/openapi_core/testing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openapi_core/wrappers/mock.py b/openapi_core/testing/mock.py similarity index 100% rename from openapi_core/wrappers/mock.py rename to openapi_core/testing/mock.py diff --git a/tests/integration/test_wrappers.py b/tests/integration/contrib/test_flask.py similarity index 97% rename from tests/integration/test_wrappers.py rename to tests/integration/contrib/test_flask.py index 6c4b7d1..15a9149 100644 --- a/tests/integration/test_wrappers.py +++ b/tests/integration/contrib/test_flask.py @@ -1,14 +1,15 @@ from flask.wrappers import Request, Response +import pytest from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict from werkzeug.routing import Map, Rule, Subdomain from werkzeug.test import create_environ -import pytest 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.wrappers.flask import (FlaskOpenAPIRequest, - FlaskOpenAPIResponse) +from openapi_core.contrib.flask import ( + FlaskOpenAPIRequest, FlaskOpenAPIResponse, +) @pytest.fixture diff --git a/tests/integration/validation/test_minimal.py b/tests/integration/validation/test_minimal.py index 936c27b..5eb832c 100644 --- a/tests/integration/validation/test_minimal.py +++ b/tests/integration/validation/test_minimal.py @@ -3,8 +3,8 @@ import pytest from openapi_core.schema.operations.exceptions import InvalidOperation from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.shortcuts import create_spec +from openapi_core.testing.mock import MockRequest from openapi_core.validation.request.validators import RequestValidator -from openapi_core.wrappers.mock import MockRequest class TestMinimal(object): diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index b07f7f0..9e46a7a 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -16,10 +16,10 @@ from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.exceptions import InvalidSchemaValue from openapi_core.schema.servers.exceptions import InvalidServer from openapi_core.shortcuts import create_spec +from openapi_core.testing.mock import MockRequest, MockResponse from openapi_core.validation.request.datatypes import RequestParameters from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator -from openapi_core.wrappers.mock import MockRequest, MockResponse class TestPetstore(object): diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index c696cf1..e323c13 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -17,10 +17,10 @@ from openapi_core.schema.responses.exceptions import ( ) from openapi_core.schema.servers.exceptions import InvalidServer from openapi_core.shortcuts import create_spec +from openapi_core.testing.mock import MockRequest, MockResponse from openapi_core.validation.request.datatypes import RequestParameters from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator -from openapi_core.wrappers.mock import MockRequest, MockResponse class TestRequestValidator(object): From cffc47c60a6942f8b0d4564aef3eb2e9b4a86cf3 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 19 Oct 2019 13:01:56 +0100 Subject: [PATCH 2/4] OpenAPI request and response datatypes --- openapi_core/testing/__init__.py | 10 ++ openapi_core/testing/mock.py | 61 +++++---- openapi_core/validation/request/datatypes.py | 25 +++- openapi_core/validation/request/validators.py | 7 +- openapi_core/validation/response/datatypes.py | 9 ++ tests/integration/validation/test_minimal.py | 2 +- tests/integration/validation/test_petstore.py | 126 +++++++++--------- .../integration/validation/test_validators.py | 2 +- 8 files changed, 148 insertions(+), 94 deletions(-) diff --git a/openapi_core/testing/__init__.py b/openapi_core/testing/__init__.py index e69de29..28b50ca 100644 --- a/openapi_core/testing/__init__.py +++ b/openapi_core/testing/__init__.py @@ -0,0 +1,10 @@ +"""OpenAPI core testing module""" +from openapi_core.testing.mock import MockRequestFactory, MockResponseFactory + +# backward compatibility +MockRequest = MockRequestFactory.create +MockResponse = MockResponseFactory.create + +__all__ = [ + 'MockRequestFactory', 'MockResponseFactory', 'MockRequest', 'MockResponse', +] diff --git a/openapi_core/testing/mock.py b/openapi_core/testing/mock.py index 9b03429..4ff0533 100644 --- a/openapi_core/testing/mock.py +++ b/openapi_core/testing/mock.py @@ -1,36 +1,45 @@ -"""OpenAPI core wrappers module""" +"""OpenAPI core testing mock module""" from werkzeug.datastructures import ImmutableMultiDict -from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse +from openapi_core.validation.request.datatypes import ( + RequestParameters, OpenAPIRequest, +) +from openapi_core.validation.response.datatypes import OpenAPIResponse -class MockRequest(BaseOpenAPIRequest): +class MockRequestFactory(object): - def __init__( - self, host_url, method, path, path_pattern=None, args=None, + @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'): - self.host_url = host_url - self.path = path - self.path_pattern = path_pattern or path - self.method = method.lower() - - self.parameters = { - 'path': view_args or {}, - 'query': ImmutableMultiDict(args or []), - 'header': headers or {}, - 'cookie': cookies or {}, - } - - self.body = data or '' - - self.mimetype = mimetype + 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 MockResponse(BaseOpenAPIResponse): +class MockResponseFactory(object): - def __init__(self, data, status_code=200, mimetype='application/json'): - self.data = data - - self.status_code = status_code - self.mimetype = mimetype + @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/datatypes.py b/openapi_core/validation/request/datatypes.py index 2e5b8ea..4158798 100644 --- a/openapi_core/validation/request/datatypes.py +++ b/openapi_core/validation/request/datatypes.py @@ -1,13 +1,17 @@ """OpenAPI core validation request datatypes module""" import attr +from werkzeug.datastructures import ImmutableMultiDict from openapi_core.validation.datatypes import BaseValidationResult +from six.moves.urllib.parse import urljoin + + @attr.s class RequestParameters(object): path = attr.ib(factory=dict) - query = attr.ib(factory=dict) + query = attr.ib(factory=ImmutableMultiDict) header = attr.ib(factory=dict) cookie = attr.ib(factory=dict) @@ -15,6 +19,25 @@ class RequestParameters(object): return getattr(self, location) +@attr.s +class OpenAPIRequest(object): + + host_url = attr.ib() + path = attr.ib() + path_pattern = attr.ib() + method = attr.ib() + + body = attr.ib() + + mimetype = attr.ib() + + parameters = attr.ib(factory=RequestParameters) + + @property + def full_url_pattern(self): + return urljoin(self.host_url, self.path_pattern) + + @attr.s class RequestValidationResult(BaseValidationResult): body = attr.ib(default=None) diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index a6d8e78..dd726d9 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -50,7 +50,7 @@ class RequestValidator(object): def _get_parameters(self, request, params): errors = [] seen = set() - parameters = RequestParameters() + locations = {} for param_name, param in params: if (param_name, param.location.value) in seen: # skip parameter already seen @@ -79,9 +79,10 @@ class RequestValidator(object): except OpenAPIMappingError as exc: errors.append(exc) else: - parameters[param.location.value][param_name] = unmarshalled + locations.setdefault(param.location.value, {}) + locations[param.location.value][param_name] = unmarshalled - return parameters, errors + return RequestParameters(**locations), errors def _get_body(self, request, operation): errors = [] diff --git a/openapi_core/validation/response/datatypes.py b/openapi_core/validation/response/datatypes.py index 38cfb07..c41f5a2 100644 --- a/openapi_core/validation/response/datatypes.py +++ b/openapi_core/validation/response/datatypes.py @@ -4,6 +4,15 @@ import attr from openapi_core.validation.datatypes import BaseValidationResult +@attr.s +class OpenAPIResponse(object): + + data = attr.ib() + status_code = attr.ib() + + mimetype = attr.ib() + + @attr.s class ResponseValidationResult(BaseValidationResult): data = attr.ib(default=None) diff --git a/tests/integration/validation/test_minimal.py b/tests/integration/validation/test_minimal.py index 5eb832c..00390aa 100644 --- a/tests/integration/validation/test_minimal.py +++ b/tests/integration/validation/test_minimal.py @@ -3,7 +3,7 @@ import pytest from openapi_core.schema.operations.exceptions import InvalidOperation from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.shortcuts import create_spec -from openapi_core.testing.mock import MockRequest +from openapi_core.testing import MockRequest from openapi_core.validation.request.validators import RequestValidator diff --git a/tests/integration/validation/test_petstore.py b/tests/integration/validation/test_petstore.py index 9e46a7a..0e06022 100644 --- a/tests/integration/validation/test_petstore.py +++ b/tests/integration/validation/test_petstore.py @@ -15,8 +15,10 @@ from openapi_core.schema.parameters.exceptions import ( from openapi_core.schema.schemas.enums import SchemaType from openapi_core.schema.schemas.exceptions import InvalidSchemaValue from openapi_core.schema.servers.exceptions import InvalidServer -from openapi_core.shortcuts import create_spec -from openapi_core.testing.mock import MockRequest, MockResponse +from openapi_core.shortcuts import ( + create_spec, validate_parameters, validate_body, +) +from openapi_core.testing import MockRequest, MockResponse from openapi_core.validation.request.datatypes import RequestParameters from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator @@ -64,8 +66,8 @@ class TestPetstore(object): path_pattern=path_pattern, args=query_params, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters( query={ @@ -100,8 +102,8 @@ class TestPetstore(object): path_pattern=path_pattern, args=query_params, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters( query={ @@ -146,8 +148,8 @@ class TestPetstore(object): path_pattern=path_pattern, args=query_params, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters( query={ @@ -197,8 +199,8 @@ class TestPetstore(object): path_pattern=path_pattern, args=query_params, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters( query={ @@ -235,8 +237,8 @@ class TestPetstore(object): path_pattern=path_pattern, args=query_params, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters( query={ @@ -274,9 +276,9 @@ class TestPetstore(object): ) with pytest.raises(InvalidParameterValue): - request.get_parameters(spec) + validate_parameters(spec, request) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -293,9 +295,9 @@ class TestPetstore(object): ) with pytest.raises(InvalidParameterValue): - request.get_parameters(spec) + validate_parameters(spec, request) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -308,9 +310,9 @@ class TestPetstore(object): ) with pytest.raises(MissingRequiredParameter): - request.get_parameters(spec) + validate_parameters(spec, request) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -327,8 +329,8 @@ class TestPetstore(object): ) with pytest.raises(EmptyParameterValue): - request.get_parameters(spec) - body = request.get_body(spec) + validate_parameters(spec, request) + body = validate_body(spec, request) assert body is None @@ -344,7 +346,7 @@ class TestPetstore(object): path_pattern=path_pattern, args=query_params, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( query={ @@ -354,7 +356,7 @@ class TestPetstore(object): } ) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -393,7 +395,7 @@ class TestPetstore(object): headers=headers, cookies=cookies, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( header={ @@ -404,7 +406,7 @@ class TestPetstore(object): }, ) - body = request.get_body(spec) + body = validate_body(spec, request) schemas = spec_dict['components']['schemas'] pet_model = schemas['PetCreate']['x-model'] @@ -453,7 +455,7 @@ class TestPetstore(object): headers=headers, cookies=cookies, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( header={ @@ -464,7 +466,7 @@ class TestPetstore(object): }, ) - body = request.get_body(spec) + body = validate_body(spec, request) schemas = spec_dict['components']['schemas'] pet_model = schemas['PetCreate']['x-model'] @@ -513,7 +515,7 @@ class TestPetstore(object): headers=headers, cookies=cookies, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( header={ @@ -524,7 +526,7 @@ class TestPetstore(object): }, ) - body = request.get_body(spec) + body = validate_body(spec, request) schemas = spec_dict['components']['schemas'] pet_model = schemas['PetCreate']['x-model'] @@ -561,7 +563,7 @@ class TestPetstore(object): headers=headers, cookies=cookies, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( header={ @@ -573,7 +575,7 @@ class TestPetstore(object): ) with pytest.raises(InvalidMediaTypeValue): - request.get_body(spec) + validate_body(spec, request) def test_post_cats_only_required_body(self, spec, spec_dict): host_url = 'http://petstore.swagger.io/v1' @@ -600,7 +602,7 @@ class TestPetstore(object): headers=headers, cookies=cookies, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( header={ @@ -611,7 +613,7 @@ class TestPetstore(object): }, ) - body = request.get_body(spec) + body = validate_body(spec, request) schemas = spec_dict['components']['schemas'] pet_model = schemas['PetCreate']['x-model'] @@ -641,7 +643,7 @@ class TestPetstore(object): headers=headers, cookies=cookies, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( header={ @@ -653,7 +655,7 @@ class TestPetstore(object): ) with pytest.raises(InvalidContentType): - request.get_body(spec) + validate_body(spec, request) def test_post_pets_missing_cookie(self, spec, spec_dict): host_url = 'http://petstore.swagger.io/v1' @@ -678,9 +680,9 @@ class TestPetstore(object): ) with pytest.raises(MissingRequiredParameter): - request.get_parameters(spec) + validate_parameters(spec, request) - body = request.get_body(spec) + body = validate_body(spec, request) schemas = spec_dict['components']['schemas'] pet_model = schemas['PetCreate']['x-model'] @@ -712,9 +714,9 @@ class TestPetstore(object): ) with pytest.raises(MissingRequiredParameter): - request.get_parameters(spec) + validate_parameters(spec, request) - body = request.get_body(spec) + body = validate_body(spec, request) schemas = spec_dict['components']['schemas'] pet_model = schemas['PetCreate']['x-model'] @@ -745,10 +747,10 @@ class TestPetstore(object): ) with pytest.raises(InvalidServer): - request.get_parameters(spec) + validate_parameters(spec, request) with pytest.raises(InvalidServer): - request.get_body(spec) + validate_body(spec, request) def test_get_pet(self, spec, response_validator): host_url = 'http://petstore.swagger.io/v1' @@ -761,7 +763,7 @@ class TestPetstore(object): path_pattern=path_pattern, view_args=view_args, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( path={ @@ -769,7 +771,7 @@ class TestPetstore(object): } ) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -806,7 +808,7 @@ class TestPetstore(object): path_pattern=path_pattern, view_args=view_args, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( path={ @@ -814,7 +816,7 @@ class TestPetstore(object): } ) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -848,7 +850,7 @@ class TestPetstore(object): path_pattern=path_pattern, view_args=view_args, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters( path={ @@ -856,7 +858,7 @@ class TestPetstore(object): } ) - body = request.get_body(spec) + body = validate_body(spec, request) assert body is None @@ -877,8 +879,8 @@ class TestPetstore(object): path_pattern=path_pattern, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters() assert body is None @@ -908,12 +910,12 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters() with pytest.raises(InvalidMediaTypeValue): - request.get_body(spec) + validate_body(spec, request) def test_post_tags_empty_body(self, spec, spec_dict): host_url = 'http://petstore.swagger.io/v1' @@ -926,12 +928,12 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters() with pytest.raises(InvalidMediaTypeValue): - request.get_body(spec) + validate_body(spec, request) def test_post_tags_wrong_property_type(self, spec): host_url = 'http://petstore.swagger.io/v1' @@ -944,12 +946,12 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) assert parameters == RequestParameters() with pytest.raises(InvalidMediaTypeValue): - request.get_body(spec) + validate_body(spec, request) def test_post_tags_additional_properties( self, spec, response_validator): @@ -966,8 +968,8 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters() assert isinstance(body, BaseModel) @@ -1012,8 +1014,8 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters() assert isinstance(body, BaseModel) @@ -1059,8 +1061,8 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) - body = request.get_body(spec) + parameters = validate_parameters(spec, request) + body = validate_body(spec, request) assert parameters == RequestParameters() assert isinstance(body, BaseModel) @@ -1106,9 +1108,9 @@ class TestPetstore(object): path_pattern=path_pattern, data=data, ) - parameters = request.get_parameters(spec) + parameters = validate_parameters(spec, request) with pytest.raises(InvalidMediaTypeValue): - request.get_body(spec) + validate_body(spec, request) assert parameters == RequestParameters() diff --git a/tests/integration/validation/test_validators.py b/tests/integration/validation/test_validators.py index e323c13..449fdf4 100644 --- a/tests/integration/validation/test_validators.py +++ b/tests/integration/validation/test_validators.py @@ -17,7 +17,7 @@ from openapi_core.schema.responses.exceptions import ( ) from openapi_core.schema.servers.exceptions import InvalidServer from openapi_core.shortcuts import create_spec -from openapi_core.testing.mock import MockRequest, MockResponse +from openapi_core.testing import MockRequest, MockResponse from openapi_core.validation.request.datatypes import RequestParameters from openapi_core.validation.request.validators import RequestValidator from openapi_core.validation.response.validators import ResponseValidator From 0bbf787423875e9ec34dead7f31c2d1733d04b97 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 19 Oct 2019 13:35:48 +0100 Subject: [PATCH 3/4] 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), ) From 76d508181d3fceec361a7896c33aa45f1e6ec204 Mon Sep 17 00:00:00 2001 From: p1c2u Date: Sat, 19 Oct 2019 13:59:16 +0100 Subject: [PATCH 4/4] README fixes --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 629aa18..9a414f4 100644 --- a/README.rst +++ b/README.rst @@ -66,8 +66,10 @@ and unmarshal request data from validation result .. code-block:: python - # get parameters dictionary with path, query, cookies and headers parameters + # get parameters object with path, query, cookies and headers parameters validated_params = result.parameters + # or specific parameters + validated_path_params = result.parameters.path # get body validated_body = result.body @@ -154,7 +156,9 @@ or simply specify response factory for shortcuts from openapi_core import validate_parameters, validate_body validated_data = validate_data( - spec, request, response, response_factory=FlaskOpenAPIResponse) + spec, request, response, + request_factory=FlaskOpenAPIRequest, + response_factory=FlaskOpenAPIResponse) Related projects ================