From d41503195b0b1a169cd33051b2550f65b6582331 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Fri, 19 Dec 2025 02:43:55 +0000 Subject: [PATCH 1/2] feat: Add --use-status-code-in-response-name option to include HTTP status code in response model names --- docs/cli-reference/openapi-only-options.md | 95 +++++++++++++++++++ src/datamodel_code_generator/__init__.py | 2 + src/datamodel_code_generator/__main__.py | 2 + src/datamodel_code_generator/arguments.py | 6 ++ src/datamodel_code_generator/cli_options.py | 3 + .../parser/openapi.py | 6 +- .../use_status_code_in_response_name.py | 23 +++++ .../use_status_code_in_response_name.yaml | 40 ++++++++ tests/main/openapi/test_main_openapi.py | 24 +++++ 9 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 tests/data/expected/main/openapi/use_status_code_in_response_name.py create mode 100644 tests/data/openapi/use_status_code_in_response_name.yaml diff --git a/docs/cli-reference/openapi-only-options.md b/docs/cli-reference/openapi-only-options.md index 6ca7cfc4a..ba58d7a1c 100644 --- a/docs/cli-reference/openapi-only-options.md +++ b/docs/cli-reference/openapi-only-options.md @@ -8,6 +8,7 @@ | [`--openapi-scopes`](#openapi-scopes) | Specify OpenAPI scopes to generate (schemas, paths, paramete... | | [`--read-only-write-only-model-type`](#read-only-write-only-model-type) | Generate separate request and response models for readOnly/w... | | [`--use-operation-id-as-name`](#use-operation-id-as-name) | Use OpenAPI operationId as the generated function/class name... | +| [`--use-status-code-in-response-name`](#use-status-code-in-response-name) | Include HTTP status code in response model names... | | [`--validation`](#validation) | Enable validation constraints (deprecated, use --field-const... | --- @@ -926,6 +927,100 @@ The `--use-operation-id-as-name` flag configures the code generation behavior. --- +## `--use-status-code-in-response-name` {#use-status-code-in-response-name} + +Include HTTP status code in response model names. + +The `--use-status-code-in-response-name` flag includes the HTTP status code +in generated response model class names. Instead of generating ambiguous names +like ResourceGetResponse, ResourceGetResponse1, ResourceGetResponse2, it generates +clear names like ResourceGetResponse200, ResourceGetResponse400, ResourceGetResponseDefault. + +**See also:** [OpenAPI-Specific Options](../openapi-options.md) + +!!! tip "Usage" + + ```bash + datamodel-codegen --input schema.json --use-status-code-in-response-name --openapi-scopes schemas paths # (1)! + ``` + + 1. :material-arrow-left: `--use-status-code-in-response-name` - the option documented here + +??? example "Input Schema" + + ```yaml + openapi: "3.0.0" + info: + version: 1.0.0 + title: Status Code Response Name Test API + paths: + /resource: + get: + summary: Get a resource + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + '400': + description: Bad request error + content: + application/json: + schema: + type: object + properties: + error: + type: string + code: + type: integer + 'default': + description: Unexpected error + content: + application/json: + schema: + type: object + properties: + message: + type: string + ``` + +??? example "Output" + + ```python + # generated by datamodel-codegen: + # filename: use_status_code_in_response_name.yaml + # timestamp: 2019-07-26T00:00:00+00:00 + + from __future__ import annotations + + from typing import Optional + + from pydantic import BaseModel + + + class ResourceGetResponse200(BaseModel): + id: Optional[int] = None + name: Optional[str] = None + + + class ResourceGetResponse400(BaseModel): + error: Optional[str] = None + code: Optional[int] = None + + + class ResourceGetResponseDefault(BaseModel): + message: Optional[str] = None + ``` + +--- + ## `--validation` {#validation} Enable validation constraints (deprecated, use --field-constraints). diff --git a/src/datamodel_code_generator/__init__.py b/src/datamodel_code_generator/__init__.py index 08337a822..349007000 100644 --- a/src/datamodel_code_generator/__init__.py +++ b/src/datamodel_code_generator/__init__.py @@ -477,6 +477,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 disable_future_imports: bool = False, type_mappings: list[str] | None = None, read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None, + use_status_code_in_response_name: bool = False, all_exports_scope: AllExportsScope | None = None, all_exports_collision_strategy: AllExportsCollisionStrategy | None = None, module_split_mode: ModuleSplitMode | None = None, @@ -536,6 +537,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 parser_class: type[Parser] = OpenAPIParser kwargs["openapi_scopes"] = openapi_scopes kwargs["include_path_parameters"] = include_path_parameters + kwargs["use_status_code_in_response_name"] = use_status_code_in_response_name elif input_file_type == InputFileType.GraphQL: from datamodel_code_generator.parser.graphql import GraphQLParser # noqa: PLC0415 diff --git a/src/datamodel_code_generator/__main__.py b/src/datamodel_code_generator/__main__.py index 7e2449649..bad69e60d 100644 --- a/src/datamodel_code_generator/__main__.py +++ b/src/datamodel_code_generator/__main__.py @@ -463,6 +463,7 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict disable_future_imports: bool = False type_mappings: Optional[list[str]] = None # noqa: UP045 read_only_write_only_model_type: Optional[ReadOnlyWriteOnlyModelType] = None # noqa: UP045 + use_status_code_in_response_name: bool = False all_exports_scope: Optional[AllExportsScope] = None # noqa: UP045 all_exports_collision_strategy: Optional[AllExportsCollisionStrategy] = None # noqa: UP045 module_split_mode: Optional[ModuleSplitMode] = None # noqa: UP045 @@ -765,6 +766,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917 disable_future_imports=config.disable_future_imports, type_mappings=config.type_mappings, read_only_write_only_model_type=config.read_only_write_only_model_type, + use_status_code_in_response_name=config.use_status_code_in_response_name, all_exports_scope=config.all_exports_scope, all_exports_collision_strategy=config.all_exports_collision_strategy, module_split_mode=config.module_split_mode, diff --git a/src/datamodel_code_generator/arguments.py b/src/datamodel_code_generator/arguments.py index 5e696504c..35f6da7ce 100644 --- a/src/datamodel_code_generator/arguments.py +++ b/src/datamodel_code_generator/arguments.py @@ -713,6 +713,12 @@ def start_section(self, heading: str | None) -> None: choices=[e.value for e in ReadOnlyWriteOnlyModelType], default=None, ) +openapi_options.add_argument( + "--use-status-code-in-response-name", + help="Include HTTP status code in response model names (e.g., ResourceGetResponse200, ResourceGetResponseDefault)", + action="store_true", + default=None, +) # ====================================================================================== # General options diff --git a/src/datamodel_code_generator/cli_options.py b/src/datamodel_code_generator/cli_options.py index 536198599..f3635a297 100644 --- a/src/datamodel_code_generator/cli_options.py +++ b/src/datamodel_code_generator/cli_options.py @@ -181,6 +181,9 @@ class CLIOptionMeta: # ========================================================================== "--openapi-scopes": CLIOptionMeta(name="--openapi-scopes", category=OptionCategory.OPENAPI), "--use-operation-id-as-name": CLIOptionMeta(name="--use-operation-id-as-name", category=OptionCategory.OPENAPI), + "--use-status-code-in-response-name": CLIOptionMeta( + name="--use-status-code-in-response-name", category=OptionCategory.OPENAPI + ), "--read-only-write-only-model-type": CLIOptionMeta( name="--read-only-write-only-model-type", category=OptionCategory.OPENAPI ), diff --git a/src/datamodel_code_generator/parser/openapi.py b/src/datamodel_code_generator/parser/openapi.py index bd2bd7d28..fa337bc54 100644 --- a/src/datamodel_code_generator/parser/openapi.py +++ b/src/datamodel_code_generator/parser/openapi.py @@ -272,6 +272,7 @@ def __init__( # noqa: PLR0913 type_mappings: list[str] | None = None, read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None, use_frozen_field: bool = False, + use_status_code_in_response_name: bool = False, ) -> None: """Initialize the OpenAPI parser with extensive configuration options.""" target_datetime_class = target_datetime_class or DatetimeClassType.Awaredatetime @@ -369,6 +370,7 @@ def __init__( # noqa: PLR0913 ) self.open_api_scopes: list[OpenAPIScope] = openapi_scopes or [OpenAPIScope.Schemas] self.include_path_parameters: bool = include_path_parameters + self.use_status_code_in_response_name: bool = use_status_code_in_response_name self._discriminator_schemas: dict[str, dict[str, Any]] = {} self._discriminator_subtypes: dict[str, list[str]] = defaultdict(list) @@ -550,6 +552,8 @@ def parse_responses( """Parse response objects into data types by status code and content type.""" data_types: defaultdict[str | int, dict[str, DataType]] = defaultdict(dict) for status_code, detail in responses.items(): + response_name = f"{name}{str(status_code).capitalize()}" if self.use_status_code_in_response_name else name + if isinstance(detail, ReferenceObject): if not detail.ref: # pragma: no cover continue @@ -563,7 +567,7 @@ def parse_responses( for content_type, obj in content.items(): response_path: list[str] = [*path, str(status_code), str(content_type)] - data_type = self._parse_schema_or_ref(name, obj.schema_, response_path) + data_type = self._parse_schema_or_ref(response_name, obj.schema_, response_path) if data_type: data_types[status_code][content_type] = data_type # pyright: ignore[reportArgumentType] diff --git a/tests/data/expected/main/openapi/use_status_code_in_response_name.py b/tests/data/expected/main/openapi/use_status_code_in_response_name.py new file mode 100644 index 000000000..7861a8d86 --- /dev/null +++ b/tests/data/expected/main/openapi/use_status_code_in_response_name.py @@ -0,0 +1,23 @@ +# generated by datamodel-codegen: +# filename: use_status_code_in_response_name.yaml +# timestamp: 2019-07-26T00:00:00+00:00 + +from __future__ import annotations + +from typing import Optional + +from pydantic import BaseModel + + +class ResourceGetResponse200(BaseModel): + id: Optional[int] = None + name: Optional[str] = None + + +class ResourceGetResponse400(BaseModel): + error: Optional[str] = None + code: Optional[int] = None + + +class ResourceGetResponseDefault(BaseModel): + message: Optional[str] = None diff --git a/tests/data/openapi/use_status_code_in_response_name.yaml b/tests/data/openapi/use_status_code_in_response_name.yaml new file mode 100644 index 000000000..b3fed2003 --- /dev/null +++ b/tests/data/openapi/use_status_code_in_response_name.yaml @@ -0,0 +1,40 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Status Code Response Name Test API +paths: + /resource: + get: + summary: Get a resource + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + '400': + description: Bad request error + content: + application/json: + schema: + type: object + properties: + error: + type: string + code: + type: integer + 'default': + description: Unexpected error + content: + application/json: + schema: + type: object + properties: + message: + type: string diff --git a/tests/main/openapi/test_main_openapi.py b/tests/main/openapi/test_main_openapi.py index 6aae90f1b..7f9740547 100644 --- a/tests/main/openapi/test_main_openapi.py +++ b/tests/main/openapi/test_main_openapi.py @@ -4223,3 +4223,27 @@ def test_main_openapi_null_only_enum(output_file: Path) -> None: assert_func=assert_file_content, expected_file="null_only_enum.py", ) + + +@pytest.mark.cli_doc( + options=["--use-status-code-in-response-name"], + input_schema="openapi/use_status_code_in_response_name.yaml", + cli_args=["--use-status-code-in-response-name", "--openapi-scopes", "schemas", "paths"], + golden_output="openapi/use_status_code_in_response_name.py", +) +def test_main_openapi_use_status_code_in_response_name(output_file: Path) -> None: + """Include HTTP status code in response model names. + + The `--use-status-code-in-response-name` flag includes the HTTP status code + in generated response model class names. Instead of generating ambiguous names + like ResourceGetResponse, ResourceGetResponse1, ResourceGetResponse2, it generates + clear names like ResourceGetResponse200, ResourceGetResponse400, ResourceGetResponseDefault. + """ + run_main_and_assert( + input_path=OPEN_API_DATA_PATH / "use_status_code_in_response_name.yaml", + output_path=output_file, + input_file_type="openapi", + assert_func=assert_file_content, + expected_file="use_status_code_in_response_name.py", + extra_args=["--use-status-code-in-response-name", "--openapi-scopes", "schemas", "paths"], + ) From 2e78cc2ae15e60ac6dd94e948abec7bda9093ada Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 Dec 2025 02:45:57 +0000 Subject: [PATCH 2/2] docs: update CLI reference documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by GitHub Actions --- docs/cli-reference/index.md | 3 ++- docs/cli-reference/openapi-only-options.md | 22 ++++++++++------------ docs/cli-reference/quick-reference.md | 2 ++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/cli-reference/index.md b/docs/cli-reference/index.md index 4a6b8d874..1014d131b 100644 --- a/docs/cli-reference/index.md +++ b/docs/cli-reference/index.md @@ -13,7 +13,7 @@ This documentation is auto-generated from test cases. | 🏷️ [Field Customization](field-customization.md) | 20 | Field naming and docstring behavior | | 🏗️ [Model Customization](model-customization.md) | 26 | Model generation behavior | | 🎨 [Template Customization](template-customization.md) | 16 | Output formatting and custom rendering | -| 📘 [OpenAPI-only Options](openapi-only-options.md) | 5 | OpenAPI-specific features | +| 📘 [OpenAPI-only Options](openapi-only-options.md) | 6 | OpenAPI-specific features | | ⚙️ [General Options](general-options.md) | 14 | Utilities and meta options | | 📝 [Utility Options](utility-options.md) | 5 | Help, version, debug options | @@ -172,6 +172,7 @@ This documentation is auto-generated from test cases. - [`--use-schema-description`](field-customization.md#use-schema-description) - [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) - [`--use-standard-collections`](typing-customization.md#use-standard-collections) +- [`--use-status-code-in-response-name`](openapi-only-options.md#use-status-code-in-response-name) - [`--use-subclass-enum`](model-customization.md#use-subclass-enum) - [`--use-title-as-name`](field-customization.md#use-title-as-name) - [`--use-type-alias`](typing-customization.md#use-type-alias) diff --git a/docs/cli-reference/openapi-only-options.md b/docs/cli-reference/openapi-only-options.md index ba58d7a1c..08aaef06c 100644 --- a/docs/cli-reference/openapi-only-options.md +++ b/docs/cli-reference/openapi-only-options.md @@ -8,7 +8,7 @@ | [`--openapi-scopes`](#openapi-scopes) | Specify OpenAPI scopes to generate (schemas, paths, paramete... | | [`--read-only-write-only-model-type`](#read-only-write-only-model-type) | Generate separate request and response models for readOnly/w... | | [`--use-operation-id-as-name`](#use-operation-id-as-name) | Use OpenAPI operationId as the generated function/class name... | -| [`--use-status-code-in-response-name`](#use-status-code-in-response-name) | Include HTTP status code in response model names... | +| [`--use-status-code-in-response-name`](#use-status-code-in-response-name) | Include HTTP status code in response model names. | | [`--validation`](#validation) | Enable validation constraints (deprecated, use --field-const... | --- @@ -936,8 +936,6 @@ in generated response model class names. Instead of generating ambiguous names like ResourceGetResponse, ResourceGetResponse1, ResourceGetResponse2, it generates clear names like ResourceGetResponse200, ResourceGetResponse400, ResourceGetResponseDefault. -**See also:** [OpenAPI-Specific Options](../openapi-options.md) - !!! tip "Usage" ```bash @@ -997,24 +995,24 @@ clear names like ResourceGetResponse200, ResourceGetResponse400, ResourceGetResp # generated by datamodel-codegen: # filename: use_status_code_in_response_name.yaml # timestamp: 2019-07-26T00:00:00+00:00 - + from __future__ import annotations - + from typing import Optional - + from pydantic import BaseModel - - + + class ResourceGetResponse200(BaseModel): id: Optional[int] = None name: Optional[str] = None - - + + class ResourceGetResponse400(BaseModel): error: Optional[str] = None code: Optional[int] = None - - + + class ResourceGetResponseDefault(BaseModel): message: Optional[str] = None ``` diff --git a/docs/cli-reference/quick-reference.md b/docs/cli-reference/quick-reference.md index 1cac69a72..b5e10beac 100644 --- a/docs/cli-reference/quick-reference.md +++ b/docs/cli-reference/quick-reference.md @@ -128,6 +128,7 @@ datamodel-codegen [OPTIONS] | [`--openapi-scopes`](openapi-only-options.md#openapi-scopes) | Specify OpenAPI scopes to generate (schemas, paths, parameters). | | [`--read-only-write-only-model-type`](openapi-only-options.md#read-only-write-only-model-type) | Generate separate request and response models for readOnly/writeOnly fields. | | [`--use-operation-id-as-name`](openapi-only-options.md#use-operation-id-as-name) | Use OpenAPI operationId as the generated function/class name. | +| [`--use-status-code-in-response-name`](openapi-only-options.md#use-status-code-in-response-name) | Include HTTP status code in response model names. | | [`--validation`](openapi-only-options.md#validation) | Enable validation constraints (deprecated, use --field-constraints). | ### ⚙️ General Options @@ -262,6 +263,7 @@ All options sorted alphabetically: - [`--use-schema-description`](field-customization.md#use-schema-description) - Use schema description as class docstring. - [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) - Wrap fields with subtypes in Pydantic's SerializeAsAny. - [`--use-standard-collections`](typing-customization.md#use-standard-collections) - Use built-in dict/list instead of typing.Dict/List. +- [`--use-status-code-in-response-name`](openapi-only-options.md#use-status-code-in-response-name) - Include HTTP status code in response model names. - [`--use-subclass-enum`](model-customization.md#use-subclass-enum) - Generate typed Enum subclasses for enums with specific field... - [`--use-title-as-name`](field-customization.md#use-title-as-name) - Use schema title as the generated class name. - [`--use-type-alias`](typing-customization.md#use-type-alias) - Use TypeAlias instead of root models for type definitions (e...