Skip to content

Added support for octet-stream content type (#116) #157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## 0.5.4 - Unreleased
### Additions
- Added support for octet-stream content type (#116)


## 0.5.3 - 2020-08-13
### Security
- All values that become file/directory names are sanitized to address path traversal vulnerabilities (CVE-2020-15141)
Expand Down
10 changes: 10 additions & 0 deletions end_to_end_tests/fastapi_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from fastapi import APIRouter, Body, FastAPI, File, Header, Query, UploadFile
from pydantic import BaseModel
from starlette.responses import FileResponse

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

Expand Down Expand Up @@ -85,6 +86,15 @@ def test_defaults(
return


@test_router.get(
"/test_octet_stream",
response_class=FileResponse,
responses={200: {"content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}}},
)
def test_octet_stream():
return


app.include_router(test_router, prefix="/tests", tags=["tests"])


Expand Down
22 changes: 22 additions & 0 deletions end_to_end_tests/fastapi_app/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,28 @@
}
}
}
},
"/tests/test_octet_stream": {
"get": {
"tags": [
"tests"
],
"summary": "Test Octet Stream",
"operationId": "test_octet_stream_tests_test_octet_stream_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
}
},
"components": {
Expand Down
15 changes: 15 additions & 0 deletions end_to_end_tests/golden-master/my_test_api_client/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,18 @@ def test_defaults_tests_test_defaults_post(
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
else:
raise ApiResponseError(response=response)


def test_octet_stream_tests_test_octet_stream_get(*, client: Client,) -> bytes:

""" """
url = "{}/tests/test_octet_stream".format(client.base_url)

headers: Dict[str, Any] = client.get_headers()

response = httpx.get(url=url, headers=headers,)

if response.status_code == 200:
return bytes(response.content)
else:
raise ApiResponseError(response=response)
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,19 @@ async def test_defaults_tests_test_defaults_post(
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
else:
raise ApiResponseError(response=response)


async def test_octet_stream_tests_test_octet_stream_get(*, client: Client,) -> bytes:

""" """
url = "{}/tests/test_octet_stream".format(client.base_url,)

headers: Dict[str, Any] = client.get_headers()

async with httpx.AsyncClient() as _client:
response = await _client.get(url=url, headers=headers,)

if response.status_code == 200:
return bytes(response.content)
else:
raise ApiResponseError(response=response)
17 changes: 17 additions & 0 deletions openapi_python_client/parser/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ def constructor(self) -> str:
return f"{self.python_type}(response.text)"


@dataclass
class BytesResponse(Response):
""" Response is a basic type """

python_type: str = "bytes"

def return_string(self) -> str:
""" How this Response should be represented as a return type """
return self.python_type

def constructor(self) -> str:
""" How the return value of this response should be constructed """
return f"{self.python_type}(response.content)"


openapi_types_to_python_type_strings = {
"string": "str",
"number": "float",
Expand All @@ -88,6 +103,8 @@ def response_from_data(*, status_code: int, data: Union[oai.Response, oai.Refere
schema_data = None
if "application/json" in content:
schema_data = data.content["application/json"].media_type_schema
elif "application/octet-stream" in content:
return BytesResponse(status_code=status_code)
elif "text/html" in content:
schema_data = data.content["text/html"].media_type_schema

Expand Down
28 changes: 28 additions & 0 deletions tests/test_openapi_parser/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ def test_constructor(self):
assert r.constructor() == "bool(response.text)"


class TestBytesResponse:
def test_return_string(self):
from openapi_python_client.parser.responses import BytesResponse

b = BytesResponse(200)

assert b.return_string() == "bytes"

def test_constructor(self):
from openapi_python_client.parser.responses import BytesResponse

b = BytesResponse(200)

assert b.constructor() == "bytes(response.content)"


class TestResponseFromData:
def test_response_from_data_no_content(self, mocker):
from openapi_python_client.parser.responses import response_from_data
Expand Down Expand Up @@ -199,3 +215,15 @@ def test_response_from_dict_unsupported_type(self):
)

assert response_from_data(status_code=200, data=data) == ParseError(data=data, detail="Unrecognized type BLAH")

def test_response_from_data_octet_stream(self, mocker):
status_code = mocker.MagicMock(autospec=int)
data = oai.Response.construct(
content={"application/octet-stream": oai.MediaType.construct(media_type_schema=mocker.MagicMock())}
)
BytesResponse = mocker.patch(f"{MODULE_NAME}.BytesResponse")
from openapi_python_client.parser.responses import response_from_data

response = response_from_data(status_code=status_code, data=data)

assert response == BytesResponse()