Skip to content

Commit c48b731

Browse files
committed
Requests integration
1 parent 2f91ea3 commit c48b731

File tree

15 files changed

+337
-27
lines changed

15 files changed

+337
-27
lines changed

README.rst

+30
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,36 @@ Pyramid
291291

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

294+
Requests
295+
********
296+
297+
This section describes integration with `Requests <https://requests.readthedocs.io>`__ library.
298+
299+
Low level
300+
=========
301+
302+
For Requests you can use RequestsOpenAPIRequest a Requests request factory:
303+
304+
.. code-block:: python
305+
306+
from openapi_core.validation.request.validators import RequestValidator
307+
from openapi_core.contrib.requests import RequestsOpenAPIRequest
308+
309+
openapi_request = RequestsOpenAPIRequest(requests_request)
310+
validator = RequestValidator(spec)
311+
result = validator.validate(openapi_request)
312+
313+
You can use RequestsOpenAPIResponse as a Requests response factory:
314+
315+
.. code-block:: python
316+
317+
from openapi_core.validation.response.validators import ResponseValidator
318+
from openapi_core.contrib.requests import RequestsOpenAPIResponse
319+
320+
openapi_response = RequestsOpenAPIResponse(requests_response)
321+
validator = ResponseValidator(spec)
322+
result = validator.validate(openapi_request, openapi_response)
323+
294324
Related projects
295325
################
296326
* `openapi-spec-validator <https://github.com/p1c2u/openapi-spec-validator>`__
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from openapi_core.contrib.requests.requests import (
2+
RequestsOpenAPIRequestFactory,
3+
)
4+
from openapi_core.contrib.requests.responses import (
5+
RequestsOpenAPIResponseFactory,
6+
)
7+
8+
# backward compatibility
9+
RequestsOpenAPIRequest = RequestsOpenAPIRequestFactory.create
10+
RequestsOpenAPIResponse = RequestsOpenAPIResponseFactory.create
11+
12+
__all__ = [
13+
'RequestsOpenAPIRequestFactory', 'RequestsOpenAPIResponseFactory',
14+
'RequestsOpenAPIRequest', 'RequestsOpenAPIResponse',
15+
]
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""OpenAPI core contrib requests requests module"""
2+
from werkzeug.datastructures import ImmutableMultiDict
3+
4+
from openapi_core.validation.request.datatypes import (
5+
RequestParameters, OpenAPIRequest,
6+
)
7+
8+
9+
class RequestsOpenAPIRequestFactory(object):
10+
11+
@classmethod
12+
def create(cls, request):
13+
method = request.method.lower()
14+
15+
cookie = request.cookies or {}
16+
17+
# gets deduced by path finder against spec
18+
path = {}
19+
20+
mimetype = request.headers.get('Accept') or \
21+
request.headers.get('Content-Type')
22+
parameters = RequestParameters(
23+
query=ImmutableMultiDict(request.params),
24+
header=request.headers,
25+
cookie=cookie,
26+
path=path,
27+
)
28+
return OpenAPIRequest(
29+
full_url_pattern=request.url,
30+
method=method,
31+
parameters=parameters,
32+
body=request.data,
33+
mimetype=mimetype,
34+
)
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""OpenAPI core contrib requests responses module"""
2+
from openapi_core.validation.response.datatypes import OpenAPIResponse
3+
4+
5+
class RequestsOpenAPIResponseFactory(object):
6+
7+
@classmethod
8+
def create(cls, response):
9+
mimetype = response.headers.get('Content-Type')
10+
return OpenAPIResponse(
11+
data=response.raw,
12+
status_code=response.status_code,
13+
mimetype=mimetype,
14+
)

openapi_core/validation/request/datatypes.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ class RequestParameters(object):
1010
"""OpenAPI request parameters dataclass.
1111
1212
Attributes:
13-
path
14-
Path parameters as dict.
1513
query
1614
Query string parameters as MultiDict. Must support getlist method.
1715
header
1816
Request headers as dict.
1917
cookie
2018
Request cookies as dict.
19+
path
20+
Path parameters as dict. Gets resolved against spec if empty.
2121
"""
22-
path = attr.ib(factory=dict)
2322
query = attr.ib(factory=ImmutableMultiDict)
2423
header = attr.ib(factory=dict)
2524
cookie = attr.ib(factory=dict)
25+
path = attr.ib(factory=dict)
2626

2727
def __getitem__(self, location):
2828
return getattr(self, location)
@@ -63,3 +63,6 @@ class RequestValidationResult(BaseValidationResult):
6363
body = attr.ib(default=None)
6464
parameters = attr.ib(factory=RequestParameters)
6565
security = attr.ib(default=None)
66+
server = attr.ib(default=None)
67+
path = attr.ib(default=None)
68+
operation = attr.ib(default=None)

openapi_core/validation/request/validators.py

+24-9
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,18 @@ class RequestValidator(BaseValidator):
2626

2727
def validate(self, request):
2828
try:
29-
path, operation, _, _, _ = self._find_path(request)
29+
path, operation, _, path_result, _ = self._find_path(request)
3030
# don't process if operation errors
3131
except PathError as exc:
32-
return RequestValidationResult([exc, ], None, None, None)
32+
return RequestValidationResult(errors=[exc, ])
3333

3434
try:
3535
security = self._get_security(request, operation)
3636
except InvalidSecurity as exc:
37-
return RequestValidationResult([exc, ], None, None, None)
37+
return RequestValidationResult(errors=[exc, ])
3838

