Skip to content

Commit fa4169c

Browse files
committed
Response binary format support
1 parent ffb17a7 commit fa4169c

22 files changed

+80
-83
lines changed

Diff for: openapi_core/contrib/aiohttp/responses.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ def __init__(self, response: web.Response):
1313
self.response = response
1414

1515
@property
16-
def data(self) -> str:
16+
def data(self) -> bytes:
1717
if self.response.body is None:
18-
return ""
18+
return b""
1919
if isinstance(self.response.body, bytes):
20-
return self.response.body.decode("utf-8")
20+
return self.response.body
2121
assert isinstance(self.response.body, str)
22-
return self.response.body
22+
return self.response.body.encode("utf-8")
2323

2424
@property
2525
def status_code(self) -> int:

Diff for: openapi_core/contrib/django/responses.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
"""OpenAPI core contrib django responses module"""
2+
from itertools import tee
3+
24
from django.http.response import HttpResponse
5+
from django.http.response import StreamingHttpResponse
36
from werkzeug.datastructures import Headers
47

58

69
class DjangoOpenAPIResponse:
710
def __init__(self, response: HttpResponse):
8-
if not isinstance(response, HttpResponse):
11+
if not isinstance(response, (HttpResponse, StreamingHttpResponse)):
912
raise TypeError(
10-
f"'response' argument is not type of {HttpResponse}"
13+
f"'response' argument is not type of {HttpResponse} or {StreamingHttpResponse}"
1114
)
1215
self.response = response
1316

1417
@property
15-
def data(self) -> str:
18+
def data(self) -> bytes:
19+
if isinstance(self.response, StreamingHttpResponse):
20+
resp_iter1, resp_iter2 = tee(self.response._iterator)
21+
self.response.streaming_content = resp_iter1
22+
content = b"".join(map(self.response.make_bytes, resp_iter2))
23+
return content
1624
assert isinstance(self.response.content, bytes)
17-
return self.response.content.decode("utf-8")
25+
return self.response.content
1826

1927
@property
2028
def status_code(self) -> int:

Diff for: openapi_core/contrib/falcon/responses.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""OpenAPI core contrib falcon responses module"""
2+
from itertools import tee
3+
24
from falcon.response import Response
35
from werkzeug.datastructures import Headers
46

@@ -10,11 +12,16 @@ def __init__(self, response: Response):
1012
self.response = response
1113

1214
@property
13-
def data(self) -> str:
15+
def data(self) -> bytes:
1416
if self.response.text is None:
15-
return ""
17+
if self.response.stream is None:
18+
return b""
19+
resp_iter1, resp_iter2 = tee(self.response.stream)
20+
self.response.stream = resp_iter1
21+
content = b"".join(resp_iter2)
22+
return content
1623
assert isinstance(self.response.text, str)
17-
return self.response.text
24+
return self.response.text.encode("utf-8")
1825

1926
@property
2027
def status_code(self) -> int:

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ def __init__(self, response: Response):
1010
self.response = response
1111

1212
@property
13-
def data(self) -> str:
13+
def data(self) -> bytes:
1414
assert isinstance(self.response.content, bytes)
15-
return self.response.content.decode("utf-8")
15+
return self.response.content
1616

1717
@property
1818
def status_code(self) -> int:

Diff for: openapi_core/contrib/starlette/responses.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ def __init__(self, response: Response):
1010
self.response = response
1111

1212
@property
13-
def data(self) -> str:
13+
def data(self) -> bytes:
1414
if isinstance(self.response.body, bytes):
15-
return self.response.body.decode("utf-8")
15+
return self.response.body
1616
assert isinstance(self.response.body, str)
17-
return self.response.body
17+
return self.response.body.encode("utf-8")
1818

1919
@property
2020
def status_code(self) -> int:

Diff for: openapi_core/contrib/werkzeug/responses.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""OpenAPI core contrib werkzeug responses module"""
2+
from itertools import tee
3+
24
from werkzeug.datastructures import Headers
35
from werkzeug.wrappers import Response
46

@@ -10,8 +12,12 @@ def __init__(self, response: Response):
1012
self.response = response
1113

1214
@property
13-
def data(self) -> str:
14-
return self.response.get_data(as_text=True)
15+
def data(self) -> bytes:
16+
if not self.response.is_sequence:
17+
resp_iter1, resp_iter2 = tee(self.response.iter_encoded())
18+
self.response.response = resp_iter1
19+
return b"".join(resp_iter2)
20+
return self.response.get_data(as_text=False)
1521

