Skip to content

Commit 9925e1b

Browse files
committed
Response binary format support
1 parent ffb17a7 commit 9925e1b

19 files changed

+67
-71
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

+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 self.response.text is None:
15-
return ""
15+
return b""
1616
assert isinstance(self.response.text, str)
17-
return self.response.text
17+
return self.response.text.encode("utf-8")
1818

1919
@property
2020
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

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

1212
@property
13-
def data(self) -> str:
14-
return self.response.get_data(as_text=True)
13+
def data(self) -> bytes:
14+
return self.response.get_data(as_text=False)
1515

1616
@property
1717
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/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/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

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def response_unmarshaller(self, spec):
3939

4040
def test_invalid_server(self, response_unmarshaller):
4141
request = MockRequest("http://petstore.invalid.net/v1", "get", "/")
42-
response = MockResponse("Not Found", status_code=404)
42+
response = MockResponse(b"Not Found", status_code=404)
4343

4444
result = response_unmarshaller.unmarshal(request, response)
4545

@@ -50,7 +50,7 @@ def test_invalid_server(self, response_unmarshaller):
5050

5151
def test_invalid_operation(self, response_unmarshaller):
5252
request = MockRequest(self.host_url, "patch", "/v1/pets")
53-
response = MockResponse("Not Found", status_code=404)
53+
response = MockResponse(b"Not Found", status_code=404)
5454

5555
result = response_unmarshaller.unmarshal(request, response)
5656

@@ -61,7 +61,7 @@ def test_invalid_operation(self, response_unmarshaller):
6161

6262
def test_invalid_response(self, response_unmarshaller):
6363
request = MockRequest(self.host_url, "get", "/v1/pets")
64-
response = MockResponse("Not Found", status_code=409)
64+
response = MockResponse(b"Not Found", status_code=409)
6565

6666
result = response_unmarshaller.unmarshal(request, response)
6767

@@ -72,7 +72,7 @@ def test_invalid_response(self, response_unmarshaller):
7272

7373
def test_invalid_content_type(self, response_unmarshaller):
7474
request = MockRequest(self.host_url, "get", "/v1/pets")
75-
response = MockResponse("Not Found", content_type="text/csv")
75+
response = MockResponse(b"Not Found", content_type="text/csv")
7676

7777
result = response_unmarshaller.unmarshal(request, response)
7878

@@ -93,20 +93,20 @@ def test_missing_body(self, response_unmarshaller):
9393

9494
def test_invalid_media_type(self, response_unmarshaller):
9595
request = MockRequest(self.host_url, "get", "/v1/pets")
96-
response = MockResponse("abcde")
96+
response = MockResponse(b"abcde")
9797

9898
result = response_unmarshaller.unmarshal(request, response)
9999

100100
assert result.errors == [DataValidationError()]
101101
assert result.errors[0].__cause__ == MediaTypeDeserializeError(
102-
mimetype="application/json", value="abcde"
102+
mimetype="application/json", value=b"abcde"
103103
)
104104
assert result.data is None
105105
assert result.headers == {}
106106

107107
def test_invalid_media_type_value(self, response_unmarshaller):
108108
request = MockRequest(self.host_url, "get", "/v1/pets")
109-
response = MockResponse("{}")
109+
response = MockResponse(b"{}")
110110

111111
result = response_unmarshaller.unmarshal(request, response)
112112

@@ -154,7 +154,7 @@ def test_invalid_header(self, response_unmarshaller):
154154
},
155155
],
156156
}
157-
response_data = json.dumps(response_json)
157+
response_data = json.dumps(response_json).encode()
158158
headers = {
159159
"x-delete-confirm": "true",
160160
"x-delete-date": "today",
@@ -181,7 +181,7 @@ def test_get_pets(self, response_unmarshaller):
181181
},
182182
],
183183
}
184-
response_data = json.dumps(response_json)
184+
response_data = json.dumps(response_json).encode()
185185
response = MockResponse(response_data)
186186

187187
result = response_unmarshaller.unmarshal(request, response)

0 commit comments

Comments
 (0)