39+
request.parameters.path = request.parameters.path or \
40+
path_result.variables
3941
params, params_errors = self._get_parameters(
4042
request, chain(
4143
iteritems(operation.parameters),
@@ -46,30 +48,43 @@ def validate(self, request):
4648
body, body_errors = self._get_body(request, operation)
4749

4850
errors = params_errors + body_errors
49-
return RequestValidationResult(errors, body, params, security)
51+
return RequestValidationResult(
52+
errors=errors,
53+
body=body,
54+
parameters=params,
55+
security=security,
56+
)
5057

5158
def _validate_parameters(self, request):
5259
try:
53-
path, operation, _, _, _ = self._find_path(request)
60+
path, operation, _, path_result, _ = self._find_path(request)
5461
except PathError as exc:
55-
return RequestValidationResult([exc, ], None, None)
62+
return RequestValidationResult(errors=[exc, ])
5663

64+
request.parameters.path = request.parameters.path or \
65+
path_result.variables
5766
params, params_errors = self._get_parameters(
5867
request, chain(
5968
iteritems(operation.parameters),
6069
iteritems(path.parameters)
6170
)
6271
)
63-
return RequestValidationResult(params_errors, None, params, None)
72+
return RequestValidationResult(
73+
errors=params_errors,
74+
parameters=params,
75+
)
6476

6577
def _validate_body(self, request):
6678
try:
6779
_, operation, _, _, _ = self._find_path(request)
6880
except PathError as exc:
69-
return RequestValidationResult([exc, ], None, None)
81+
return RequestValidationResult(errors=[exc, ])
7082

7183
body, body_errors = self._get_body(request, operation)
72-
return RequestValidationResult(body_errors, body, None, None)
84+
return RequestValidationResult(
85+
errors=body_errors,
86+
body=body,
87+
)
7388

7489
def _get_security(self, request, operation):
7590
security = operation.security or self.spec.security

openapi_core/validation/response/validators.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,26 @@ def validate(self, request, response):
2121
_, operation, _, _, _ = self._find_path(request)
2222
# don't process if operation errors
2323
except PathError as exc:
24-
return ResponseValidationResult([exc, ], None, None)
24+
return ResponseValidationResult(errors=[exc, ])
2525

2626
try:
2727
operation_response = self._get_operation_response(
2828
operation, response)
2929
# don't process if operation errors
3030
except InvalidResponse as exc:
31-
return ResponseValidationResult([exc, ], None, None)
31+
return ResponseValidationResult(errors=[exc, ])
3232

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

3535
headers, headers_errors = self._get_headers(
3636
response, operation_response)
3737

3838
errors = data_errors + headers_errors
39-
return ResponseValidationResult(errors, data, headers)
39+
return ResponseValidationResult(
40+
errors=errors,
41+
data=data,
42+
headers=headers,
43+
)
4044

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

5155
try:
5256
operation_response = self._get_operation_response(
5357
operation, response)
5458
# don't process if operation errors
5559
except InvalidResponse as exc:
56-
return ResponseValidationResult([exc, ], None, None)
60+
return ResponseValidationResult(errors=[exc, ])
5761

5862
data, data_errors = self._get_data(response, operation_response)
59-
return ResponseValidationResult(data_errors, data, None)
63+
return ResponseValidationResult(
64+
errors=data_errors,
65+
data=data,
66+
)
6067

6168
def _get_data(self, response, operation_response):
6269
if not operation_response.content:

requirements_dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ pytest-flake8
44
pytest-cov==2.5.1
55
flask
66
django==2.2.9; python_version>="3.0"
7+
requests==2.22.0
78
webob

setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ exclude =
4949
[options.extras_require]
5050
django = django>=2.2; python_version>="3.0"
5151
flask = flask
52+
requests = requests
5253

5354
[tool:pytest]
5455
addopts = -sv --flake8 --junitxml reports/junit.xml --cov openapi_core --cov-report term-missing --cov-report xml:reports/coverage.xml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import pytest
2+
from requests.models import Request, Response
3+
from requests.structures import CaseInsensitiveDict
4+
from six.moves.urllib.parse import urljoin, parse_qs
5+
6+
7+
@pytest.fixture
8+
def request_factory():
9+
schema = 'http'
10+
server_name = 'localhost'
11+
12+
def create_request(method, path, subdomain=None, query_string=''):
13+
base_url = '://'.join([schema, server_name])
14+
url = urljoin(base_url, path)
15+
params = parse_qs(query_string)
16+
headers = {
17+
'Content-Type': 'application/json',
18+
}
19+
return Request(method, url, params=params, headers=headers)
20+
return create_request
21+
22+
23+
@pytest.fixture
24+
def response_factory():
25+
def create_response(
26+
data, status_code=200, content_type='application/json'):
27+
resp = Response()
28+
resp.headers = CaseInsensitiveDict({
29+
'Content-Type': content_type,
30+
})
31+
resp.status_code = status_code
32+
resp.raw = data
33+
return resp
34+
return create_response
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
openapi: "3.0.0"
2+
info:
3+
title: Basic OpenAPI specification used with requests integration tests
4+
version: "0.1"
5+
servers:
6+
- url: 'http://localhost'
7+
paths:
8+
'/browse/{id}/':
9+
parameters:
10+
- name: id
11+
in: path
12+
required: true
13+
description: the ID of the resource to retrieve
14+
schema:
15+
type: integer
16+
get:
17+
responses:
18+
200:
19+
description: Return the resource.
20+
content:
21+
application/json:
22+
schema:
23+
type: object
24+
required:
25+
- data
26+
properties:
27+
data:
28+
type: string
29+
default:
30+
description: Return errors.
31+
content:
32+
application/json:
33+
schema:
34+
type: object
35+
required:
36+
- errors
37+
properties:
38+
errors:
39+
type: array
40+
items:
41+
type: object
42+
properties:
43+
title:
44+
type: string
45+
code:
46+
type: string
47+
message:
48+
type: string

0 commit comments

Comments
 (0)