Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/cli-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down Expand Up @@ -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)
Expand Down
93 changes: 93 additions & 0 deletions docs/cli-reference/openapi-only-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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... |

---
Expand Down Expand Up @@ -926,6 +927,98 @@ 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.

!!! 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).
Expand Down
2 changes: 2 additions & 0 deletions docs/cli-reference/quick-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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...
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/datamodel_code_generator/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/datamodel_code_generator/cli_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
Expand Down
6 changes: 5 additions & 1 deletion src/datamodel_code_generator/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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]

Expand Down
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions tests/data/openapi/use_status_code_in_response_name.yaml
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions tests/main/openapi/test_main_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
Loading