Skip to content

Commit 5dfaf71

Browse files
committed
Added support for octet-stream content type (openapi-generators#116)
1 parent 0230ec5 commit 5dfaf71

File tree

7 files changed

+119
-0
lines changed

7 files changed

+119
-0
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77

8+
## 0.5.4 - Unreleased
9+
### Additions
10+
- Added support for octet-stream content type (#116)
11+
12+
813
## 0.5.3 - 2020-08-13
914
### Security
1015
- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)

end_to_end_tests/fastapi_app/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
99
from pydantic import BaseModel
10+
from starlette.responses import FileResponse
1011

1112
app = FastAPI(title="My Test API", description="An API for testing openapi-python-client",)
1213

@@ -85,6 +86,15 @@ def test_defaults(
8586
return
8687

8788

89+
@test_router.get(
90+
"/test_octet_stream",
91+
response_class=FileResponse,
92+
responses={200: {"content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}}},
93+
)
94+
def test_octet_stream():
95+
return
96+
97+
8898
app.include_router(test_router, prefix="/tests", tags=["tests"])
8999

90100

end_to_end_tests/fastapi_app/openapi.json

+22
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,28 @@
334334
}
335335
}
336336
}
337+
},
338+
"/tests/test_octet_stream": {
339+
"get": {
340+
"tags": [
341+
"tests"
342+
],
343+
"summary": "Test Octet Stream",
344+
"operationId": "test_octet_stream_tests_test_octet_stream_get",
345+
"responses": {
346+
"200": {
347+
"description": "Successful Response",
348+
"content": {
349+
"application/octet-stream": {
350+
"schema": {
351+
"type": "string",
352+
"format": "binary"
353+
}
354+
}
355+
}
356+
}
357+
}
358+
}
337359
}
338360
},
339361
"components": {

end_to_end_tests/golden-master/my_test_api_client/api/tests.py

+15
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,18 @@ def test_defaults_tests_test_defaults_post(
172172
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
173173
else:
174174
raise ApiResponseError(response=response)
175+
176+
177+
def test_octet_stream_tests_test_octet_stream_get(*, client: Client,) -> bytes:
178+
179+
""" """
180+
url = "{}/tests/test_octet_stream".format(client.base_url)
181+
182+
headers: Dict[str, Any] = client.get_headers()
183+
184+
response = httpx.get(url=url, headers=headers,)
185+
186+
if response.status_code == 200:
187+
return bytes(response.content)
188+
else:
189+
raise ApiResponseError(response=response)

end_to_end_tests/golden-master/my_test_api_client/async_api/tests.py

+16
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,19 @@ async def test_defaults_tests_test_defaults_post(
176176
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
177177
else:
178178
raise ApiResponseError(response=response)
179+
180+
181+
async def test_octet_stream_tests_test_octet_stream_get(*, client: Client,) -> bytes:
182+
183+
""" """
184+
url = "{}/tests/test_octet_stream".format(client.base_url,)
185+
186+
headers: Dict[str, Any] = client.get_headers()
187+
188+
async with httpx.AsyncClient() as _client:
189+
response = await _client.get(url=url, headers=headers,)
190+
191+
if response.status_code == 200:
192+
return bytes(response.content)
193+
else:
194+
raise ApiResponseError(response=response)

openapi_python_client/parser/responses.py

+19
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ def constructor(self) -> str:
7070
return f"{self.python_type}(response.text)"
7171

7272

73+
@dataclass
74+
class BytesResponse(Response):
75+
""" Response is a basic type """
76+
77+
python_type: str = "bytes"
78+
79+
def return_string(self) -> str:
80+
""" How this Response should be represented as a return type """
81+
return self.python_type
82+
83+
def constructor(self) -> str:
84+
""" How the return value of this response should be constructed """
85+
return f"{self.python_type}(response.content)"
86+
87+
7388
openapi_types_to_python_type_strings = {
7489
"string": "str",
7590
"number": "float",
@@ -88,6 +103,8 @@ def response_from_data(*, status_code: int, data: Union[oai.Response, oai.Refere
88103
schema_data = None
89104
if "application/json" in content:
90105
schema_data = data.content["application/json"].media_type_schema
106+
elif "application/octet-stream" in content:
107+
schema_data = data.content["application/octet-stream"].media_type_schema
91108
elif "text/html" in content:
92109
schema_data = data.content["text/html"].media_type_schema
93110

@@ -101,6 +118,8 @@ def response_from_data(*, status_code: int, data: Union[oai.Response, oai.Refere
101118
return Response(status_code=status_code)
102119
if response_type == "array" and isinstance(schema_data.items, oai.Reference):
103120
return ListRefResponse(status_code=status_code, reference=Reference.from_ref(schema_data.items.ref),)
121+
if response_type == "string" and schema_data.schema_format in {"binary", "base64"}:
122+
return BytesResponse(status_code=status_code)
104123
if response_type in openapi_types_to_python_type_strings:
105124
return BasicResponse(status_code=status_code, openapi_type=response_type)
106125
return ParseError(data=data, detail=f"Unrecognized type {schema_data.type}")

tests/test_openapi_parser/test_responses.py

+32
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ def test_constructor(self):
9494
assert r.constructor() == "bool(response.text)"
9595

9696

97+
class TestBytesResponse:
98+
def test_return_string(self):
99+
from openapi_python_client.parser.responses import BytesResponse
100+
101+
b = BytesResponse(200)
102+
103+
assert b.return_string() == "bytes"
104+
105+
def test_constructor(self):
106+
from openapi_python_client.parser.responses import BytesResponse
107+
108+
b = BytesResponse(200)
109+
110+
assert b.constructor() == "bytes(response.content)"
111+
112+
97113
class TestResponseFromData:
98114
def test_response_from_data_no_content(self, mocker):
99115
from openapi_python_client.parser.responses import response_from_data
@@ -199,3 +215,19 @@ def test_response_from_dict_unsupported_type(self):
199215
)
200216

201217
assert response_from_data(status_code=200, data=data) == ParseError(data=data, detail="Unrecognized type BLAH")
218+
219+
def test_response_from_data_octet_stream(self, mocker):
220+
status_code = mocker.MagicMock(autospec=int)
221+
data = oai.Response.construct(
222+
content={
223+
"application/octet-stream": oai.MediaType.construct(
224+
media_type_schema=oai.Schema.construct(type="string", schema_format="binary")
225+
)
226+
}
227+
)
228+
BytesResponse = mocker.patch(f"{MODULE_NAME}.BytesResponse")
229+
from openapi_python_client.parser.responses import response_from_data
230+
231+
response = response_from_data(status_code=status_code, data=data)
232+
233+
assert response == BytesResponse()

0 commit comments

Comments
 (0)