Skip to content

Commit 695f4e6

Browse files
authored
Merge pull request #271 from eyadgaran/master
use prepared request to format payload before converting
2 parents 6a222b9 + ff4a6c8 commit 695f4e6

File tree

4 files changed

+98
-18
lines changed

4 files changed

+98
-18
lines changed
+41-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""OpenAPI core contrib requests requests module"""
2+
from __future__ import absolute_import
23
from werkzeug.datastructures import ImmutableMultiDict
4+
from requests import Request
5+
from six.moves.urllib.parse import urlparse, parse_qs
36

47
from openapi_core.validation.request.datatypes import (
58
RequestParameters, OpenAPIRequest,
@@ -10,25 +13,56 @@ class RequestsOpenAPIRequestFactory(object):
1013

1114
@classmethod
1215
def create(cls, request):
16+
"""
17+
Converts a requests request to an OpenAPI one
18+
19+
Internally converts to a `PreparedRequest` first to parse the exact
20+
payload being sent
21+
"""
22+
if isinstance(request, Request):
23+
request = request.prepare()
24+
25+
# Method
1326
method = request.method.lower()
1427

15-
cookie = request.cookies or {}
28+
# Cookies
29+
cookie = {}
30+
if request._cookies is not None:
31+
# cookies are stored in a cookiejar object
32+
cookie = request._cookies.get_dict()
33+
34+
# Preparing a request formats the URL with params, strip them out again
35+
o = urlparse(request.url)
36+
params = parse_qs(o.query)
37+
# extract the URL without query parameters
38+
url = o._replace(query=None).geturl()
1639

1740
# gets deduced by path finder against spec
1841
path = {}
1942

20-
mimetype = request.headers.get('Accept') or \
21-
request.headers.get('Content-Type')
43+
# Order matters because all python requests issued from a session
44+
# include Accept */* which does not necessarily match the content type
45+
mimetype = request.headers.get('Content-Type') or \
46+
request.headers.get('Accept')
47+
48+
# Headers - request.headers is not an instance of dict
49+
# which is expected
50+
header = dict(request.headers)
51+
52+
# Body
53+
# TODO: figure out if request._body_position is relevant
54+
body = request.body
55+
2256
parameters = RequestParameters(
23-
query=ImmutableMultiDict(request.params),
24-
header=request.headers,
57+
query=ImmutableMultiDict(params),
58+
header=header,
2559
cookie=cookie,
2660
path=path,
2761
)
2862
return OpenAPIRequest(
29-
full_url_pattern=request.url,
63+
full_url_pattern=url,
3064
method=method,
3165
parameters=parameters,
32-
body=request.data,
66+
body=body,
3367
mimetype=mimetype,
3468
)

tests/integration/contrib/requests/data/v3.0/requests_factory.yaml

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,25 @@ paths:
1313
description: the ID of the resource to retrieve
1414
schema:
1515
type: integer
16-
get:
16+
- name: q
17+
in: query
18+
required: true
19+
description: query key
20+
schema:
21+
type: string
22+
post:
23+
requestBody:
24+
description: request data
25+
required: True
26+
content:
27+
application/json:
28+
schema:
29+
type: object
30+
required:
31+
- param1
32+
properties:
33+
param1:
34+
type: integer
1735
responses:
1836
200:
1937
description: Return the resource.

tests/integration/contrib/requests/test_requests_requests.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def test_simple(self, request_factory, request):
1515
query = ImmutableMultiDict([])
1616
headers = request.headers
1717
cookies = {}
18+
prepared = request.prepare()
1819
assert openapi_request.parameters == RequestParameters(
1920
path=path,
2021
query=query,
@@ -23,7 +24,7 @@ def test_simple(self, request_factory, request):
2324
)
2425
assert openapi_request.method == request.method.lower()
2526
assert openapi_request.full_url_pattern == 'http://localhost/'
26-
assert openapi_request.body == request.data
27+
assert openapi_request.body == prepared.body
2728
assert openapi_request.mimetype == 'application/json'
2829

2930
def test_multiple_values(self, request_factory, request):
@@ -44,9 +45,10 @@ def test_multiple_values(self, request_factory, request):
4445
header=headers,
4546
cookie=cookies,
4647
)
48+
prepared = request.prepare()
4749
assert openapi_request.method == request.method.lower()
4850
assert openapi_request.full_url_pattern == 'http://localhost/'
49-
assert openapi_request.body == request.data
51+
assert openapi_request.body == prepared.body
5052
assert openapi_request.mimetype == 'application/json'
5153

5254
def test_url_rule(self, request_factory, request):
@@ -57,16 +59,19 @@ def test_url_rule(self, request_factory, request):
5759
# empty when not bound to spec
5860
path = {}
5961
query = ImmutableMultiDict([])
60-
headers = request.headers
62+
headers = (
63+
('Content-Type', 'application/json'),
64+
)
6165
cookies = {}
6266
assert openapi_request.parameters == RequestParameters(
6367
path=path,
6468
query=query,
6569
header=headers,
6670
cookie=cookies,
6771
)
72+
prepared = request.prepare()
6873
assert openapi_request.method == request.method.lower()
6974
assert openapi_request.full_url_pattern == \
7075
'http://localhost/browse/12/'
71-
assert openapi_request.body == request.data
76+
assert openapi_request.body == prepared.body
7277
assert openapi_request.mimetype == 'application/json'

tests/integration/contrib/requests/test_requests_validation.py

+29-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from openapi_core.validation.response.validators import ResponseValidator
1111

1212

13-
class TestFlaskOpenAPIValidation(object):
13+
class TestRequestsOpenAPIValidation(object):
1414

1515
@pytest.fixture
1616
def spec(self, factory):
@@ -20,10 +20,16 @@ def spec(self, factory):
2020
@responses.activate
2121
def test_response_validator_path_pattern(self, spec):
2222
responses.add(
23-
responses.GET, 'http://localhost/browse/12/',
24-
json={"data": "data"}, status=200)
23+
responses.POST, 'http://localhost/browse/12/?q=string',
24+
json={"data": "data"}, status=200, match_querystring=True,
25+
)
2526
validator = ResponseValidator(spec)
26-
request = requests.Request('GET', 'http://localhost/browse/12/')
27+
request = requests.Request(
28+
'POST', 'http://localhost/browse/12/',
29+
params={'q': 'string'},
30+
headers={'content-type': 'application/json'},
31+
json={'param1': 1},
32+
)
2733
request_prepared = request.prepare()
2834
session = requests.Session()
2935
response = session.send(request_prepared)
@@ -32,10 +38,27 @@ def test_response_validator_path_pattern(self, spec):
3238
result = validator.validate(openapi_request, openapi_response)
3339
assert not result.errors
3440

35-
@responses.activate
3641
def test_request_validator_path_pattern(self, spec):
3742
validator = RequestValidator(spec)
38-
request = requests.Request('GET', 'http://localhost/browse/12/')
43+
request = requests.Request(
44+
'POST', 'http://localhost/browse/12/',
45+
params={'q': 'string'},
46+
headers={'content-type': 'application/json'},
47+
json={'param1': 1},
48+
)
3949
openapi_request = RequestsOpenAPIRequest(request)
4050
result = validator.validate(openapi_request)
4151
assert not result.errors
52+
53+
def test_request_validator_prepared_request(self, spec):
54+
validator = RequestValidator(spec)
55+
request = requests.Request(
56+
'POST', 'http://localhost/browse/12/',
57+
params={'q': 'string'},
58+
headers={'content-type': 'application/json'},
59+
json={'param1': 1},
60+
)
61+
request_prepared = request.prepare()
62+
openapi_request = RequestsOpenAPIRequest(request_prepared)
63+
result = validator.validate(openapi_request)
64+
assert not result.errors

0 commit comments

Comments
 (0)