Skip to content

Commit 17eabdc

Browse files
committed
Requests chunk encoded request handling
1 parent 4658b25 commit 17eabdc

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

Diff for: openapi_core/contrib/requests/requests.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""OpenAPI core contrib requests requests module"""
2+
from typing import Mapping
23
from typing import Optional
34
from typing import Union
45
from urllib.parse import parse_qs
@@ -7,6 +8,7 @@
78
from requests import PreparedRequest
89
from requests import Request
910
from requests.cookies import RequestsCookieJar
11+
from requests.utils import rewind_body
1012
from werkzeug.datastructures import Headers
1113
from werkzeug.datastructures import ImmutableMultiDict
1214

@@ -28,7 +30,9 @@ def __init__(self, request: Union[Request, PreparedRequest]):
2830
"'request' argument is not type of "
2931
f"{Request} or {PreparedRequest}"
3032
)
33+
self._request = None
3134
if isinstance(request, Request):
35+
self._request = request
3236
request = request.prepare()
3337

3438
self.request = request
@@ -65,13 +69,28 @@ def method(self) -> str:
6569

6670
@property
6771
def body(self) -> Optional[str]:
68-
if self.request.body is None:
72+
import ipdb; ipdb.set_trace()
73+
body = self.request.body
74+
if body is None:
6975
return None
70-
if isinstance(self.request.body, bytes):
71-
return self.request.body.decode("utf-8")
72-
assert isinstance(self.request.body, str)
76+
chunks = None
77+
is_stream = all(
78+
[
79+
hasattr(body, "__iter__"),
80+
not isinstance(body, (bytes, str, list, tuple, Mapping)),
81+
]
82+
)
83+
if is_stream:
84+
chunks = list(body)
85+
body = b"".join(chunks)
86+
if isinstance(body, bytes):
87+
body = body.decode("utf-8")
88+
assert isinstance(body, str)
89+
# recreate request stream from evaluated chunks
90+
if chunks is not None:
91+
self.request.body = (x for x in chunks)
7392
# TODO: figure out if request._body_position is relevant
74-
return self.request.body
93+
return body
7594

7695
@property
7796
def mimetype(self) -> str:

Diff for: tests/integration/contrib/requests/test_requests_validation.py

+34
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from types import GeneratorType
2+
13
import pytest
24
import requests
35
import responses
@@ -9,6 +11,8 @@
911
from openapi_core.contrib.requests import RequestsOpenAPIRequest
1012
from openapi_core.contrib.requests import RequestsOpenAPIResponse
1113
from openapi_core.contrib.requests import RequestsOpenAPIWebhookRequest
14+
from openapi_core.datatypes import Parameters
15+
from openapi_core.datatypes import RequestParameters
1216

1317

1418
class TestRequestsOpenAPIValidation:
@@ -72,6 +76,36 @@ def test_request_validator_path_pattern(self, request_unmarshaller):
7276
result = request_unmarshaller.unmarshal(openapi_request)
7377
assert not result.errors
7478

79+
def test_request_validator_encoded_chunks(self, request_unmarshaller):
80+
request_chunks = [
81+
b'{',
82+
b'"param1": 1',
83+
b'}',
84+
]
85+
def gen():
86+
for chunk in request_chunks:
87+
yield chunk
88+
89+
request = requests.Request(
90+
"POST",
91+
"http://localhost/browse/12/",
92+
params={"q": "string"},
93+
headers={"content-type": "application/json"},
94+
data=gen(),
95+
)
96+
openapi_request = RequestsOpenAPIRequest(request)
97+
result = request_unmarshaller.unmarshal(openapi_request)
98+
assert not result.errors
99+
assert result.body == {"param1": 1}
100+
assert result.parameters == Parameters(
101+
query={"q": "string"},
102+
header={},
103+
cookie={},
104+
path={"id": 12},
105+
)
106+
assert isinstance(request.data, GeneratorType)
107+
assert list(request.data) == request_chunks
108+
75109
def test_request_validator_prepared_request(self, request_unmarshaller):
76110
request = requests.Request(
77111
"POST",

0 commit comments

Comments
 (0)