diff --git a/docs/cli-reference/field-customization.md b/docs/cli-reference/field-customization.md index 67336098a..8156d06c3 100644 --- a/docs/cli-reference/field-customization.md +++ b/docs/cli-reference/field-customization.md @@ -12,7 +12,6 @@ | [`--field-extra-keys`](#field-extra-keys) | Include specific extra keys in Field() definitions. | | [`--field-extra-keys-without-x-prefix`](#field-extra-keys-without-x-prefix) | Include specified schema extension keys in Field() without r... | | [`--field-include-all-keys`](#field-include-all-keys) | Include all schema keys in Field() json_schema_extra. | -| [`--field-type-collision-strategy`](#field-type-collision-strategy) | Configure how to resolve naming conflicts between field name... | | [`--no-alias`](#no-alias) | Disable Field alias generation for non-Python-safe property ... | | [`--original-field-name-delimiter`](#original-field-name-delimiter) | Specify delimiter for original field names when using snake-... | | [`--remove-special-field-name-prefix`](#remove-special-field-name-prefix) | Remove the special prefix from field names. | @@ -1814,71 +1813,6 @@ The `--field-include-all-keys` flag configures the code generation behavior. --- -## `--field-type-collision-strategy` {#field-type-collision-strategy} - -Configure how to resolve naming conflicts between field names and generated type names. - -When a schema property name matches a generated type name (e.g., a property "Fruit" with -an enum type that would also be named "Fruit"), a collision occurs. This option controls -the resolution strategy: - -- `rename-field` (default): Rename the field with a suffix (e.g., `Fruit_1`) and preserve - the original name via `Field(alias='Fruit')` -- `rename-type`: Rename the type class with a suffix (e.g., `Fruit_`) and keep the original - field name - -!!! tip "Usage" - - ```bash - datamodel-codegen --input schema.json --output-model-type pydantic_v2.BaseModel --field-type-collision-strategy rename-type # (1)! - ``` - - 1. :material-arrow-left: `--field-type-collision-strategy` - the option documented here - -??? example "Examples" - - **Input Schema:** - - ```json - { - "title": "Test", - "type": "object", - "properties": { - "Fruit": { - "enum": [ - "apple", - "banana" - ] - } - } - } - ``` - - **Output:** - - ```python - # generated by datamodel-codegen: - # filename: field_type_collision_rename_type.json - # timestamp: 2019-07-26T00:00:00+00:00 - - from __future__ import annotations - - from enum import Enum - - from pydantic import BaseModel - - - class Fruit_1(Enum): - apple = 'apple' - banana = 'banana' - - - class Test(BaseModel): - Fruit: Fruit_1 | None = None - ``` - ---- - ## `--no-alias` {#no-alias} Disable Field alias generation for non-Python-safe property names. diff --git a/docs/cli-reference/index.md b/docs/cli-reference/index.md index e509e3855..f7a3d7886 100644 --- a/docs/cli-reference/index.md +++ b/docs/cli-reference/index.md @@ -10,7 +10,7 @@ This documentation is auto-generated from test cases. |----------|---------|-------------| | 📁 [Base Options](base-options.md) | 5 | Input/output configuration | | 🔧 [Typing Customization](typing-customization.md) | 17 | Type annotation and import behavior | -| 🏷️ [Field Customization](field-customization.md) | 21 | Field naming and docstring behavior | +| 🏷️ [Field Customization](field-customization.md) | 20 | Field naming and docstring behavior | | 🏗️ [Model Customization](model-customization.md) | 29 | Model generation behavior | | 🎨 [Template Customization](template-customization.md) | 16 | Output formatting and custom rendering | | 📘 [OpenAPI-only Options](openapi-only-options.md) | 6 | OpenAPI-specific features | @@ -75,7 +75,6 @@ This documentation is auto-generated from test cases. - [`--field-extra-keys`](field-customization.md#field-extra-keys) - [`--field-extra-keys-without-x-prefix`](field-customization.md#field-extra-keys-without-x-prefix) - [`--field-include-all-keys`](field-customization.md#field-include-all-keys) -- [`--field-type-collision-strategy`](field-customization.md#field-type-collision-strategy) - [`--force-optional`](model-customization.md#force-optional) - [`--formatters`](template-customization.md#formatters) - [`--frozen-dataclasses`](model-customization.md#frozen-dataclasses) diff --git a/docs/cli-reference/quick-reference.md b/docs/cli-reference/quick-reference.md index 4e735d431..c8b2b343c 100644 --- a/docs/cli-reference/quick-reference.md +++ b/docs/cli-reference/quick-reference.md @@ -56,7 +56,6 @@ datamodel-codegen [OPTIONS] | [`--field-extra-keys`](field-customization.md#field-extra-keys) | Include specific extra keys in Field() definitions. | | [`--field-extra-keys-without-x-prefix`](field-customization.md#field-extra-keys-without-x-prefix) | Include specified schema extension keys in Field() without requiring 'x-' prefix... | | [`--field-include-all-keys`](field-customization.md#field-include-all-keys) | Include all schema keys in Field() json_schema_extra. | -| [`--field-type-collision-strategy`](field-customization.md#field-type-collision-strategy) | Configure how to resolve naming conflicts between field names and generated type... | | [`--no-alias`](field-customization.md#no-alias) | Disable Field alias generation for non-Python-safe property names. | | [`--original-field-name-delimiter`](field-customization.md#original-field-name-delimiter) | Specify delimiter for original field names when using snake-case conversion. | | [`--remove-special-field-name-prefix`](field-customization.md#remove-special-field-name-prefix) | Remove the special prefix from field names. | @@ -207,7 +206,6 @@ All options sorted alphabetically: - [`--field-extra-keys`](field-customization.md#field-extra-keys) - Include specific extra keys in Field() definitions. - [`--field-extra-keys-without-x-prefix`](field-customization.md#field-extra-keys-without-x-prefix) - Include specified schema extension keys in Field() without r... - [`--field-include-all-keys`](field-customization.md#field-include-all-keys) - Include all schema keys in Field() json_schema_extra. -- [`--field-type-collision-strategy`](field-customization.md#field-type-collision-strategy) - Configure how to resolve naming conflicts between field name... - [`--force-optional`](model-customization.md#force-optional) - Force all fields to be Optional regardless of required statu... - [`--formatters`](template-customization.md#formatters) - Specify code formatters to apply to generated output. - [`--frozen-dataclasses`](model-customization.md#frozen-dataclasses) - Generate frozen dataclasses with optional keyword-only field... diff --git a/src/datamodel_code_generator/__init__.py b/src/datamodel_code_generator/__init__.py index 42b4dac40..3008dfb09 100644 --- a/src/datamodel_code_generator/__init__.py +++ b/src/datamodel_code_generator/__init__.py @@ -309,17 +309,6 @@ class ModuleSplitMode(Enum): Single = "single" -class FieldTypeCollisionStrategy(Enum): - """Strategy for handling field name and type name collisions in Pydantic v2. - - RenameField: Rename the field with a suffix (e.g., Fruit_1) and add alias (default). - RenameType: Rename the type class with a suffix (e.g., Fruit_) to preserve field name. - """ - - RenameField = "rename-field" - RenameType = "rename-type" - - class Error(Exception): """Base exception for datamodel-code-generator errors.""" @@ -496,7 +485,6 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915 all_exports_scope: AllExportsScope | None = None, all_exports_collision_strategy: AllExportsCollisionStrategy | None = None, module_split_mode: ModuleSplitMode | None = None, - field_type_collision_strategy: FieldTypeCollisionStrategy | None = None, ) -> None: """Generate Python data models from schema definitions or structured data. @@ -742,7 +730,6 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]: dataclass_arguments=dataclass_arguments, type_mappings=type_mappings, read_only_write_only_model_type=read_only_write_only_model_type, - field_type_collision_strategy=field_type_collision_strategy, **kwargs, ) @@ -889,7 +876,6 @@ def infer_input_type(text: str) -> InputFileType: "DatetimeClassType", "DefaultPutDict", "Error", - "FieldTypeCollisionStrategy", "InputFileType", "InvalidClassNameError", "LiteralType", diff --git a/src/datamodel_code_generator/__main__.py b/src/datamodel_code_generator/__main__.py index c31783e2d..fb2791d2e 100644 --- a/src/datamodel_code_generator/__main__.py +++ b/src/datamodel_code_generator/__main__.py @@ -28,7 +28,6 @@ DataclassArguments, DataModelType, Error, - FieldTypeCollisionStrategy, InputFileType, InvalidClassNameError, ModuleSplitMode, @@ -472,7 +471,6 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict 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 - field_type_collision_strategy: Optional[FieldTypeCollisionStrategy] = None # noqa: UP045 watch: bool = False watch_delay: float = 0.5 @@ -780,7 +778,6 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917 all_exports_scope=config.all_exports_scope, all_exports_collision_strategy=config.all_exports_collision_strategy, module_split_mode=config.module_split_mode, - field_type_collision_strategy=config.field_type_collision_strategy, ) diff --git a/src/datamodel_code_generator/arguments.py b/src/datamodel_code_generator/arguments.py index bf4396553..9cedb2ef6 100644 --- a/src/datamodel_code_generator/arguments.py +++ b/src/datamodel_code_generator/arguments.py @@ -21,7 +21,6 @@ AllOfMergeMode, DataclassArguments, DataModelType, - FieldTypeCollisionStrategy, InputFileType, ModuleSplitMode, OpenAPIScope, @@ -602,14 +601,6 @@ def start_section(self, heading: str | None) -> None: choices=[u.value for u in UnionMode], default=None, ) -field_options.add_argument( - "--field-type-collision-strategy", - help="Strategy for handling field name and type name collisions (Pydantic v2 only). " - "'rename-field': rename field with suffix and add alias (default). " - "'rename-type': rename type class with suffix to preserve field name.", - choices=[s.value for s in FieldTypeCollisionStrategy], - default=None, -) field_options.add_argument( "--no-alias", help="""Do not add a field alias. E.g., if --snake-case-field is used along with a base class, which has an diff --git a/src/datamodel_code_generator/cli_options.py b/src/datamodel_code_generator/cli_options.py index 7586635c0..fc86244e8 100644 --- a/src/datamodel_code_generator/cli_options.py +++ b/src/datamodel_code_generator/cli_options.py @@ -135,9 +135,6 @@ class CLIOptionMeta: "--use-enum-values-in-discriminator": CLIOptionMeta( name="--use-enum-values-in-discriminator", category=OptionCategory.FIELD ), - "--field-type-collision-strategy": CLIOptionMeta( - name="--field-type-collision-strategy", category=OptionCategory.FIELD - ), # ========================================================================== # Typing Customization # ========================================================================== diff --git a/src/datamodel_code_generator/parser/base.py b/src/datamodel_code_generator/parser/base.py index 52dc0d22c..253a2ea51 100644 --- a/src/datamodel_code_generator/parser/base.py +++ b/src/datamodel_code_generator/parser/base.py @@ -28,7 +28,6 @@ AllExportsScope, AllOfMergeMode, Error, - FieldTypeCollisionStrategy, ModuleSplitMode, ReadOnlyWriteOnlyModelType, ReuseScope, @@ -771,7 +770,6 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915 dataclass_arguments: DataclassArguments | None = None, type_mappings: list[str] | None = None, read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None, - field_type_collision_strategy: FieldTypeCollisionStrategy | None = None, ) -> None: """Initialize the Parser with configuration options.""" self.keyword_only = keyword_only @@ -928,7 +926,6 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915 self.read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = read_only_write_only_model_type self.use_frozen_field: bool = use_frozen_field self.use_default_factory_for_optional_nested_models: bool = use_default_factory_for_optional_nested_models - self.field_type_collision_strategy: FieldTypeCollisionStrategy | None = field_type_collision_strategy @property def field_name_model_type(self) -> ModelType: @@ -1847,34 +1844,22 @@ def __change_field_name( ) -> None: if not self.data_model_type.SUPPORTS_FIELD_RENAMING: return - - rename_type = self.field_type_collision_strategy == FieldTypeCollisionStrategy.RenameType - for model in models: - if "Enum" in model.base_class or not model.BASE_CLASS: + if "Enum" in model.base_class: + continue + if not model.BASE_CLASS: continue for field in model.fields: filed_name = field.name - resolver = ModelResolver(snake_case_field=self.snake_case_field, remove_suffix_number=True) - colliding_reference: Reference | None = None + filed_name_resolver = ModelResolver(snake_case_field=self.snake_case_field, remove_suffix_number=True) for data_type in field.data_type.all_data_types: - if not data_type.reference: - continue - resolver.exclude_names.add(data_type.reference.short_name) - if rename_type and colliding_reference is None and data_type.reference.short_name == filed_name: - colliding_reference = data_type.reference - - if colliding_reference is not None: - source = cast("DataModel", colliding_reference.source) - resolver.exclude_names.add(cast("str", filed_name)) - new_class_name = resolver.add(["type"], cast("str", source.class_name)).name - source.class_name = new_class_name - else: - new_filed_name = resolver.add(["field"], cast("str", filed_name)).name - if filed_name != new_filed_name: - field.alias = filed_name - field.name = new_filed_name + if data_type.reference: + filed_name_resolver.exclude_names.add(data_type.reference.short_name) + new_filed_name = filed_name_resolver.add(["field"], cast("str", filed_name)).name + if filed_name != new_filed_name: + field.alias = filed_name + field.name = new_filed_name def __set_one_literal_on_default(self, models: list[DataModel]) -> None: if not self.use_one_literal_as_default: diff --git a/src/datamodel_code_generator/parser/graphql.py b/src/datamodel_code_generator/parser/graphql.py index 1ae0200ec..3ee1974d7 100644 --- a/src/datamodel_code_generator/parser/graphql.py +++ b/src/datamodel_code_generator/parser/graphql.py @@ -18,7 +18,6 @@ AllOfMergeMode, DataclassArguments, DefaultPutDict, - FieldTypeCollisionStrategy, LiteralType, PythonVersion, PythonVersionMin, @@ -194,7 +193,6 @@ def __init__( # noqa: PLR0913 use_serialize_as_any: bool = False, use_frozen_field: bool = False, use_default_factory_for_optional_nested_models: bool = False, - field_type_collision_strategy: FieldTypeCollisionStrategy | None = None, ) -> None: """Initialize the GraphQL parser with configuration options.""" super().__init__( @@ -292,7 +290,6 @@ def __init__( # noqa: PLR0913 use_serialize_as_any=use_serialize_as_any, use_frozen_field=use_frozen_field, use_default_factory_for_optional_nested_models=use_default_factory_for_optional_nested_models, - field_type_collision_strategy=field_type_collision_strategy, ) self.data_model_scalar_type = data_model_scalar_type diff --git a/src/datamodel_code_generator/parser/jsonschema.py b/src/datamodel_code_generator/parser/jsonschema.py index 8290e7c42..8e69d3a50 100644 --- a/src/datamodel_code_generator/parser/jsonschema.py +++ b/src/datamodel_code_generator/parser/jsonschema.py @@ -25,7 +25,6 @@ DEFAULT_SHARED_MODULE_NAME, AllOfMergeMode, DataclassArguments, - FieldTypeCollisionStrategy, InvalidClassNameError, ReadOnlyWriteOnlyModelType, ReuseScope, @@ -606,7 +605,6 @@ def __init__( # noqa: PLR0913 dataclass_arguments: DataclassArguments | None = None, type_mappings: list[str] | None = None, read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None, - field_type_collision_strategy: FieldTypeCollisionStrategy | None = None, ) -> None: """Initialize the JSON Schema parser with configuration options.""" target_datetime_class = target_datetime_class or DatetimeClassType.Awaredatetime @@ -705,7 +703,6 @@ def __init__( # noqa: PLR0913 dataclass_arguments=dataclass_arguments, type_mappings=type_mappings, read_only_write_only_model_type=read_only_write_only_model_type, - field_type_collision_strategy=field_type_collision_strategy, ) self.remote_object_cache: DefaultPutDict[str, dict[str, YamlValue]] = DefaultPutDict() diff --git a/src/datamodel_code_generator/parser/openapi.py b/src/datamodel_code_generator/parser/openapi.py index a36b6ad4c..9d9511421 100644 --- a/src/datamodel_code_generator/parser/openapi.py +++ b/src/datamodel_code_generator/parser/openapi.py @@ -22,7 +22,6 @@ AllOfMergeMode, DataclassArguments, Error, - FieldTypeCollisionStrategy, LiteralType, OpenAPIScope, PythonVersion, @@ -278,7 +277,6 @@ def __init__( # noqa: PLR0913 use_frozen_field: bool = False, use_default_factory_for_optional_nested_models: bool = False, use_status_code_in_response_name: bool = False, - field_type_collision_strategy: FieldTypeCollisionStrategy | None = None, ) -> None: """Initialize the OpenAPI parser with extensive configuration options.""" target_datetime_class = target_datetime_class or DatetimeClassType.Awaredatetime @@ -377,7 +375,6 @@ def __init__( # noqa: PLR0913 read_only_write_only_model_type=read_only_write_only_model_type, use_frozen_field=use_frozen_field, use_default_factory_for_optional_nested_models=use_default_factory_for_optional_nested_models, - field_type_collision_strategy=field_type_collision_strategy, ) self.open_api_scopes: list[OpenAPIScope] = openapi_scopes or [OpenAPIScope.Schemas] self.include_path_parameters: bool = include_path_parameters diff --git a/tests/data/expected/main/jsonschema/field_type_collision_rename_type.py b/tests/data/expected/main/jsonschema/field_type_collision_rename_type.py deleted file mode 100644 index ebc256bbf..000000000 --- a/tests/data/expected/main/jsonschema/field_type_collision_rename_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: field_type_collision_rename_type.json -# timestamp: 2019-07-26T00:00:00+00:00 - -from __future__ import annotations - -from enum import Enum - -from pydantic import BaseModel - - -class Fruit_1(Enum): - apple = 'apple' - banana = 'banana' - - -class Test(BaseModel): - Fruit: Fruit_1 | None = None diff --git a/tests/data/expected/main/jsonschema/field_type_collision_rename_type_multi_model.py b/tests/data/expected/main/jsonschema/field_type_collision_rename_type_multi_model.py deleted file mode 100644 index 56c7f83e3..000000000 --- a/tests/data/expected/main/jsonschema/field_type_collision_rename_type_multi_model.py +++ /dev/null @@ -1,23 +0,0 @@ -# generated by datamodel-codegen: -# filename: field_type_collision_rename_type_multi_model.json -# timestamp: 2019-07-26T00:00:00+00:00 - -from __future__ import annotations - -from enum import Enum - -from pydantic import BaseModel - - -class Fruit_1(Enum): - apple = 'apple' - banana = 'banana' - - -class Child(BaseModel): - Fruit: Fruit_1 | None = None - - -class Parent(BaseModel): - Fruit: Fruit_1 | None = None - child: Child | None = None diff --git a/tests/data/jsonschema/field_type_collision_rename_type.json b/tests/data/jsonschema/field_type_collision_rename_type.json deleted file mode 100644 index 547d2166e..000000000 --- a/tests/data/jsonschema/field_type_collision_rename_type.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Test", - "type": "object", - "properties": { - "Fruit": { - "enum": [ - "apple", - "banana" - ] - } - } -} diff --git a/tests/data/jsonschema/field_type_collision_rename_type_multi_model.json b/tests/data/jsonschema/field_type_collision_rename_type_multi_model.json deleted file mode 100644 index 8fdfdc83c..000000000 --- a/tests/data/jsonschema/field_type_collision_rename_type_multi_model.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "title": "Parent", - "type": "object", - "properties": { - "Fruit": { - "$ref": "#/definitions/Fruit" - }, - "child": { - "$ref": "#/definitions/Child" - } - }, - "definitions": { - "Fruit": { - "title": "Fruit", - "enum": ["apple", "banana"] - }, - "Child": { - "title": "Child", - "type": "object", - "properties": { - "Fruit": { - "$ref": "#/definitions/Fruit" - } - } - } - } -} diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 4a60a1477..1475e83a3 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -3572,56 +3572,6 @@ def test_main_jsonschema_field_has_same_name(output_model: str, expected_output: ) -@pytest.mark.cli_doc( - options=["--field-type-collision-strategy"], - input_schema="jsonschema/field_type_collision_rename_type.json", - cli_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-type-collision-strategy", "rename-type"], - golden_output="main/jsonschema/field_type_collision_rename_type.py", -) -def test_main_jsonschema_field_type_collision_rename_type(output_file: Path) -> None: - """Configure how to resolve naming conflicts between field names and generated type names. - - When a schema property name matches a generated type name (e.g., a property "Fruit" with - an enum type that would also be named "Fruit"), a collision occurs. This option controls - the resolution strategy: - - - `rename-field` (default): Rename the field with a suffix (e.g., `Fruit_1`) and preserve - the original name via `Field(alias='Fruit')` - - `rename-type`: Rename the type class with a suffix (e.g., `Fruit_`) and keep the original - field name - """ - run_main_and_assert( - input_path=JSON_SCHEMA_DATA_PATH / "field_type_collision_rename_type.json", - output_path=output_file, - input_file_type="jsonschema", - assert_func=assert_file_content, - expected_file="field_type_collision_rename_type.py", - extra_args=[ - "--output-model-type", - "pydantic_v2.BaseModel", - "--field-type-collision-strategy", - "rename-type", - ], - ) - - -def test_main_jsonschema_field_type_collision_rename_type_multi_model(output_file: Path) -> None: - """Test field-type collision with rename-type strategy across multiple models.""" - run_main_and_assert( - input_path=JSON_SCHEMA_DATA_PATH / "field_type_collision_rename_type_multi_model.json", - output_path=output_file, - input_file_type="jsonschema", - assert_func=assert_file_content, - expected_file="field_type_collision_rename_type_multi_model.py", - extra_args=[ - "--output-model-type", - "pydantic_v2.BaseModel", - "--field-type-collision-strategy", - "rename-type", - ], - ) - - @pytest.mark.benchmark def test_main_jsonschema_required_and_any_of_required(output_file: Path) -> None: """Test required field with anyOf required."""