Skip to content
Open
15 changes: 15 additions & 0 deletions .changeset/support_non_standard_http_codes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
default: minor
---

# Summary

Added support for responses with non-standard HTTP Status Codes. Off by default, enabled with `allow_int_response_codes`.

# Problem

Currently if a response with a status code that does not exist in `http.HTTPStatusCode` a `ValueError` is raised. For example: `ValueError: 490 is not a valid HTTPStatus`.

# Edge Case

If a non-standard status code is received from an endpoint that doesn't define any responses with non-standard status codes the old behavior will appear.
21 changes: 21 additions & 0 deletions end_to_end_tests/baseline_openapi_3.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,27 @@
}
}
},
"/tests/nonstandard-response-code": {
"get": {
"tags": [
"tests"
],
"summary": "Test nonstandard response code",
"description": "Test endpoint with nonstandard response code",
"operationId": "nonstandard_response_code",
"responses": {
"200": {
"description": "Successful response"
},
"499": {
"description": "Nonstandard response code"
},
"99999": {
"description": "Nonstandard response code"
}
}
}
},
"/config/content-type-override": {
"post": {
"tags": [
Expand Down
21 changes: 21 additions & 0 deletions end_to_end_tests/baseline_openapi_3.1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,27 @@ info:
}
}
},
"/tests/nonstandard-response-code": {
"get": {
"tags": [
"tests"
],
"summary": "Test nonstandard response code",
"description": "Test endpoint with nonstandard response code",
"operationId": "nonstandard_response_code",
"responses": {
"200": {
"description": "Successful response"
},
"499": {
"description": "Nonstandard response code"
},
"99999": {
"description": "Nonstandard response code"
}
}
}
},
"/config/content-type-override": {
"post": {
"tags": [
Expand Down
1 change: 1 addition & 0 deletions end_to_end_tests/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ field_prefix: attr_
content_type_overrides:
openapi/python/client: application/json
generate_all_tags: true
allow_int_response_codes: true
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
get_user_list,
json_body_tests_json_body_post,
no_response_tests_no_response_get,
nonstandard_response_code,
octet_stream_tests_octet_stream_get,
octet_stream_tests_octet_stream_post,
post_form_data,
Expand Down Expand Up @@ -150,3 +151,10 @@ def description_with_backslash(cls) -> types.ModuleType:
Test description with \
"""
return description_with_backslash

@classmethod
def nonstandard_response_code(cls) -> types.ModuleType:
"""
Test endpoint with nonstandard response code
"""
return nonstandard_response_code
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ def to_tuple(self) -> FileTypes:


T = TypeVar("T")
S = TypeVar("S", bound=HTTPStatus | int)


@define
class Response(Generic[T]):
class Response(Generic[T, S]):
"""A response from an endpoint"""

status_code: HTTPStatus
status_code: S
content: bytes
headers: MutableMapping[str, str]
parsed: T | None
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any

import httpx
Expand Down Expand Up @@ -39,9 +39,11 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[Any, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -52,7 +54,7 @@ def sync_detailed(
*,
client: AuthenticatedClient | Client,
body: JsonLikeBody | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""A content type that works like json but isn't application/json

Args:
Expand Down Expand Up @@ -81,7 +83,7 @@ async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
body: JsonLikeBody | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""A content type that works like json but isn't application/json

Args:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any

import httpx
Expand Down Expand Up @@ -39,9 +39,11 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[Any, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -52,7 +54,7 @@ def sync_detailed(
*,
client: AuthenticatedClient | Client,
body: OptionalBodyBody | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""Test optional request body

Args:
Expand Down Expand Up @@ -81,7 +83,7 @@ async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
body: OptionalBodyBody | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""Test optional request body

Args:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any

import httpx
Expand Down Expand Up @@ -57,9 +57,11 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[Any, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -70,7 +72,7 @@ def sync_detailed(
*,
client: AuthenticatedClient | Client,
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""Test multiple bodies

Args:
Expand Down Expand Up @@ -102,7 +104,7 @@ async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
body: PostBodiesMultipleJsonBody | File | PostBodiesMultipleDataBody | PostBodiesMultipleFilesBody | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""Test multiple bodies

Args:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any

import httpx
Expand Down Expand Up @@ -39,9 +39,11 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[Any, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -52,7 +54,7 @@ def sync_detailed(
*,
client: AuthenticatedClient | Client,
body: AModel | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""Test request body defined via ref

Args:
Expand Down Expand Up @@ -81,7 +83,7 @@ async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
body: AModel | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""Test request body defined via ref

Args:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any, cast

import httpx
Expand Down Expand Up @@ -39,9 +39,11 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[str]:
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[str, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -52,7 +54,7 @@ def sync_detailed(
*,
client: AuthenticatedClient | Client,
body: str | Unset = UNSET,
) -> Response[str]:
) -> Response[str, http.HTTPStatus]:
"""Content Type Override

Args:
Expand Down Expand Up @@ -105,7 +107,7 @@ async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
body: str | Unset = UNSET,
) -> Response[str]:
) -> Response[str, http.HTTPStatus]:
"""Content Type Override

Args:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any

import httpx
Expand Down Expand Up @@ -37,9 +37,11 @@ def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Res
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[Any, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -50,7 +52,7 @@ def sync_detailed(
*,
client: AuthenticatedClient | Client,
common: str | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""
Args:
common (str | Unset):
Expand Down Expand Up @@ -78,7 +80,7 @@ async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
common: str | Unset = UNSET,
) -> Response[Any]:
) -> Response[Any, http.HTTPStatus]:
"""
Args:
common (str | Unset):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from http import HTTPStatus
import http
from typing import Any

import httpx
Expand Down Expand Up @@ -34,9 +34,9 @@ def _parse_response(

def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[GetModelsAllofResponse200]:
) -> Response[GetModelsAllofResponse200, http.HTTPStatus]:
return Response(
status_code=HTTPStatus(response.status_code),
status_code=http.HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
Expand All @@ -46,7 +46,7 @@ def _build_response(
def sync_detailed(
*,
client: AuthenticatedClient | Client,
) -> Response[GetModelsAllofResponse200]:
) -> Response[GetModelsAllofResponse200, http.HTTPStatus]:
"""
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
Expand Down Expand Up @@ -86,7 +86,7 @@ def sync(
async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
) -> Response[GetModelsAllofResponse200]:
) -> Response[GetModelsAllofResponse200, http.HTTPStatus]:
"""
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
Expand Down
Loading