Skip to content

OpenAPI request/response factories introduction #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -81,27 +83,27 @@ 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

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

Expand Down Expand Up @@ -136,25 +138,27 @@ 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

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)
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,
request_factory=FlaskOpenAPIRequest,
response_factory=FlaskOpenAPIResponse)

Related projects
================
Expand Down
File renamed without changes.
11 changes: 11 additions & 0 deletions openapi_core/contrib/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from openapi_core.contrib.flask.requests import FlaskOpenAPIRequestFactory
from openapi_core.contrib.flask.responses import FlaskOpenAPIResponseFactory

# backward compatibility
FlaskOpenAPIRequest = FlaskOpenAPIRequestFactory.create
FlaskOpenAPIResponse = FlaskOpenAPIResponseFactory.create

__all__ = [
'FlaskOpenAPIRequestFactory', 'FlaskOpenAPIResponseFactory',
'FlaskOpenAPIRequest', 'FlaskOpenAPIResponse',
]
39 changes: 39 additions & 0 deletions openapi_core/contrib/flask/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""OpenAPI core contrib flask requests module"""
import re

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 FlaskOpenAPIRequestFactory(object):

path_regex = re.compile(PATH_PARAMETER_PATTERN)

@classmethod
def create(cls, request):
method = request.method.lower()

if request.url_rule is None:
path_pattern = request.path
else:
path_pattern = cls.path_regex.sub(r'{\1}', request.url_rule.rule)

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,
)
15 changes: 15 additions & 0 deletions openapi_core/contrib/flask/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""OpenAPI core contrib flask responses module"""
import re

from openapi_core.validation.response.datatypes import OpenAPIResponse


class FlaskOpenAPIResponseFactory(object):

@classmethod
def create(cls, response):
return OpenAPIResponse(
data=response.data,
status_code=response._status_code,
mimetype=response.mimetype,
)
24 changes: 12 additions & 12 deletions openapi_core/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions openapi_core/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -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',
]
45 changes: 45 additions & 0 deletions openapi_core/testing/mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""OpenAPI core testing mock module"""
from werkzeug.datastructures import ImmutableMultiDict

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,
)
25 changes: 24 additions & 1 deletion openapi_core/validation/request/datatypes.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
"""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)

def __getitem__(self, location):
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)
Expand Down
7 changes: 4 additions & 3 deletions openapi_core/validation/request/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def validate(self, request):
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
Expand Down Expand Up @@ -79,9 +79,10 @@ def _get_parameters(self, request, params):
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 = []
Expand Down
9 changes: 9 additions & 0 deletions openapi_core/validation/response/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
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)
Expand Down
49 changes: 0 additions & 49 deletions openapi_core/wrappers/base.py

This file was deleted.

Loading