1622
@property
1723
def status_code(self) -> int:

Diff for: openapi_core/protocols.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class Response(Protocol):
120120
"""
121121

122122
@property
123-
def data(self) -> str:
123+
def data(self) -> Optional[bytes]:
124124
...
125125

126126
@property

Diff for: openapi_core/testing/responses.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
class MockResponse:
1010
def __init__(
1111
self,
12-
data: str,
12+
data: bytes,
1313
status_code: int = 200,
1414
headers: Optional[Dict[str, Any]] = None,
1515
content_type: str = "application/json",

Diff for: tests/integration/contrib/aiohttp/test_aiohttp_project.py

-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ def api_key_encoded(self):
3838

3939

4040
class TestPetPhotoView(BaseTestPetstore):
41-
@pytest.mark.xfail(
42-
reason="response binary format not supported",
43-
strict=True,
44-
)
4541
async def test_get_valid(self, client, data_gif):
4642
headers = {
4743
"Authorization": "Basic testuser",

Diff for: tests/integration/contrib/django/test_django_project.py

-4
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,6 @@ def test_get_skip_response_validation(self, client):
398398

399399

400400
class TestPetPhotoView(BaseTestDjangoProject):
401-
@pytest.mark.xfail(
402-
reason="response binary format not supported",
403-
strict=True,
404-
)
405401
def test_get_valid(self, client, data_gif):
406402
headers = {
407403
"HTTP_AUTHORIZATION": "Basic testuser",

Diff for: tests/integration/contrib/falcon/test_falcon_project.py

-4
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,6 @@ def test_delete_method_invalid(self, client):
371371

372372

373373
class TestPetPhotoResource(BaseTestFalconProject):
374-
@pytest.mark.xfail(
375-
reason="response binary format not supported",
376-
strict=True,
377-
)
378374
def test_get_valid(self, client, data_gif):
379375
cookies = {"user": 1}
380376
headers = {

Diff for: tests/integration/contrib/flask/test_flask_project.py

-4
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,6 @@ def api_key_encoded(self):
4141

4242

4343
class TestPetPhotoView(BaseTestFlaskProject):
44-
@pytest.mark.xfail(
45-
reason="response binary format not supported",
46-
strict=True,
47-
)
4844
def test_get_valid(self, client, data_gif):
4945
headers = {
5046
"Authorization": "Basic testuser",

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

-4
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,6 @@ def request_unmarshaller(self, spec):
165165
def response_unmarshaller(self, spec):
166166
return V30ResponseUnmarshaller(spec)
167167

168-
@pytest.mark.xfail(
169-
reason="response binary format not supported",
170-
strict=True,
171-
)
172168
@responses.activate
173169
def test_response_binary_valid(self, response_unmarshaller, data_gif):
174170
responses.add(

Diff for: tests/integration/contrib/starlette/test_starlette_project.py

-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ def api_key_encoded(self):
3838

3939

4040
class TestPetPhotoView(BaseTestPetstore):
41-
@pytest.mark.xfail(
42-
reason="response binary format not supported",
43-
strict=True,
44-
)
4541
def test_get_valid(self, client, data_gif):
4642
headers = {
4743
"Authorization": "Basic testuser",

Diff for: tests/integration/test_petstore.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def test_get_pets(self, spec):
123123
data_json = {
124124
"data": [],
125125
}
126-
data = json.dumps(data_json)
126+
data = json.dumps(data_json).encode()
127127
headers = {
128128
"Content-Type": "application/json",
129129
"x-next": "next-url",
@@ -185,7 +185,7 @@ def test_get_pets_response(self, spec):
185185
}
186186
],
187187
}
188-
data = json.dumps(data_json)
188+
data = json.dumps(data_json).encode()
189189
response = MockResponse(data)
190190

191191
response_result = unmarshal_response(request, response, spec=spec)
@@ -286,7 +286,7 @@ def test_get_pets_invalid_response(self, spec, response_unmarshaller):
286286
}
287287
],
288288
}
289-
response_data = json.dumps(response_data_json)
289+
response_data = json.dumps(response_data_json).encode()
290290
response = MockResponse(response_data)
291291

292292
with pytest.raises(InvalidData) as exc_info:
@@ -349,7 +349,7 @@ def test_get_pets_ids_param(self, spec):
349349
data_json = {
350350
"data": [],
351351
}
352-
data = json.dumps(data_json)
352+
data = json.dumps(data_json).encode()
353353
response = MockResponse(data)
354354

355355
response_result = unmarshal_response(request, response, spec=spec)
@@ -398,7 +398,7 @@ def test_get_pets_tags_param(self, spec):
398398
data_json = {
399399
"data": [],
400400
}
401-
data = json.dumps(data_json)
401+
data = json.dumps(data_json).encode()
402402
response = MockResponse(data)
403403

404404
response_result = unmarshal_response(request, response, spec=spec)
@@ -1267,7 +1267,7 @@ def test_post_pets_raises_invalid_server_error(self, spec):
12671267
},
12681268
},
12691269
}
1270-
data = json.dumps(data_json)
1270+
data = json.dumps(data_json).encode()
12711271
response = MockResponse(data)
12721272

12731273
with pytest.raises(ServerNotFound):
@@ -1362,7 +1362,7 @@ def test_get_pet(self, spec):
13621362
},
13631363
},
13641364
}
1365-
data = json.dumps(data_json)
1365+
data = json.dumps(data_json).encode()
13661366
response = MockResponse(data)
13671367

13681368
response_result = unmarshal_response(request, response, spec=spec)
@@ -1413,7 +1413,7 @@ def test_get_pet_not_found(self, spec):
14131413
"message": message,
14141414
"rootCause": rootCause,
14151415
}
1416-
data = json.dumps(data_json)
1416+
data = json.dumps(data_json).encode()
14171417
response = MockResponse(data, status_code=404)
14181418

14191419
response_result = unmarshal_response(request, response, spec=spec)
@@ -1492,7 +1492,7 @@ def test_get_tags(self, spec):
14921492
assert result.body is None
14931493

14941494
data_json = ["cats", "birds"]
1495-
data = json.dumps(data_json)
1495+
data = json.dumps(data_json).encode()
14961496
response = MockResponse(data)
14971497

14981498
response_result = unmarshal_response(request, response, spec=spec)
@@ -1637,7 +1637,7 @@ def test_post_tags_additional_properties(self, spec):
16371637
"rootCause": rootCause,
16381638
"additionalinfo": additionalinfo,
16391639
}
1640-
data = json.dumps(data_json)
1640+
data = json.dumps(data_json).encode()
16411641
response = MockResponse(data, status_code=404)
16421642

16431643
response_result = unmarshal_response(request, response, spec=spec)
@@ -1694,7 +1694,7 @@ def test_post_tags_created_now(self, spec):
16941694
"rootCause": "Tag already exist",
16951695
"additionalinfo": "Tag Dog already exist",
16961696
}
1697-
data = json.dumps(data_json)
1697+
data = json.dumps(data_json).encode()
16981698
response = MockResponse(data, status_code=404)
16991699

17001700
response_result = unmarshal_response(request, response, spec=spec)
@@ -1753,7 +1753,7 @@ def test_post_tags_created_datetime(self, spec):
17531753
"rootCause": rootCause,
17541754
"additionalinfo": additionalinfo,
17551755
}
1756-
response_data = json.dumps(response_data_json)
1756+
response_data = json.dumps(response_data_json).encode()
17571757
response = MockResponse(response_data, status_code=404)
17581758

17591759
result = unmarshal_response(
@@ -1827,7 +1827,7 @@ def test_post_tags_urlencoded(self, spec):
18271827
"rootCause": rootCause,
18281828
"additionalinfo": additionalinfo,
18291829
}
1830-
response_data = json.dumps(response_data_json)
1830+
response_data = json.dumps(response_data_json).encode()
18311831
response = MockResponse(response_data, status_code=404)
18321832

18331833
result = unmarshal_response(
@@ -1898,7 +1898,7 @@ def test_post_tags_created_invalid_type(self, spec):
18981898
"rootCause": rootCause,
18991899
"additionalinfo": additionalinfo,
19001900
}
1901-
data = json.dumps(data_json)
1901+
data = json.dumps(data_json).encode()
19021902
response = MockResponse(data, status_code=404)
19031903

19041904
response_result = unmarshal_response(request, response, spec=spec)

Diff for: tests/integration/unmarshalling/test_read_only_write_only.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_read_only_property_response(self, response_unmarshaller):
5555
"id": 10,
5656
"name": "Pedro",
5757
}
58-
)
58+
).encode()
5959

6060
request = MockRequest(host_url="", method="POST", path="/users")
6161

0 commit comments

Comments
 (0)