Skip to content

Requests integration #209

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 1 commit into from
Mar 3, 2020
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
30 changes: 30 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,36 @@ Pyramid

See `pyramid_openapi3 <https://github.com/niteoweb/pyramid_openapi3>`_ project.

Requests
********

This section describes integration with `Requests <https://requests.readthedocs.io>`__ library.

Low level
=========

For Requests you can use RequestsOpenAPIRequest a Requests request factory:

.. code-block:: python

from openapi_core.validation.request.validators import RequestValidator
from openapi_core.contrib.requests import RequestsOpenAPIRequest

openapi_request = RequestsOpenAPIRequest(requests_request)
validator = RequestValidator(spec)
result = validator.validate(openapi_request)

You can use RequestsOpenAPIResponse as a Requests response factory:

.. code-block:: python

from openapi_core.validation.response.validators import ResponseValidator
from openapi_core.contrib.requests import RequestsOpenAPIResponse

openapi_response = RequestsOpenAPIResponse(requests_response)
validator = ResponseValidator(spec)
result = validator.validate(openapi_request, openapi_response)

Related projects
################
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__
Expand Down
15 changes: 15 additions & 0 deletions openapi_core/contrib/requests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from openapi_core.contrib.requests.requests import (
RequestsOpenAPIRequestFactory,
)
from openapi_core.contrib.requests.responses import (
RequestsOpenAPIResponseFactory,
)

# backward compatibility
RequestsOpenAPIRequest = RequestsOpenAPIRequestFactory.create
RequestsOpenAPIResponse = RequestsOpenAPIResponseFactory.create

__all__ = [
'RequestsOpenAPIRequestFactory', 'RequestsOpenAPIResponseFactory',
'RequestsOpenAPIRequest', 'RequestsOpenAPIResponse',
]
34 changes: 34 additions & 0 deletions openapi_core/contrib/requests/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""OpenAPI core contrib requests requests module"""
from werkzeug.datastructures import ImmutableMultiDict

from openapi_core.validation.request.datatypes import (
RequestParameters, OpenAPIRequest,
)


class RequestsOpenAPIRequestFactory(object):

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

cookie = request.cookies or {}

# gets deduced by path finder against spec
path = {}

mimetype = request.headers.get('Accept') or \
request.headers.get('Content-Type')
parameters = RequestParameters(
query=ImmutableMultiDict(request.params),
header=request.headers,
cookie=cookie,
path=path,
)
return OpenAPIRequest(
full_url_pattern=request.url,
method=method,
parameters=parameters,
body=request.data,
mimetype=mimetype,
)
14 changes: 14 additions & 0 deletions openapi_core/contrib/requests/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""OpenAPI core contrib requests responses module"""
from openapi_core.validation.response.datatypes import OpenAPIResponse


class RequestsOpenAPIResponseFactory(object):

@classmethod
def create(cls, response):
mimetype = response.headers.get('Content-Type')
return OpenAPIResponse(
data=response.raw,
status_code=response.status_code,
mimetype=mimetype,
)
9 changes: 6 additions & 3 deletions openapi_core/validation/request/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ class RequestParameters(object):
"""OpenAPI request parameters dataclass.

Attributes:
path
Path parameters as dict.
query
Query string parameters as MultiDict. Must support getlist method.
header
Request headers as dict.
cookie
Request cookies as dict.
path
Path parameters as dict. Gets resolved against spec if empty.
"""
path = attr.ib(factory=dict)
query = attr.ib(factory=ImmutableMultiDict)
header = attr.ib(factory=dict)
cookie = attr.ib(factory=dict)
path = attr.ib(factory=dict)

def __getitem__(self, location):
return getattr(self, location)
Expand Down Expand Up @@ -63,3 +63,6 @@ class RequestValidationResult(BaseValidationResult):
body = attr.ib(default=None)
parameters = attr.ib(factory=RequestParameters)
security = attr.ib(default=None)
server = attr.ib(default=None)
path = attr.ib(default=None)
operation = attr.ib(default=None)
33 changes: 24 additions & 9 deletions openapi_core/validation/request/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ class RequestValidator(BaseValidator):

def validate(self, request):
try:
path, operation, _, _, _ = self._find_path(request)
path, operation, _, path_result, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return RequestValidationResult([exc, ], None, None, None)
return RequestValidationResult(errors=[exc, ])

try:
security = self._get_security(request, operation)
except InvalidSecurity as exc:
return RequestValidationResult([exc, ], None, None, None)
return RequestValidationResult(errors=[exc, ])

