From e863084f615fe00faf9782b7e849b5dbe91310dd Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 14 Feb 2026 04:00:54 +0000 Subject: [PATCH] Fix missing Field import with multiple aliases on required fields --- .../model/pydantic/base_model.py | 4 --- .../aliases/multiple_aliases_required.json | 4 +++ .../multiple_aliases_required.json | 5 +++ .../multiple_aliases_required_code.py | 13 ++++++++ ...a_multiple_aliases_required_pydantic_v2.py | 16 ++++++++++ .../jsonschema/multiple_aliases_required.json | 10 ++++++ tests/main/jsonschema/test_main_jsonschema.py | 16 ++++++++++ tests/main/test_dynamic_models.py | 31 +++++++++++++++++++ 8 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 tests/data/aliases/multiple_aliases_required.json create mode 100644 tests/data/expected/dynamic_models/multiple_aliases_required.json create mode 100644 tests/data/expected/dynamic_models/multiple_aliases_required_code.py create mode 100644 tests/data/expected/main/jsonschema/jsonschema_multiple_aliases_required_pydantic_v2.py create mode 100644 tests/data/jsonschema/multiple_aliases_required.json diff --git a/src/datamodel_code_generator/model/pydantic/base_model.py b/src/datamodel_code_generator/model/pydantic/base_model.py index ce83fad6d..edf55f1aa 100644 --- a/src/datamodel_code_generator/model/pydantic/base_model.py +++ b/src/datamodel_code_generator/model/pydantic/base_model.py @@ -297,10 +297,6 @@ def annotated(self) -> str | None: @property def imports(self) -> tuple[Import, ...]: """Get all required imports including Field if needed.""" - # Fast path: skip expensive self.field check for simple required fields - if self.required and not self.nullable and not self.alias and self.constraints is None and not self.extras: - return super().imports - if self.field: return chain_as_tuple(super().imports, (IMPORT_FIELD,)) return super().imports diff --git a/tests/data/aliases/multiple_aliases_required.json b/tests/data/aliases/multiple_aliases_required.json new file mode 100644 index 000000000..af5222318 --- /dev/null +++ b/tests/data/aliases/multiple_aliases_required.json @@ -0,0 +1,4 @@ +{ + "name": ["full_name", "customer_name"], + "email": ["email_address", "mail"] +} diff --git a/tests/data/expected/dynamic_models/multiple_aliases_required.json b/tests/data/expected/dynamic_models/multiple_aliases_required.json new file mode 100644 index 000000000..45995c225 --- /dev/null +++ b/tests/data/expected/dynamic_models/multiple_aliases_required.json @@ -0,0 +1,5 @@ +{ + "Customer": { + "full_name": "John" + } +} \ No newline at end of file diff --git a/tests/data/expected/dynamic_models/multiple_aliases_required_code.py b/tests/data/expected/dynamic_models/multiple_aliases_required_code.py new file mode 100644 index 000000000..f7f526faf --- /dev/null +++ b/tests/data/expected/dynamic_models/multiple_aliases_required_code.py @@ -0,0 +1,13 @@ +# generated by datamodel-codegen: +# filename: +# timestamp: 2019-07-26T00:00:00+00:00 + +from __future__ import annotations + +from pydantic import AliasChoices, BaseModel, Field + + +class Customer(BaseModel): + full_name: str = Field( + ..., validation_alias=AliasChoices('name', 'full_name', 'customer_name') + ) \ No newline at end of file diff --git a/tests/data/expected/main/jsonschema/jsonschema_multiple_aliases_required_pydantic_v2.py b/tests/data/expected/main/jsonschema/jsonschema_multiple_aliases_required_pydantic_v2.py new file mode 100644 index 000000000..e1926671e --- /dev/null +++ b/tests/data/expected/main/jsonschema/jsonschema_multiple_aliases_required_pydantic_v2.py @@ -0,0 +1,16 @@ +# generated by datamodel-codegen: +# filename: multiple_aliases_required.json +# timestamp: 2019-07-26T00:00:00+00:00 + +from __future__ import annotations + +from pydantic import AliasChoices, BaseModel, Field + + +class Root(BaseModel): + full_name: str = Field( + ..., validation_alias=AliasChoices('name', 'full_name', 'customer_name') + ) + email_address: str = Field( + ..., validation_alias=AliasChoices('email', 'email_address', 'mail') + ) diff --git a/tests/data/jsonschema/multiple_aliases_required.json b/tests/data/jsonschema/multiple_aliases_required.json new file mode 100644 index 000000000..a21bc9442 --- /dev/null +++ b/tests/data/jsonschema/multiple_aliases_required.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Root", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string" } + }, + "required": ["name", "email"] +} diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 78ccdaf29..9c73b525e 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -8495,3 +8495,19 @@ def test_main_jsonschema_dynamic_ref_in_defs_pydantic_v2(output_file: Path) -> N expected_file="dynamic_ref_in_defs_pydantic_v2.py", extra_args=["--output-model-type", "pydantic_v2.BaseModel"], ) + + +def test_main_jsonschema_multiple_aliases_required_pydantic_v2(output_file: Path) -> None: + """Test multiple aliases with AliasChoices on required fields for Pydantic v2. (#2989).""" + run_main_and_assert( + input_path=JSON_SCHEMA_DATA_PATH / "multiple_aliases_required.json", + output_path=output_file, + input_file_type="jsonschema", + assert_func=assert_file_content, + extra_args=[ + "--aliases", + str(ALIASES_DATA_PATH / "multiple_aliases_required.json"), + "--output-model-type", + "pydantic_v2.BaseModel", + ], + ) diff --git a/tests/main/test_dynamic_models.py b/tests/main/test_dynamic_models.py index 3cd2ff6ef..7b475da26 100644 --- a/tests/main/test_dynamic_models.py +++ b/tests/main/test_dynamic_models.py @@ -459,6 +459,37 @@ def test_execute_multi_module_no_models() -> None: assert models == {} +def test_multiple_aliases_required_field() -> None: + """Test dynamic models with multiple aliases on required fields. (#2989).""" + schema = make_object_schema({"name": {"type": "string"}}, required=["name"]) + config = GenerateConfig( + input_file_type=InputFileType.JsonSchema, + output_model_type=DataModelType.PydanticV2BaseModel, + aliases={"name": ["full_name", "customer_name"]}, + class_name="Customer", + ) + assert_dynamic_models( + schema, + {"Customer": {"name": "John"}}, + EXPECTED_PATH / "multiple_aliases_required.json", + config=config, + ) + + +def test_multiple_aliases_required_field_code_output() -> None: + """Test generated code includes Field import with multiple aliases on required fields. (#2989).""" + schema = make_object_schema({"name": {"type": "string"}}, required=["name"]) + config = GenerateConfig( + input_file_type=InputFileType.JsonSchema, + output_model_type=DataModelType.PydanticV2BaseModel, + aliases={"name": ["full_name", "customer_name"]}, + class_name="Customer", + ) + result = generate(input_=schema, config=config) + assert isinstance(result, str) + assert_output(result, EXPECTED_PATH / "multiple_aliases_required_code.py") + + def test_execute_multi_module_enum_only() -> None: """Test _execute_multi_module with enum only to cover non-BaseModel branch.""" from datamodel_code_generator.dynamic import _execute_multi_module