diff --git a/.changeset/support_patterned_and_default_http_statuses.md b/.changeset/support_patterned_and_default_http_statuses.md new file mode 100644 index 000000000..54c5e2f40 --- /dev/null +++ b/.changeset/support_patterned_and_default_http_statuses.md @@ -0,0 +1,17 @@ +--- +default: minor +--- + +# Support patterned and default HTTP statuses + +HTTP statuses like `2XX` and `default` are now supported! + +A big thank you to: +- @PSU3D0 for PR #973 (eons ago 😅) +- @obs-gh-peterkolloch for PR #1300 +- @goodsonjr for PR #1304 + +Closes #1271 and #832 + +> [!NOTE] +> Custom template users: the `endpoint.responses` type has changed quite a bit. Check out #1303 for the changes. diff --git a/end_to_end_tests/__snapshots__/test_end_to_end.ambr b/end_to_end_tests/__snapshots__/test_end_to_end.ambr index 525f8baf2..9558b006f 100644 --- a/end_to_end_tests/__snapshots__/test_end_to_end.ambr +++ b/end_to_end_tests/__snapshots__/test_end_to_end.ambr @@ -6,7 +6,7 @@ WARNING parsing GET / within default. - Invalid response status code abcdef (not a valid HTTP status code), response will be omitted from generated client + Invalid response status code pattern: abcdef, response will be omitted from generated client If you believe this was a mistake or this tool is missing a feature you need, please open an issue at https://github.com/openapi-generators/openapi-python-client/issues/new/choose diff --git a/end_to_end_tests/baseline_openapi_3.0.json b/end_to_end_tests/baseline_openapi_3.0.json index cd568541a..f3ce912c9 100644 --- a/end_to_end_tests/baseline_openapi_3.0.json +++ b/end_to_end_tests/baseline_openapi_3.0.json @@ -402,8 +402,7 @@ "some_nullable_object", "some_required_number" ], - "properties": { - } + "properties": {} } } }, @@ -975,6 +974,124 @@ } } }, + "/responses/status-codes/default": { + "get": { + "tags": [ + "responses" + ], + "summary": "Default Status Code Only", + "operationId": "default_status_code", + "responses": { + "default": { + "description": "Default response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/response/status-codes/patterns": { + "get": { + "tags": [ + "responses" + ], + "summary": "Status Code Patterns", + "operationId": "status_code_patterns", + "responses": { + "2XX": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "success", + "failure" + ] + } + } + } + } + } + }, + "4XX": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/response/status-codes/precedence": { + "get": { + "operationId": "status_code_precedence", + "tags": [ + "responses" + ], + "summary": "Status Codes Precedence", + "description": "Verify that specific status codes are always checked first, then ranges, then default", + "responses": { + "default": { + "description": "Default Response Should Be Last", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "4XX": { + "description": "Pattern should be after specific codes", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/auth/token_with_cookie": { "get": { "tags": [ @@ -1133,7 +1250,10 @@ }, "/tag_with_number": { "get": { - "tags": ["1", "2"], + "tags": [ + "1", + "2" + ], "responses": { "200": { "description": "Success" @@ -1668,7 +1788,9 @@ "type": "string" } }, - "required": ["type"] + "required": [ + "type" + ] }, { "type": "object", @@ -1680,7 +1802,9 @@ "type": "string" } }, - "required": ["type"] + "required": [ + "type" + ] } ] } diff --git a/end_to_end_tests/baseline_openapi_3.1.yaml b/end_to_end_tests/baseline_openapi_3.1.yaml index a45b2356a..74b85229d 100644 --- a/end_to_end_tests/baseline_openapi_3.1.yaml +++ b/end_to_end_tests/baseline_openapi_3.1.yaml @@ -958,6 +958,115 @@ info: } } }, + "/responses/status-codes/default": { + "get": { + "tags": ["responses"], + "summary": "Default Status Code Only", + "operationId": "default_status_code", + "responses": { + "default": { + "description": "Default response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/response/status-codes/patterns": { + "get": { + "tags": ["responses"], + "summary": "Status Code Patterns", + "operationId": "status_code_patterns", + "responses": { + "2XX": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["success", "failure"] + } + } + } + } + } + }, + "4XX": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/response/status-codes/precedence": { + "get": { + "operationId": "status_code_precedence", + "tags": ["responses"], + "summary": "Status Codes Precedence", + "description": "Verify that specific status codes are always checked first, then ranges, then default", + "responses": { + "default": { + "description": "Default Response Should Be Last", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "4XX": { + "description": "Pattern should be after specific codes", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/auth/token_with_cookie": { "get": { "tags": [ diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/responses/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/responses/__init__.py index e09dee3e3..d1361e311 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/responses/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/responses/__init__.py @@ -2,7 +2,14 @@ import types -from . import post_responses_unions_simple_before_complex, reference_response, text_response +from . import ( + default_status_code, + post_responses_unions_simple_before_complex, + reference_response, + status_code_patterns, + status_code_precedence, + text_response, +) class ResponsesEndpoints: @@ -26,3 +33,24 @@ def reference_response(cls) -> types.ModuleType: Endpoint using predefined response """ return reference_response + + @classmethod + def default_status_code(cls) -> types.ModuleType: + """ + Default Status Code Only + """ + return default_status_code + + @classmethod + def status_code_patterns(cls) -> types.ModuleType: + """ + Status Code Patterns + """ + return status_code_patterns + + @classmethod + def status_code_precedence(cls) -> types.ModuleType: + """ + Verify that specific status codes are always checked first, then ranges, then default + """ + return status_code_precedence diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py index e49c19427..e4af47d5b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/json_like.py @@ -31,6 +31,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py index 652e2c6db..b9abb8190 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/post_bodies_multiple.py @@ -51,6 +51,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/refs.py b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/refs.py index 81812cdea..c476b5bef 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/bodies/refs.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/bodies/refs.py @@ -31,6 +31,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/config/content_type_override.py b/end_to_end_tests/golden-record/my_test_api_client/api/config/content_type_override.py index d2757f759..c3aaf7da7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/config/content_type_override.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/config/content_type_override.py @@ -31,6 +31,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt if response.status_code == 200: response_200 = cast(str, response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py index 7de222f55..50c9d46c6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_common_parameters.py @@ -30,6 +30,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_allof.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_allof.py index 9d837acd6..4c04fc4c6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_allof.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_allof.py @@ -25,6 +25,7 @@ def _parse_response( response_200 = GetModelsAllofResponse200.from_dict(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_oneof_with_required_const.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_oneof_with_required_const.py index 85f68fb7c..01479d008 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_oneof_with_required_const.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/get_models_oneof_with_required_const.py @@ -52,6 +52,7 @@ def _parse_response_200( response_200 = _parse_response_200(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py index 5bd941c69..e35eabb1b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/post_common_parameters.py @@ -30,6 +30,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py index fe7adf04c..516590c47 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/default/reserved_parameters.py @@ -33,6 +33,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py index ffc9b535e..c002334f2 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/defaults/defaults_tests_defaults_post.py @@ -92,10 +92,12 @@ def _parse_response( if response.status_code == 200: response_200 = response.json() return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/enums/bool_enum_tests_bool_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/enums/bool_enum_tests_bool_enum_post.py index 52385855c..327c4ed46 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/enums/bool_enum_tests_bool_enum_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/enums/bool_enum_tests_bool_enum_post.py @@ -30,6 +30,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/enums/int_enum_tests_int_enum_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/enums/int_enum_tests_int_enum_post.py index 26c3729fe..b0e0a411f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/enums/int_enum_tests_int_enum_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/enums/int_enum_tests_int_enum_post.py @@ -32,6 +32,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py index ad9428a72..67738790f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_header_types.py @@ -50,6 +50,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py index e28e37a36..4fc4fd529 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/location/get_location_query_optionality.py @@ -56,6 +56,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py index a0caba2d6..fec2d528e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/hyphen_in_path.py @@ -22,6 +22,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/mixed_case.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/mixed_case.py index 7df2d318f..71784c16a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/naming/mixed_case.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/mixed_case.py @@ -38,6 +38,7 @@ def _parse_response( response_200 = MixedCaseResponse200.from_dict(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py index bf1ebf6ca..968e9bef9 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/naming/post_naming_property_conflict_with_import.py @@ -38,6 +38,7 @@ def _parse_response( response_200 = PostNamingPropertyConflictWithImportResponse200.from_dict(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py index e7a8e2712..a835fc126 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameter_references/get_parameter_references_path_param.py @@ -46,6 +46,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py index 704996107..17019985a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/delete_common_parameters_overriding_param.py @@ -31,6 +31,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py index b6efbba9b..53df43186 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_common_parameters_overriding_param.py @@ -31,6 +31,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py index 6a7ed7fd5..b7da5fbf8 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/get_same_name_multiple_locations_param.py @@ -43,6 +43,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py index 44345aa26..3ca61fd2a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/parameters/multiple_path_parameters.py @@ -25,6 +25,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/default_status_code.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/default_status_code.py new file mode 100644 index 000000000..c9b80fbe5 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/default_status_code.py @@ -0,0 +1,114 @@ +from http import HTTPStatus +from typing import Any, Optional, Union + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response + + +def _get_kwargs() -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/responses/status-codes/default", + } + + return _kwargs + + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> str: + response_default = response.text + return response_default + + +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]: + """Default Status Code Only + + 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]: + """Default Status Code Only + + 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]: + """Default Status Code Only + + 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]: + """Default Status Code Only + + 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 diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py index cf0599306..ac6535a5e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/post_responses_unions_simple_before_complex.py @@ -27,6 +27,7 @@ def _parse_response( response_200 = PostResponsesUnionsSimpleBeforeComplexResponse200.from_dict(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/reference_response.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/reference_response.py index ac71e9e50..096c7844c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/reference_response.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/reference_response.py @@ -23,6 +23,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt response_200 = AModel.from_dict(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/status_code_patterns.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/status_code_patterns.py new file mode 100644 index 000000000..84d4983b0 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/status_code_patterns.py @@ -0,0 +1,133 @@ +from http import HTTPStatus +from typing import Any, Optional, Union + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.status_code_patterns_response_2xx import StatusCodePatternsResponse2XX +from ...models.status_code_patterns_response_4xx import StatusCodePatternsResponse4XX +from ...types import Response + + +def _get_kwargs() -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/response/status-codes/patterns", + } + + return _kwargs + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]]: + if 200 <= response.status_code <= 299: + response_2xx = StatusCodePatternsResponse2XX.from_dict(response.json()) + + return response_2xx + + if 400 <= response.status_code <= 499: + response_4xx = StatusCodePatternsResponse4XX.from_dict(response.json()) + + return response_4xx + + 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[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]]: + 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[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]]: + """Status Code Patterns + + 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[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]] + """ + + kwargs = _get_kwargs() + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: Union[AuthenticatedClient, Client], +) -> Optional[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]]: + """Status Code Patterns + + 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: + Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX] + """ + + return sync_detailed( + client=client, + ).parsed + + +async def asyncio_detailed( + *, + client: Union[AuthenticatedClient, Client], +) -> Response[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]]: + """Status Code Patterns + + 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[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]] + """ + + 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[Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX]]: + """Status Code Patterns + + 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: + Union[StatusCodePatternsResponse2XX, StatusCodePatternsResponse4XX] + """ + + return ( + await asyncio_detailed( + client=client, + ) + ).parsed diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/status_code_precedence.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/status_code_precedence.py new file mode 100644 index 000000000..7a12dd723 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/status_code_precedence.py @@ -0,0 +1,134 @@ +from http import HTTPStatus +from typing import Any, Optional, Union + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response + + +def _get_kwargs() -> dict[str, Any]: + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/response/status-codes/precedence", + } + + return _kwargs + + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> str: + if response.status_code == 200: + response_200 = response.text + return response_200 + + if response.status_code == 404: + response_404 = response.text + return response_404 + + if 400 <= response.status_code <= 499: + response_4xx = response.text + return response_4xx + + response_default = response.text + return response_default + + +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]: + """Status Codes Precedence + + Verify that specific status codes are always checked first, then ranges, then default + + 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]: + """Status Codes Precedence + + Verify that specific status codes are always checked first, then ranges, then default + + 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]: + """Status Codes Precedence + + Verify that specific status codes are always checked first, then ranges, then default + + 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]: + """Status Codes Precedence + + Verify that specific status codes are always checked first, then ranges, then default + + 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 diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py b/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py index 057ceb2de..56b3dd85c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/responses/text_response.py @@ -21,6 +21,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt if response.status_code == 200: response_200 = response.text return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py b/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py index 62631355f..57bb070b3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tag1/get_tag_with_number.py @@ -20,6 +20,7 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tag2/get_tag_with_number.py b/end_to_end_tests/golden-record/my_test_api_client/api/tag2/get_tag_with_number.py index 62631355f..57bb070b3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tag2/get_tag_with_number.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tag2/get_tag_with_number.py @@ -20,6 +20,7 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py index dbda22bc3..d73d4a714 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/callback_test.py @@ -35,10 +35,12 @@ def _parse_response( if response.status_code == 200: response_200 = response.json() return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py index e7cd44f70..14848fdab 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/description_with_backslash.py @@ -20,6 +20,7 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py index 147eed3a7..182942660 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_booleans.py @@ -22,6 +22,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt response_200 = cast(list[bool], response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py index 02b3abb1f..af4faf108 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_floats.py @@ -22,6 +22,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt response_200 = cast(list[float], response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py index e71537363..365f32b3b 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_integers.py @@ -22,6 +22,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt response_200 = cast(list[int], response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py index 70f153829..5d1d337a7 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_basic_list_of_strings.py @@ -22,6 +22,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt response_200 = cast(list[str], response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py index a708cf71d..99806d8b5 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/get_user_list.py @@ -75,14 +75,17 @@ def _parse_response( response_200.append(response_200_item) return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if response.status_code == 423: response_423 = HTTPValidationError.from_dict(response.json()) return response_423 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py index f256727c2..c81285168 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/json_body_tests_json_body_post.py @@ -35,10 +35,12 @@ def _parse_response( if response.status_code == 200: response_200 = response.json() return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py index 586947f49..1a356815a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/no_response_tests_no_response_get.py @@ -20,6 +20,7 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py index efb0f4ae5..8ddba0532 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_get.py @@ -23,6 +23,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt response_200 = File(payload=BytesIO(response.content)) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py index bf4a1fcb0..5deb77db1 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/octet_stream_tests_octet_stream_post.py @@ -36,10 +36,12 @@ def _parse_response( response_200 = OctetStreamTestsOctetStreamPostResponse200.from_dict(response.json()) return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py index 41610afc0..900546434 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py @@ -31,6 +31,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py index 9bb3cd7c0..a6d58e574 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data_inline.py @@ -31,6 +31,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py index 4f879eed8..a2cf1fd9f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_tests_json_body_string.py @@ -34,10 +34,12 @@ def _parse_response( if response.status_code == 200: response_200 = cast(str, response.json()) return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py index 74eb8ae5c..632b3bdca 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/test_inline_objects.py @@ -36,6 +36,7 @@ def _parse_response( response_200 = TestInlineObjectsResponse200.from_dict(response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py index 22ac00650..d3acab9b1 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/token_with_cookie_auth_token_with_cookie_get.py @@ -27,8 +27,10 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if response.status_code == 401: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py index 61e8434e6..666fa781c 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/unsupported_content_tests_unsupported_content_get.py @@ -20,6 +20,7 @@ def _get_kwargs() -> dict[str, Any]: def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py index ad372b91f..1cf5931ab 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/upload_file_tests_upload_post.py @@ -33,10 +33,12 @@ def _parse_response( if response.status_code == 200: response_200 = response.json() return response_200 + if response.status_code == 422: response_422 = HTTPValidationError.from_dict(response.json()) return response_422 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py b/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py index b46550153..4079e804a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py +++ b/end_to_end_tests/golden-record/my_test_api_client/api/true_/false_.py @@ -30,6 +30,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 4d7471400..da9b57b85 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -86,6 +86,9 @@ from .post_responses_unions_simple_before_complex_response_200a_type_1 import ( PostResponsesUnionsSimpleBeforeComplexResponse200AType1, ) +from .status_code_patterns_response_2xx import StatusCodePatternsResponse2XX +from .status_code_patterns_response_2xx_status import StatusCodePatternsResponse2XXStatus +from .status_code_patterns_response_4xx import StatusCodePatternsResponse4XX from .test_inline_objects_body import TestInlineObjectsBody from .test_inline_objects_response_200 import TestInlineObjectsResponse200 from .validation_error import ValidationError @@ -167,6 +170,9 @@ "PostNamingPropertyConflictWithImportResponse200", "PostResponsesUnionsSimpleBeforeComplexResponse200", "PostResponsesUnionsSimpleBeforeComplexResponse200AType1", + "StatusCodePatternsResponse2XX", + "StatusCodePatternsResponse2XXStatus", + "StatusCodePatternsResponse4XX", "TestInlineObjectsBody", "TestInlineObjectsResponse200", "ValidationError", diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_2xx.py b/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_2xx.py new file mode 100644 index 000000000..ce23043fc --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_2xx.py @@ -0,0 +1,67 @@ +from collections.abc import Mapping +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.status_code_patterns_response_2xx_status import StatusCodePatternsResponse2XXStatus +from ..types import UNSET, Unset + +T = TypeVar("T", bound="StatusCodePatternsResponse2XX") + + +@_attrs_define +class StatusCodePatternsResponse2XX: + """ + Attributes: + status (Union[Unset, StatusCodePatternsResponse2XXStatus]): + """ + + status: Union[Unset, StatusCodePatternsResponse2XXStatus] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + status: Union[Unset, str] = UNSET + if not isinstance(self.status, Unset): + status = self.status.value + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if status is not UNSET: + field_dict["status"] = status + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + _status = d.pop("status", UNSET) + status: Union[Unset, StatusCodePatternsResponse2XXStatus] + if isinstance(_status, Unset): + status = UNSET + else: + status = StatusCodePatternsResponse2XXStatus(_status) + + status_code_patterns_response_2xx = cls( + status=status, + ) + + status_code_patterns_response_2xx.additional_properties = d + return status_code_patterns_response_2xx + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_2xx_status.py b/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_2xx_status.py new file mode 100644 index 000000000..0baaf6da8 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_2xx_status.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class StatusCodePatternsResponse2XXStatus(str, Enum): + FAILURE = "failure" + SUCCESS = "success" + + def __str__(self) -> str: + return str(self.value) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_4xx.py b/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_4xx.py new file mode 100644 index 000000000..3440448ca --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/status_code_patterns_response_4xx.py @@ -0,0 +1,59 @@ +from collections.abc import Mapping +from typing import Any, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="StatusCodePatternsResponse4XX") + + +@_attrs_define +class StatusCodePatternsResponse4XX: + """ + Attributes: + error (Union[Unset, str]): + """ + + error: Union[Unset, str] = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + error = self.error + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if error is not UNSET: + field_dict["error"] = error + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + error = d.pop("error", UNSET) + + status_code_patterns_response_4xx = cls( + error=error, + ) + + status_code_patterns_response_4xx.additional_properties = d + return status_code_patterns_response_4xx + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/bool_enum_tests_bool_enum_post.py b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/bool_enum_tests_bool_enum_post.py index 52385855c..327c4ed46 100644 --- a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/bool_enum_tests_bool_enum_post.py +++ b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/bool_enum_tests_bool_enum_post.py @@ -30,6 +30,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/int_enum_tests_int_enum_post.py b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/int_enum_tests_int_enum_post.py index af4c4ca22..737f9e70b 100644 --- a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/int_enum_tests_int_enum_post.py +++ b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/enums/int_enum_tests_int_enum_post.py @@ -32,6 +32,7 @@ def _get_kwargs( def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: if response.status_code == 200: return None + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/get_user_list.py b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/get_user_list.py index 00bc801d9..4b0be1e5d 100644 --- a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/get_user_list.py +++ b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/get_user_list.py @@ -78,6 +78,7 @@ def _parse_response( response_200.append(response_200_item) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/post_user_list.py b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/post_user_list.py index 223f5c073..50308a583 100644 --- a/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/post_user_list.py +++ b/end_to_end_tests/literal-enums-golden-record/my_enum_api_client/api/tests/post_user_list.py @@ -39,6 +39,7 @@ def _parse_response( response_200.append(response_200_item) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py b/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py index 1bb532823..d0e0b92e9 100644 --- a/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py +++ b/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/const/post_const_path.py @@ -50,6 +50,7 @@ def _parse_response( f"response_200 must match const 'Why have a fixed response? I dunno', got '{response_200}'" ) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/prefix_items/post_prefix_items.py b/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/prefix_items/post_prefix_items.py index 48a2c1733..14a8d9ddd 100644 --- a/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/prefix_items/post_prefix_items.py +++ b/end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/prefix_items/post_prefix_items.py @@ -32,6 +32,7 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt if response.status_code == 200: response_200 = cast(str, response.json()) return response_200 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/integration-tests/integration_tests/api/body/post_body_multipart.py b/integration-tests/integration_tests/api/body/post_body_multipart.py index 3f5c883f4..d8f903408 100644 --- a/integration-tests/integration_tests/api/body/post_body_multipart.py +++ b/integration-tests/integration_tests/api/body/post_body_multipart.py @@ -35,10 +35,12 @@ def _parse_response( response_200 = PostBodyMultipartResponse200.from_dict(response.json()) return response_200 + if response.status_code == 400: response_400 = PublicError.from_dict(response.json()) return response_400 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/integration-tests/integration_tests/api/parameters/post_parameters_header.py b/integration-tests/integration_tests/api/parameters/post_parameters_header.py index 0981585cc..582451881 100644 --- a/integration-tests/integration_tests/api/parameters/post_parameters_header.py +++ b/integration-tests/integration_tests/api/parameters/post_parameters_header.py @@ -42,10 +42,12 @@ def _parse_response( response_200 = PostParametersHeaderResponse200.from_dict(response.json()) return response_200 + if response.status_code == 400: response_400 = PublicError.from_dict(response.json()) return response_400 + if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index 0aab5a717..7b0ff691c 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -2,7 +2,6 @@ from collections.abc import Iterator from copy import deepcopy from dataclasses import dataclass, field -from http import HTTPStatus from typing import Any, Optional, Protocol, Union from pydantic import ValidationError @@ -26,7 +25,7 @@ property_from_data, ) from .properties.schemas import parameter_from_reference -from .responses import Response, response_from_data +from .responses import HTTPStatusPattern, Responses, response_from_data _PATH_PARAM_REGEX = re.compile("{([a-zA-Z_-][a-zA-Z0-9_-]*)}") @@ -147,7 +146,7 @@ class Endpoint: path_parameters: list[Property] = field(default_factory=list) header_parameters: list[Property] = field(default_factory=list) cookie_parameters: list[Property] = field(default_factory=list) - responses: list[Response] = field(default_factory=list) + responses: Responses = field(default_factory=lambda: Responses(patterns=[], default=None)) bodies: list[Body] = field(default_factory=list) errors: list[ParseError] = field(default_factory=list) @@ -162,19 +161,9 @@ def _add_responses( ) -> tuple["Endpoint", Schemas]: endpoint = deepcopy(endpoint) for code, response_data in data.items(): - status_code: HTTPStatus - try: - status_code = HTTPStatus(int(code)) - except ValueError: - endpoint.errors.append( - ParseError( - detail=( - f"Invalid response status code {code} (not a valid HTTP " - f"status code), response will be omitted from generated " - f"client" - ) - ) - ) + status_code = HTTPStatusPattern.parse(code) + if isinstance(status_code, ParseError): + endpoint.errors.append(status_code) continue response, schemas = response_from_data( @@ -190,7 +179,7 @@ def _add_responses( endpoint.errors.append( ParseError( detail=( - f"Cannot parse response for status code {status_code}{detail_suffix}, " + f"Cannot parse response for status code {code}{detail_suffix}, " f"response will be omitted from generated client" ), data=response.data, @@ -201,7 +190,11 @@ def _add_responses( # No reasons to use lazy imports in endpoints, so add lazy imports to relative here. endpoint.relative_imports |= response.prop.get_lazy_imports(prefix=models_relative_prefix) endpoint.relative_imports |= response.prop.get_imports(prefix=models_relative_prefix) - endpoint.responses.append(response) + if response.is_default(): + endpoint.responses.default = response + else: + endpoint.responses.patterns.append(response) + endpoint.responses.patterns.sort() return endpoint, schemas @staticmethod @@ -480,7 +473,7 @@ def response_type(self) -> str: if len(types) == 0: return "Any" if len(types) == 1: - return self.responses[0].prop.get_type_string(quoted=False) + return types[0] return f"Union[{', '.join(types)}]" def iter_all_parameters(self) -> Iterator[tuple[oai.ParameterLocation, Property]]: diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py index ec0f6136b..704a35f2d 100644 --- a/openapi_python_client/parser/responses.py +++ b/openapi_python_client/parser/responses.py @@ -1,6 +1,6 @@ -__all__ = ["Response", "response_from_data"] +__all__ = ["HTTPStatusPattern", "Response", "Responses", "response_from_data"] -from http import HTTPStatus +from collections.abc import Iterator from typing import Optional, TypedDict, Union from attrs import define @@ -15,6 +15,20 @@ from .properties import AnyProperty, Property, Schemas, property_from_data +@define +class Responses: + patterns: list["Response"] + default: Optional["Response"] + + def __iter__(self) -> Iterator["Response"]: + yield from self.patterns + if self.default: + yield self.default + + def __len__(self) -> int: + return len(self.patterns) + (1 if self.default else 0) + + class _ResponseSource(TypedDict): """What data should be pulled from the httpx Response object""" @@ -28,15 +42,91 @@ class _ResponseSource(TypedDict): NONE_SOURCE = _ResponseSource(attribute="None", return_type="None") -@define +class HTTPStatusPattern: + """Status code patterns come in three flavors, in order of precedence: + 1. Specific status codes, such as 200. This is represented by `min` and `max` being the same. + 2. Ranges of status codes, such as 2XX. This is represented by `min` and `max` being different. + 3. The special `default` status code, which is used when no other status codes match. `range` is `None` in this case. + + https://github.com/openapi-generators/openapi-python-client/blob/61b6c54994e2a6285bb422ee3b864c45b5d88c15/openapi_python_client/schema/3.1.0.md#responses-object + """ + + pattern: str + range: Optional[tuple[int, int]] + + def __init__(self, *, pattern: str, code_range: Optional[tuple[int, int]]): + """Initialize with a range of status codes or None for the default case.""" + self.pattern = pattern + self.range = code_range + + @staticmethod + def parse(pattern: str) -> Union["HTTPStatusPattern", ParseError]: + """Parse a status code pattern such as 2XX or 404""" + if pattern == "default": + return HTTPStatusPattern(pattern=pattern, code_range=None) + + if pattern.endswith("XX") and pattern[0].isdigit(): + first_digit = int(pattern[0]) + return HTTPStatusPattern(pattern=pattern, code_range=(first_digit * 100, first_digit * 100 + 99)) + + try: + code = int(pattern) + return HTTPStatusPattern(pattern=pattern, code_range=(code, code)) + except ValueError: + return ParseError( + detail=( + f"Invalid response status code pattern: {pattern}, response will be omitted from generated client" + ) + ) + + def is_range(self) -> bool: + """Check if this is a range of status codes, such as 2XX""" + return self.range is not None and self.range[0] != self.range[1] + + def __lt__(self, other: "HTTPStatusPattern") -> bool: + """Compare two HTTPStatusPattern objects based on the order they should be applied in""" + if self.range is None: + return False # Default gets applied last + if other.range is None: + return True # Other is default, so this one gets applied first + + # Specific codes appear before ranges + if self.is_range() and not other.is_range(): + return False + if not self.is_range() and other.is_range(): + return True + + # Order specific codes numerically + return self.range[0] < other.range[0] + + def __eq__(self, other: object) -> bool: # pragma: no cover + if not isinstance(other, HTTPStatusPattern): + return False + return self.range == other.range + + def __hash__(self) -> int: # pragma: no cover + return hash(self.range) + + def __repr__(self) -> str: # pragma: no cover + return f"" + + +@define(order=False) class Response: """Describes a single response for an endpoint""" - status_code: HTTPStatus + status_code: HTTPStatusPattern prop: Property source: _ResponseSource data: Union[oai.Response, oai.Reference] # Original data which created this response, useful for custom templates + def is_default(self) -> bool: + return self.status_code.range is None + + def __lt__(self, other: "Response") -> bool: + """Compare two responses based on the order in which they should be applied in""" + return self.status_code < other.status_code + def _source_by_content_type(content_type: str, config: Config) -> Optional[_ResponseSource]: parsed_content_type = utils.get_content_type(content_type, config) @@ -59,7 +149,7 @@ def _source_by_content_type(content_type: str, config: Config) -> Optional[_Resp def empty_response( *, - status_code: HTTPStatus, + status_code: HTTPStatusPattern, response_name: str, config: Config, data: Union[oai.Response, oai.Reference], @@ -82,7 +172,7 @@ def empty_response( def response_from_data( # noqa: PLR0911 *, - status_code: HTTPStatus, + status_code: HTTPStatusPattern, data: Union[oai.Response, oai.Reference], schemas: Schemas, responses: dict[str, Union[oai.Response, oai.Reference]], @@ -91,7 +181,7 @@ def response_from_data( # noqa: PLR0911 ) -> tuple[Union[Response, ParseError], Schemas]: """Generate a Response from the OpenAPI dictionary representation of it""" - response_name = f"response_{status_code}" + response_name = f"response_{status_code.pattern}" if isinstance(data, oai.Reference): ref_path = parse_reference_path(data.ref) if isinstance(ref_path, ParseError): diff --git a/openapi_python_client/templates/endpoint_macros.py.jinja b/openapi_python_client/templates/endpoint_macros.py.jinja index 1b53becdd..9f0ea6b55 100644 --- a/openapi_python_client/templates/endpoint_macros.py.jinja +++ b/openapi_python_client/templates/endpoint_macros.py.jinja @@ -184,3 +184,18 @@ Returns: {% macro docstring(endpoint, return_string, is_detailed) %} {{ safe_docstring(docstring_content(endpoint, return_string, is_detailed)) }} {% endmacro %} + +{% macro parse_response(parsed_responses, response) %} +{% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %} +{% if prop_template.construct %} +{{ prop_template.construct(response.prop, response.source.attribute) }} +{% elif response.source.return_type == response.prop.get_type_string() %} +{{ response.prop.python_name }} = {{ response.source.attribute }} +{% else %} +{{ response.prop.python_name }} = cast({{ response.prop.get_type_string() }}, {{ response.source.attribute }}) +{% endif %} +return {{ response.prop.python_name }} +{% else %} +return None +{% endif %} +{% endmacro %} diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 802fcc2ea..a7b82df90 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -64,27 +64,31 @@ def _get_kwargs( {% endif %} return _kwargs +{% if endpoint.responses.default %} + {% set return_type = return_string %} +{% else %} + {% set return_type = "Optional[" + return_string + "]" %} +{% endif %} -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[{{ return_string }}]: - {% for response in endpoint.responses %} - if response.status_code == {{ response.status_code.value }}: - {% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %} - {% if prop_template.construct %} - {{ prop_template.construct(response.prop, response.source.attribute) | indent(8) }} - {% elif response.source.return_type == response.prop.get_type_string() %} - {{ response.prop.python_name }} = {{ response.source.attribute }} - {% else %} - {{ response.prop.python_name }} = cast({{ response.prop.get_type_string() }}, {{ response.source.attribute }}) - {% endif %} - return {{ response.prop.python_name }} - {% else %} - return None - {% endif %} + +def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> {{return_type}}: + {% for response in endpoint.responses.patterns %} + {% set code_range = response.status_code.range %} + {% if code_range[0] == code_range[1] %} + if response.status_code == {{ code_range[0] }}: + {% else %} + if {{ code_range[0] }} <= response.status_code <= {{ code_range[1] }}: + {% endif %} + {{ parse_response(parsed_responses, response) | indent(8) }} {% endfor %} + {% if endpoint.responses.default %} + {{ parse_response(parsed_responses, endpoint.responses.default) | indent(4) }} + {% else %} if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: return None + {% endif %} def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[{{ return_string }}]: diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index 3d1391ae2..8ab663418 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -24,80 +24,6 @@ def make_endpoint(self): relative_imports={"import_3"}, ) - @pytest.mark.parametrize("response_status_code", ["not_a_number", 499]) - def test__add_responses_status_code_error(self, response_status_code, mocker): - schemas = Schemas() - response_1_data = mocker.MagicMock() - data = { - response_status_code: response_1_data, - } - endpoint = self.make_endpoint() - parse_error = ParseError(data=mocker.MagicMock()) - response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", return_value=(parse_error, schemas)) - config = MagicMock() - - response, schemas = Endpoint._add_responses( - endpoint=endpoint, data=data, schemas=schemas, responses={}, config=config - ) - - assert response.errors == [ - ParseError( - detail=f"Invalid response status code {response_status_code} (not a valid HTTP status code), " - "response will be omitted from generated client" - ) - ] - response_from_data.assert_not_called() - - def test__add_responses_error(self, mocker): - schemas = Schemas() - response_1_data = mocker.MagicMock() - response_2_data = mocker.MagicMock() - data = { - "200": response_1_data, - "404": response_2_data, - } - endpoint = self.make_endpoint() - parse_error = ParseError(data=mocker.MagicMock(), detail="some problem") - response_from_data = mocker.patch(f"{MODULE_NAME}.response_from_data", return_value=(parse_error, schemas)) - config = MagicMock() - - response, schemas = Endpoint._add_responses( - endpoint=endpoint, data=data, schemas=schemas, responses={}, config=config - ) - - response_from_data.assert_has_calls( - [ - mocker.call( - status_code=200, - data=response_1_data, - schemas=schemas, - responses={}, - parent_name="name", - config=config, - ), - mocker.call( - status_code=404, - data=response_2_data, - schemas=schemas, - responses={}, - parent_name="name", - config=config, - ), - ] - ) - assert response.errors == [ - ParseError( - detail="Cannot parse response for status code 200 (some problem), " - "response will be omitted from generated client", - data=parse_error.data, - ), - ParseError( - detail="Cannot parse response for status code 404 (some problem), " - "response will be omitted from generated client", - data=parse_error.data, - ), - ] - def test_add_parameters_handles_no_params(self): endpoint = self.make_endpoint() schemas = Schemas() @@ -685,7 +611,7 @@ def test_response_type(self, response_types, expected): for response_type in response_types: mock_response = MagicMock() mock_response.prop.get_type_string.return_value = response_type - endpoint.responses.append(mock_response) + endpoint.responses.patterns.append(mock_response) assert endpoint.response_type() == expected diff --git a/tests/test_parser/test_responses.py b/tests/test_parser/test_responses.py index 8fb04d720..22c3ba613 100644 --- a/tests/test_parser/test_responses.py +++ b/tests/test_parser/test_responses.py @@ -6,7 +6,13 @@ from openapi_python_client.parser import responses from openapi_python_client.parser.errors import ParseError, PropertyError from openapi_python_client.parser.properties import Schemas -from openapi_python_client.parser.responses import JSON_SOURCE, NONE_SOURCE, Response, response_from_data +from openapi_python_client.parser.responses import ( + JSON_SOURCE, + NONE_SOURCE, + HTTPStatusPattern, + Response, + response_from_data, +) MODULE_NAME = "openapi_python_client.parser.responses" @@ -15,7 +21,7 @@ def test_response_from_data_no_content(any_property_factory): data = oai.Response.model_construct(description="") response, schemas = response_from_data( - status_code=200, + status_code=HTTPStatusPattern(pattern="200", code_range=(200, 200)), data=data, schemas=Schemas(), responses={}, @@ -24,7 +30,7 @@ def test_response_from_data_no_content(any_property_factory): ) assert response == Response( - status_code=200, + status_code=HTTPStatusPattern(pattern="200", code_range=(200, 200)), prop=any_property_factory( name="response_200", default=None, @@ -36,12 +42,15 @@ def test_response_from_data_no_content(any_property_factory): ) +status_code = HTTPStatusPattern(pattern="200", code_range=(200, 200)) + + def test_response_from_data_unsupported_content_type(): data = oai.Response.model_construct(description="", content={"blah": None}) config = MagicMock() config.content_type_overrides = {} response, schemas = response_from_data( - status_code=200, + status_code=status_code, data=data, schemas=Schemas(), responses={}, @@ -60,7 +69,7 @@ def test_response_from_data_no_content_schema(any_property_factory): config = MagicMock() config.content_type_overrides = {} response, schemas = response_from_data( - status_code=200, + status_code=status_code, data=data, schemas=Schemas(), responses={}, @@ -69,7 +78,7 @@ def test_response_from_data_no_content_schema(any_property_factory): ) assert response == Response( - status_code=200, + status_code=status_code, prop=any_property_factory( name="response_200", default=None, @@ -91,7 +100,7 @@ def test_response_from_data_property_error(mocker): config.content_type_overrides = {} response, schemas = responses.response_from_data( - status_code=400, + status_code=HTTPStatusPattern(pattern="400", code_range=(400, 400)), data=data, schemas=Schemas(), responses={}, @@ -119,9 +128,10 @@ def test_response_from_data_property(mocker, any_property_factory): ) config = MagicMock() config.content_type_overrides = {} + status_code = HTTPStatusPattern(pattern="400", code_range=(400, 400)) response, schemas = responses.response_from_data( - status_code=400, + status_code=status_code, data=data, schemas=Schemas(), responses={}, @@ -130,7 +140,7 @@ def test_response_from_data_property(mocker, any_property_factory): ) assert response == responses.Response( - status_code=400, + status_code=status_code, prop=prop, source=JSON_SOURCE, data=data, @@ -156,7 +166,7 @@ def test_response_from_data_reference(mocker, any_property_factory): config.content_type_overrides = {} response, schemas = responses.response_from_data( - status_code=400, + status_code=HTTPStatusPattern(pattern="400", code_range=(400, 400)), data=oai.Reference.model_construct(ref="#/components/responses/ErrorResponse"), schemas=Schemas(), responses={"ErrorResponse": predefined_response_data}, @@ -165,7 +175,7 @@ def test_response_from_data_reference(mocker, any_property_factory): ) assert response == responses.Response( - status_code=400, + status_code=HTTPStatusPattern(pattern="400", code_range=(400, 400)), prop=prop, source=JSON_SOURCE, data=predefined_response_data, @@ -191,7 +201,7 @@ def test_response_from_data_invalid_reference(ref_string, expected_error_string, config.content_type_overrides = {} response, schemas = responses.response_from_data( - status_code=400, + status_code=HTTPStatusPattern(pattern="400", code_range=(400, 400)), data=oai.Reference.model_construct(ref=ref_string), schemas=Schemas(), responses={"ErrorResponse": predefined_response_data}, @@ -217,7 +227,7 @@ def test_response_from_data_ref_to_response_that_is_a_ref(mocker, any_property_f config.content_type_overrides = {} response, schemas = responses.response_from_data( - status_code=400, + status_code=HTTPStatusPattern(pattern="400", code_range=(400, 400)), data=oai.Reference.model_construct(ref="#/components/responses/ErrorResponse"), schemas=Schemas(), responses={ @@ -229,7 +239,7 @@ def test_response_from_data_ref_to_response_that_is_a_ref(mocker, any_property_f ) assert isinstance(response, ParseError) - assert "Top-level $ref" in response.detail + assert response.detail is not None and "Top-level $ref" in response.detail def test_response_from_data_content_type_overrides(any_property_factory): @@ -240,7 +250,7 @@ def test_response_from_data_content_type_overrides(any_property_factory): config = MagicMock() config.content_type_overrides = {"application/zip": "application/octet-stream"} response, schemas = response_from_data( - status_code=200, + status_code=HTTPStatusPattern(pattern="200", code_range=(200, 200)), data=data, schemas=Schemas(), responses={}, @@ -249,7 +259,7 @@ def test_response_from_data_content_type_overrides(any_property_factory): ) assert response == Response( - status_code=200, + status_code=HTTPStatusPattern(pattern="200", code_range=(200, 200)), prop=any_property_factory( name="response_200", default=None, @@ -259,3 +269,23 @@ def test_response_from_data_content_type_overrides(any_property_factory): source=NONE_SOURCE, data=data, ) + + +@pytest.mark.parametrize( + "pattern1, pattern2, result", + [ + ("400", "401", True), + ("503", "500", False), + ("default", "400", False), + ("400", "default", True), + ("2XX", "3XX", True), + ("3XX", "2XX", False), + ("2XX", "400", False), + ], +) +def test_http_status_pattern_lt(pattern1: str, pattern2: str, result: bool) -> None: + first = HTTPStatusPattern.parse(pattern1) + second = HTTPStatusPattern.parse(pattern2) + assert isinstance(first, HTTPStatusPattern) + assert isinstance(second, HTTPStatusPattern) + assert (first < second) == result