request.parameters.path = request.parameters.path or \
path_result.variables
params, params_errors = self._get_parameters(
request, chain(
iteritems(operation.parameters),
Expand All @@ -46,30 +48,43 @@ def validate(self, request):
body, body_errors = self._get_body(request, operation)

errors = params_errors + body_errors
return RequestValidationResult(errors, body, params, security)
return RequestValidationResult(
errors=errors,
body=body,
parameters=params,
security=security,
)

def _validate_parameters(self, request):
try:
path, operation, _, _, _ = self._find_path(request)
path, operation, _, path_result, _ = self._find_path(request)
except PathError as exc:
return RequestValidationResult([exc, ], None, None)
return RequestValidationResult(errors=[exc, ])

request.parameters.path = request.parameters.path or \
path_result.variables
params, params_errors = self._get_parameters(
request, chain(
iteritems(operation.parameters),
iteritems(path.parameters)
)
)
return RequestValidationResult(params_errors, None, params, None)
return RequestValidationResult(
errors=params_errors,
parameters=params,
)

def _validate_body(self, request):
try:
_, operation, _, _, _ = self._find_path(request)
except PathError as exc:
return RequestValidationResult([exc, ], None, None)
return RequestValidationResult(errors=[exc, ])

body, body_errors = self._get_body(request, operation)
return RequestValidationResult(body_errors, body, None, None)
return RequestValidationResult(
errors=body_errors,
body=body,
)

def _get_security(self, request, operation):
security = operation.security or self.spec.security
Expand Down
19 changes: 13 additions & 6 deletions openapi_core/validation/response/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,26 @@ def validate(self, request, response):
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseValidationResult([exc, ], None, None)
return ResponseValidationResult(errors=[exc, ])

try:
operation_response = self._get_operation_response(
operation, response)
# don't process if operation errors
except InvalidResponse as exc:
return ResponseValidationResult([exc, ], None, None)
return ResponseValidationResult(errors=[exc, ])

data, data_errors = self._get_data(response, operation_response)

headers, headers_errors = self._get_headers(
response, operation_response)

errors = data_errors + headers_errors
return ResponseValidationResult(errors, data, headers)
return ResponseValidationResult(
errors=errors,
data=data,
headers=headers,
)

def _get_operation_response(self, operation, response):
return operation.get_response(str(response.status_code))
Expand All @@ -46,17 +50,20 @@ def _validate_data(self, request, response):
_, operation, _, _, _ = self._find_path(request)
# don't process if operation errors
except PathError as exc:
return ResponseValidationResult([exc, ], None, None)
return ResponseValidationResult(errors=[exc, ])

try:
operation_response = self._get_operation_response(
operation, response)
# don't process if operation errors
except InvalidResponse as exc:
return ResponseValidationResult([exc, ], None, None)
return ResponseValidationResult(errors=[exc, ])

data, data_errors = self._get_data(response, operation_response)
return ResponseValidationResult(data_errors, data, None)
return ResponseValidationResult(
errors=data_errors,
data=data,
)

def _get_data(self, response, operation_response):
if not operation_response.content:
Expand Down
1 change: 1 addition & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pytest-flake8
pytest-cov==2.5.1
flask
django==2.2.10; python_version>="3.0"
requests==2.22.0
webob
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ exclude =
[options.extras_require]
django = django>=2.2; python_version>="3.0"
flask = flask
requests = requests

[tool:pytest]
addopts = -sv --flake8 --junitxml reports/junit.xml --cov openapi_core --cov-report term-missing --cov-report xml:reports/coverage.xml
34 changes: 34 additions & 0 deletions tests/integration/contrib/requests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import pytest
from requests.models import Request, Response
from requests.structures import CaseInsensitiveDict
from six.moves.urllib.parse import urljoin, parse_qs


@pytest.fixture
def request_factory():
schema = 'http'
server_name = 'localhost'

def create_request(method, path, subdomain=None, query_string=''):
base_url = '://'.join([schema, server_name])
url = urljoin(base_url, path)
params = parse_qs(query_string)
headers = {
'Content-Type': 'application/json',
}
return Request(method, url, params=params, headers=headers)
return create_request


@pytest.fixture
def response_factory():
def create_response(
data, status_code=200, content_type='application/json'):
resp = Response()
resp.headers = CaseInsensitiveDict({
'Content-Type': content_type,
})
resp.status_code = status_code
resp.raw = data
return resp
return create_response
48 changes: 48 additions & 0 deletions tests/integration/contrib/requests/data/v3.0/requests_factory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
openapi: "3.0.0"
info:
title: Basic OpenAPI specification used with requests integration tests
version: "0.1"
servers:
- url: 'http://localhost'
paths:
'/browse/{id}/':
parameters:
- name: id
in: path
required: true
description: the ID of the resource to retrieve
schema:
type: integer
get:
responses:
200:
description: Return the resource.
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
type: string
default:
description: Return errors.
content:
application/json:
schema:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
properties:
title:
type: string
code:
type: string
message:
type: string
Loading