From 99574c6479c950b52ec065d5c2448465982609af Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 10 Jan 2026 06:23:02 +0000 Subject: [PATCH] Fix empty list default for GraphQL list fields --- .../model/pydantic/base_model.py | 16 +++++++++- .../main/graphql/empty_list_default.py | 32 +++++++++++++++++++ tests/data/graphql/empty_list_default.graphql | 8 +++++ tests/main/graphql/test_main_graphql.py | 19 +++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/data/expected/main/graphql/empty_list_default.py create mode 100644 tests/data/graphql/empty_list_default.graphql diff --git a/src/datamodel_code_generator/model/pydantic/base_model.py b/src/datamodel_code_generator/model/pydantic/base_model.py index 6368a9379..54ab4303e 100644 --- a/src/datamodel_code_generator/model/pydantic/base_model.py +++ b/src/datamodel_code_generator/model/pydantic/base_model.py @@ -126,9 +126,23 @@ def _get_strict_field_constraint_value(self, constraint: str, value: Any) -> Any return value return int(value) - def _get_default_as_pydantic_model(self) -> str | None: + def _get_default_as_pydantic_model(self) -> str | None: # noqa: PLR0911, PLR0912 if isinstance(self.default, WrappedDefault): return f"lambda :{self.default!r}" + # Handle the case where self.data_type.is_list is True directly (e.g., GraphQL) + if self.data_type.is_list and len(self.data_type.data_types) == 1: + data_type_child = self.data_type.data_types[0] + if ( + data_type_child.reference + and isinstance(data_type_child.reference.source, BaseModelBase) + and isinstance(self.default, list) + ): + if not self.default: + return STANDARD_LIST + return ( # pragma: no cover + f"lambda :[{data_type_child.alias or data_type_child.reference.source.class_name}." + f"{self._PARSE_METHOD}(v) for v in {self.default!r}]" + ) for data_type in self.data_type.data_types or (self.data_type,): # TODO: Check nested data_types if data_type.is_dict: diff --git a/tests/data/expected/main/graphql/empty_list_default.py b/tests/data/expected/main/graphql/empty_list_default.py new file mode 100644 index 000000000..7a27e52a2 --- /dev/null +++ b/tests/data/expected/main/graphql/empty_list_default.py @@ -0,0 +1,32 @@ +# generated by datamodel-codegen: +# filename: empty_list_default.graphql +# timestamp: 2019-07-26T00:00:00+00:00 + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, Field +from typing_extensions import TypeAliasType + +Boolean = TypeAliasType("Boolean", bool) +""" +The `Boolean` scalar type represents `true` or `false`. +""" + + +String = TypeAliasType("String", str) +""" +The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. +""" + + +class TagInput(BaseModel): + name: String + value: String + typename__: Literal['TagInput'] | None = Field('TagInput', alias='__typename') + + +class ModelInput(BaseModel): + tags: list[TagInput] | None = Field(default_factory=list) + typename__: Literal['ModelInput'] | None = Field('ModelInput', alias='__typename') diff --git a/tests/data/graphql/empty_list_default.graphql b/tests/data/graphql/empty_list_default.graphql new file mode 100644 index 000000000..ba1419a31 --- /dev/null +++ b/tests/data/graphql/empty_list_default.graphql @@ -0,0 +1,8 @@ +input TagInput { + name: String! + value: String! +} + +input ModelInput { + tags: [TagInput!] = [] +} diff --git a/tests/main/graphql/test_main_graphql.py b/tests/main/graphql/test_main_graphql.py index 14b2ae52a..79427d714 100644 --- a/tests/main/graphql/test_main_graphql.py +++ b/tests/main/graphql/test_main_graphql.py @@ -890,3 +890,22 @@ def test_main_graphql_no_typename(output_file: Path) -> None: expected_file="no_typename.py", extra_args=["--graphql-no-typename"], ) + + +def test_main_graphql_empty_list_default(output_file: Path) -> None: + """Test that empty list default generates default_factory=list, not model_validate([]). + + When a GraphQL field has a list type with an empty list default (e.g., tags: [TagInput!] = []), + the generator should produce `Field(default_factory=list)` instead of + `Field(default_factory=lambda: TagInput.model_validate([]))`. + + This test verifies fix for issue #2947. + """ + run_main_and_assert( + input_path=GRAPHQL_DATA_PATH / "empty_list_default.graphql", + output_path=output_file, + input_file_type="graphql", + assert_func=assert_file_content, + expected_file="empty_list_default.py", + extra_args=["--output-model-type", "pydantic_v2.BaseModel"], + )