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
81 changes: 81 additions & 0 deletions docs/cli-reference/field-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
| [`--use-field-description-example`](#use-field-description-example) | Add field examples to docstrings. |
| [`--use-inline-field-description`](#use-inline-field-description) | Add field descriptions as inline comments. |
| [`--use-schema-description`](#use-schema-description) | Use schema description as class docstring. |
| [`--use-serialization-alias`](#use-serialization-alias) | Use serialization_alias instead of alias for field aliasing ... |
| [`--use-title-as-name`](#use-title-as-name) | Use schema title as the generated class name. |

---
Expand Down Expand Up @@ -3663,6 +3664,86 @@ useful for preserving documentation from your schema in the generated code.

---

## `--use-serialization-alias` {#use-serialization-alias}

Use serialization_alias instead of alias for field aliasing (Pydantic v2 only).

The `--use-serialization-alias` flag changes field aliasing to use `serialization_alias`
instead of `alias`. This allows setting values using the Pythonic field name while
serializing to the original JSON property name.

!!! tip "Usage"

```bash
datamodel-codegen --input schema.json --use-serialization-alias --output-model-type pydantic_v2.BaseModel # (1)!
```

1. :material-arrow-left: `--use-serialization-alias` - the option documented here

??? example "Examples"

**Input Schema:**

```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"first-name": {
"type": "string"
},
"last-name": {
"type": "string"
},
"email_address": {
"type": "string"
}
},
"required": ["first-name", "last-name"]
}
```

**Output:**

=== "With Option"

```python
# generated by datamodel-codegen:
# filename: no_alias.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel, Field


class Person(BaseModel):
first_name: str = Field(..., serialization_alias='first-name')
last_name: str = Field(..., serialization_alias='last-name')
email_address: str | None = None
```

=== "Without Option"

```python
# generated by datamodel-codegen:
# filename: no_alias.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel, Field


class Person(BaseModel):
first_name: str = Field(..., alias='first-name')
last_name: str = Field(..., alias='last-name')
email_address: str | None = None
```

---

## `--use-title-as-name` {#use-title-as-name}

Use schema title as the generated class name.
Expand Down
3 changes: 2 additions & 1 deletion docs/cli-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This documentation is auto-generated from test cases.
|----------|---------|-------------|
| 📁 [Base Options](base-options.md) | 7 | Input/output configuration |
| 🔧 [Typing Customization](typing-customization.md) | 27 | Type annotation and import behavior |
| 🏷️ [Field Customization](field-customization.md) | 23 | Field naming and docstring behavior |
| 🏷️ [Field Customization](field-customization.md) | 24 | Field naming and docstring behavior |
| 🏗️ [Model Customization](model-customization.md) | 39 | Model generation behavior |
| 🎨 [Template Customization](template-customization.md) | 18 | Output formatting and custom rendering |
| 📘 [OpenAPI-only Options](openapi-only-options.md) | 7 | OpenAPI-specific features |
Expand Down Expand Up @@ -203,6 +203,7 @@ This documentation is auto-generated from test cases.
- [`--use-pendulum`](typing-customization.md#use-pendulum)
- [`--use-root-model-type-alias`](typing-customization.md#use-root-model-type-alias)
- [`--use-schema-description`](field-customization.md#use-schema-description)
- [`--use-serialization-alias`](field-customization.md#use-serialization-alias)
- [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any)
- [`--use-specialized-enum`](typing-customization.md#use-specialized-enum)
- [`--use-standard-collections`](typing-customization.md#use-standard-collections)
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 @@ -82,6 +82,7 @@ datamodel-codegen [OPTIONS]
| [`--use-field-description-example`](field-customization.md#use-field-description-example) | Add field examples to docstrings. |
| [`--use-inline-field-description`](field-customization.md#use-inline-field-description) | Add field descriptions as inline comments. |
| [`--use-schema-description`](field-customization.md#use-schema-description) | Use schema description as class docstring. |
| [`--use-serialization-alias`](field-customization.md#use-serialization-alias) | Use serialization_alias instead of alias for field aliasing (Pydantic v2 only). |
| [`--use-title-as-name`](field-customization.md#use-title-as-name) | Use schema title as the generated class name. |

### 🏗️ Model Customization
Expand Down Expand Up @@ -333,6 +334,7 @@ All options sorted alphabetically:
- [`--use-pendulum`](typing-customization.md#use-pendulum) - Use pendulum types for date/time fields instead of datetime ...
- [`--use-root-model-type-alias`](typing-customization.md#use-root-model-type-alias) - Generate RootModel as type alias format for better mypy supp...
- [`--use-schema-description`](field-customization.md#use-schema-description) - Use schema description as class docstring.
- [`--use-serialization-alias`](field-customization.md#use-serialization-alias) - Use serialization_alias instead of alias for field aliasing ...
- [`--use-serialize-as-any`](model-customization.md#use-serialize-as-any) - Wrap fields with subtypes in Pydantic's SerializeAsAny.
- [`--use-specialized-enum`](typing-customization.md#use-specialized-enum) - Generate StrEnum/IntEnum for string/integer enums (Python 3....
- [`--use-standard-collections`](typing-customization.md#use-standard-collections) - Use built-in dict/list instead of typing.Dict/List.
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 @@ -595,6 +595,7 @@ def validate_class_name_affix_scope(cls, v: str | ClassNameAffixScope | None) ->
frozen_dataclasses: bool = False
dataclass_arguments: Optional[DataclassArguments] = None # noqa: UP045
no_alias: bool = False
use_serialization_alias: bool = False
use_frozen_field: bool = False
use_default_factory_for_optional_nested_models: bool = False
formatters: list[Formatter] | None = None
Expand Down Expand Up @@ -1007,6 +1008,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
keyword_only=config.keyword_only,
frozen_dataclasses=config.frozen_dataclasses,
no_alias=config.no_alias,
use_serialization_alias=config.use_serialization_alias,
use_frozen_field=config.use_frozen_field,
use_default_factory_for_optional_nested_models=config.use_default_factory_for_optional_nested_models,
formatters=config.formatters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class GenerateConfigDict(TypedDict):
keyword_only: NotRequired[bool]
frozen_dataclasses: NotRequired[bool]
no_alias: NotRequired[bool]
use_serialization_alias: NotRequired[bool]
use_frozen_field: NotRequired[bool]
use_default_factory_for_optional_nested_models: NotRequired[bool]
formatters: NotRequired[list[Formatter] | None]
Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/_types/parser_config_dicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class ParserConfigDict(TypedDict):
keyword_only: NotRequired[bool]
frozen_dataclasses: NotRequired[bool]
no_alias: NotRequired[bool]
use_serialization_alias: NotRequired[bool]
use_frozen_field: NotRequired[bool]
use_default_factory_for_optional_nested_models: NotRequired[bool]
formatters: NotRequired[list[Formatter] | None]
Expand Down
7 changes: 7 additions & 0 deletions src/datamodel_code_generator/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,13 @@ def start_section(self, heading: str | None) -> None:
action="store_true",
default=None,
)
field_options.add_argument(
"--use-serialization-alias",
help="Use serialization_alias instead of alias for field aliasing (Pydantic v2 only). "
"This allows setting values using the Pythonic field name while serializing to the original name.",
action="store_true",
default=None,
)
field_options.add_argument(
"--use-frozen-field",
help="Use Field(frozen=True) for readOnly fields (Pydantic v2) or Field(allow_mutation=False) (Pydantic v1)",
Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/cli_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class CLIOptionMeta:
"--aliases": CLIOptionMeta(name="--aliases", category=OptionCategory.FIELD),
"--default-values": CLIOptionMeta(name="--default-values", category=OptionCategory.FIELD),
"--no-alias": CLIOptionMeta(name="--no-alias", category=OptionCategory.FIELD),
"--use-serialization-alias": CLIOptionMeta(name="--use-serialization-alias", category=OptionCategory.FIELD),
"--use-title-as-name": CLIOptionMeta(name="--use-title-as-name", category=OptionCategory.FIELD),
"--use-schema-description": CLIOptionMeta(name="--use-schema-description", category=OptionCategory.FIELD),
"--use-field-description": CLIOptionMeta(name="--use-field-description", category=OptionCategory.FIELD),
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class Config:
keyword_only: bool = False
frozen_dataclasses: bool = False
no_alias: bool = False
use_serialization_alias: bool = False
use_frozen_field: bool = False
use_default_factory_for_optional_nested_models: bool = False
formatters: list[Formatter] | None = None
Expand Down Expand Up @@ -318,6 +319,7 @@ class Config:
keyword_only: bool = False
frozen_dataclasses: bool = False
no_alias: bool = False
use_serialization_alias: bool = False
use_frozen_field: bool = False
use_default_factory_for_optional_nested_models: bool = False
formatters: list[Formatter] | None = None
Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class Config:
read_only: bool = False
write_only: bool = False
use_frozen_field: bool = False
use_serialization_alias: bool = False
use_default_factory_for_optional_nested_models: bool = False

if not TYPE_CHECKING: # pragma: no branch
Expand Down
3 changes: 3 additions & 0 deletions src/datamodel_code_generator/model/pydantic_v2/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ def _process_data_in_str(self, data: dict[str, Any]) -> None:
aliases_repr = ", ".join(repr(a) for a in self.validation_aliases)
data["validation_alias"] = _RawRepr(f"AliasChoices({aliases_repr})")

if self.use_serialization_alias and "alias" in data:
data["serialization_alias"] = data.pop("alias")

# **extra is not supported in pydantic 2.0
json_schema_extra = {k: v for k, v in data.items() if k not in self._DEFAULT_FIELD_KEYS}
if json_schema_extra:
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ def __init__( # noqa: PLR0912, PLR0915
}
self.read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = config.read_only_write_only_model_type
self.use_frozen_field: bool = config.use_frozen_field
self.use_serialization_alias: bool = config.use_serialization_alias
self.use_default_factory_for_optional_nested_models: bool = (
config.use_default_factory_for_optional_nested_models
)
Expand Down Expand Up @@ -1463,6 +1464,7 @@ def check_paths(
required=True,
alias=single_alias,
validation_aliases=validation_aliases,
use_serialization_alias=self.use_serialization_alias,
)
)
has_imported_literal = any(import_ == IMPORT_LITERAL for import_ in imports)
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/parser/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def _typename_field(self, name: str) -> DataModelFieldBase:
use_one_literal_as_default=True,
use_default_kwarg=self.use_default_kwarg,
has_default=True,
use_serialization_alias=self.use_serialization_alias,
)

def _get_default( # noqa: PLR6301
Expand Down Expand Up @@ -417,6 +418,7 @@ def parse_field(
use_default_kwarg=self.use_default_kwarg,
original_name=field_name,
has_default=effective_has_default,
use_serialization_alias=self.use_serialization_alias,
)

def parse_object_like(
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ def get_object_field( # noqa: PLR0913
read_only=self._resolve_field_flag(field, "readOnly"),
write_only=self._resolve_field_flag(field, "writeOnly"),
use_frozen_field=self.use_frozen_field,
use_serialization_alias=self.use_serialization_alias,
use_default_factory_for_optional_nested_models=self.use_default_factory_for_optional_nested_models,
)

Expand Down Expand Up @@ -2221,6 +2222,7 @@ def _parse_all_of_item( # noqa: PLR0912, PLR0913, PLR0915, PLR0917
alias=single_alias,
validation_aliases=validation_aliases,
data_type=data_type,
use_serialization_alias=self.use_serialization_alias,
)
)
existing_field_names.update({request, field_name})
Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ def parse_all_parameters( # noqa: PLR0912, PLR0914
original_name=parameter_name,
has_default=effective_has_default,
type_has_null=object_schema.type_has_null if object_schema else None,
use_serialization_alias=self.use_serialization_alias,
)
)

Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/prompt_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"--use-pendulum": "Use pendulum types for date/time fields instead of datetime module.",
"--use-root-model-type-alias": "Generate RootModel as type alias format for better mypy support.",
"--use-schema-description": "Use schema description as class docstring.",
"--use-serialization-alias": "Use serialization_alias instead of alias for field aliasing (Pydantic v2 only).",
"--use-serialize-as-any": "Wrap fields with subtypes in Pydantic's SerializeAsAny.",
"--use-specialized-enum": "Generate StrEnum/IntEnum for string/integer enums (Python 3.11+).",
"--use-standard-collections": "Use built-in dict/list instead of typing.Dict/List.",
Expand Down
1 change: 1 addition & 0 deletions tests/data/expected/main/input_model/config_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class GenerateConfig(TypedDict):
keyword_only: NotRequired[bool]
frozen_dataclasses: NotRequired[bool]
no_alias: NotRequired[bool]
use_serialization_alias: NotRequired[bool]
use_frozen_field: NotRequired[bool]
use_default_factory_for_optional_nested_models: NotRequired[bool]
formatters: NotRequired[list[Formatter] | None]
Expand Down
13 changes: 13 additions & 0 deletions tests/data/expected/main_kr/use_serialization_alias/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# generated by datamodel-codegen:
# filename: no_alias.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel, Field


class Person(BaseModel):
first_name: str = Field(..., serialization_alias='first-name')
last_name: str = Field(..., serialization_alias='last-name')
email_address: str | None = None
2 changes: 2 additions & 0 deletions tests/main/test_public_api_signature_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def _baseline_generate(
keyword_only: bool = False,
frozen_dataclasses: bool = False,
no_alias: bool = False,
use_serialization_alias: bool = False,
use_frozen_field: bool = False,
use_default_factory_for_optional_nested_models: bool = False,
formatters: list[Formatter] | None = None,
Expand Down Expand Up @@ -295,6 +296,7 @@ def __init__(
keyword_only: bool = False,
frozen_dataclasses: bool = False,
no_alias: bool = False,
use_serialization_alias: bool = False,
use_frozen_field: bool = False,
use_default_factory_for_optional_nested_models: bool = False,
formatters: list[Formatter] | None = None,
Expand Down
30 changes: 30 additions & 0 deletions tests/test_main_kr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,36 @@ def test_no_alias(output_file: Path) -> None:
)


@pytest.mark.cli_doc(
options=["--use-serialization-alias"],
option_description="""Use serialization_alias instead of alias for field aliasing (Pydantic v2 only).

The `--use-serialization-alias` flag changes field aliasing to use `serialization_alias`
instead of `alias`. This allows setting values using the Pythonic field name while
serializing to the original JSON property name.""",
input_schema="jsonschema/no_alias.json",
cli_args=["--use-serialization-alias", "--output-model-type", "pydantic_v2.BaseModel"],
golden_output="main_kr/use_serialization_alias/output.py",
comparison_output="main_kr/no_alias/without_option.py",
)
@freeze_time("2019-07-26")
def test_use_serialization_alias(output_file: Path) -> None:
"""Use serialization_alias instead of alias for field aliasing (Pydantic v2 only).

The `--use-serialization-alias` flag changes field aliasing to use `serialization_alias`
instead of `alias`. This allows setting values using the Pythonic field name while
serializing to the original JSON property name.
"""
run_main_and_assert(
input_path=JSON_SCHEMA_DATA_PATH / "no_alias.json",
output_path=output_file,
input_file_type="jsonschema",
assert_func=assert_file_content,
expected_file=EXPECTED_MAIN_KR_PATH / "use_serialization_alias" / "output.py",
extra_args=["--use-serialization-alias", "--output-model-type", "pydantic_v2.BaseModel"],
)


@pytest.mark.cli_doc(
options=["--custom-file-header"],
option_description="""Add custom header text to the generated file.
Expand Down
Loading