Skip to content

Support all text/* content types in responses #897

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 8 commits into from
Dec 6, 2023
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
11 changes: 11 additions & 0 deletions .changeset/support_all_text_content_types_in_responses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
default: minor
---

# Support all `text/*` content types in responses

Within an API response, any content type which starts with `text/` will now be treated the same as `text/html` already was—they will return the `response.text` attribute from the [httpx Response](https://www.python-httpx.org/api/#response).

Thanks to @fdintino for the initial implementation, and thanks for the discussions from @kairntech, @rubenfiszel, and @antoneladestito.

Closes #797 and #821.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import types

from . import post_responses_unions_simple_before_complex
from . import post_responses_unions_simple_before_complex, text_response


class ResponsesEndpoints:
Expand All @@ -12,3 +12,10 @@ def post_responses_unions_simple_before_complex(cls) -> types.ModuleType:
Regression test for #603
"""
return post_responses_unions_simple_before_complex

@classmethod
def text_response(cls) -> types.ModuleType:
"""
Text Response
"""
return text_response
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from http import HTTPStatus
from typing import Any, Dict, Optional, Union

import httpx

from ... import errors
from ...client import AuthenticatedClient, Client
from ...types import Response


def _get_kwargs() -> Dict[str, Any]:
return {
"method": "post",
"url": "/responses/text",
}


def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[str]:
if response.status_code == HTTPStatus.OK:
response_200 = response.text
return response_200
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None


def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[str]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)


def sync_detailed(
*,
client: Union[AuthenticatedClient, Client],
) -> Response[str]:
"""Text Response

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Response[str]
"""

kwargs = _get_kwargs()

response = client.get_httpx_client().request(
**kwargs,
)

return _build_response(client=client, response=response)


def sync(
*,
client: Union[AuthenticatedClient, Client],
) -> Optional[str]:
"""Text Response

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
str
"""

return sync_detailed(
client=client,
).parsed


async def asyncio_detailed(
*,
client: Union[AuthenticatedClient, Client],
) -> Response[str]:
"""Text Response

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Response[str]
"""

kwargs = _get_kwargs()

response = await client.get_async_httpx_client().request(**kwargs)

return _build_response(client=client, response=response)


async def asyncio(
*,
client: Union[AuthenticatedClient, Client],
) -> Optional[str]:
"""Text Response

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
str
"""

return (
await asyncio_detailed(
client=client,
)
).parsed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from http import HTTPStatus
from typing import Any, Dict, Optional, Union, cast
from typing import Any, Dict, Optional, Union

import httpx

Expand Down Expand Up @@ -27,7 +27,7 @@ def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == HTTPStatus.OK:
response_200 = cast(Any, response.json())
response_200 = response.json()
return response_200
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
response_422 = HTTPValidationError.from_dict(response.json())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime
from http import HTTPStatus
from typing import Any, Dict, List, Optional, Union, cast
from typing import Any, Dict, List, Optional, Union

import httpx
from dateutil.parser import isoparse
Expand Down Expand Up @@ -94,7 +94,7 @@ def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == HTTPStatus.OK:
response_200 = cast(Any, response.json())
response_200 = response.json()
return response_200
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
response_422 = HTTPValidationError.from_dict(response.json())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from http import HTTPStatus
from typing import Any, Dict, Optional, Union, cast
from typing import Any, Dict, Optional, Union

import httpx

Expand Down Expand Up @@ -32,7 +32,7 @@ def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == HTTPStatus.OK:
response_200 = cast(Any, response.json())
response_200 = response.json()
return response_200
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
response_422 = HTTPValidationError.from_dict(response.json())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from http import HTTPStatus
from typing import Any, Dict, Optional, Union, cast
from typing import Any, Dict, Optional, Union

import httpx

Expand Down Expand Up @@ -27,7 +27,7 @@ def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == HTTPStatus.OK:
response_200 = cast(Any, response.json())
response_200 = response.json()
return response_200
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
response_422 = HTTPValidationError.from_dict(response.json())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from http import HTTPStatus
from typing import Any, Dict, Optional, Union, cast
from typing import Any, Dict, Optional, Union

import httpx

Expand Down Expand Up @@ -27,7 +27,7 @@ def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == HTTPStatus.OK:
response_200 = cast(Any, response.json())
response_200 = response.json()
return response_200
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
response_422 = HTTPValidationError.from_dict(response.json())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from http import HTTPStatus
from typing import Any, Dict, List, Optional, Union, cast
from typing import Any, Dict, List, Optional, Union

import httpx

Expand Down Expand Up @@ -30,7 +30,7 @@ def _parse_response(
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == HTTPStatus.OK:
response_200 = cast(Any, response.json())
response_200 = response.json()
return response_200
if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
response_422 = HTTPValidationError.from_dict(response.json())
Expand Down
21 changes: 21 additions & 0 deletions end_to_end_tests/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,27 @@
}
}
},
"/responses/text": {
"post": {
"tags": [
"responses"
],
"summary": "Text Response",
"operationId": "text_response",
"responses": {
"200": {
"description": "Text response",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/auth/token_with_cookie": {
"get": {
"tags": [
Expand Down
Loading