Skip to content

Unexpected default_factory for list of object field with optional [] default results in validation failures during instantiation #2947

@rpmcginty

Description

@rpmcginty

Describe the bug
A clear and concise description of what the bug is.

I upgraded datamodel-codegen recently from 0.35 to 0.52.2 and now there seems to be some weird behavior for how the default is specified for a field of type list[NestedClass]. If you specify the default as [] in graphql definition, the default is set as default_factory=lambda:NestedClass.model_validate([]).

I was able to pinpoint the version that introduced this change to be 0.44

To Reproduce

Example schema: (tmp.graphql)

input TagInput {
  name: String!
  value: String!
}

input ModelInput {
  tags: [TagInput!] = []
}

Used commandline:

$ datamodel-codegen --input tmp.graphql --input-file-type graphql --output tmp.py --output-model-type pydantic_v2.BaseModel

The resulting output:

# generated by datamodel-codegen:
#   filename:  tmp.graphql
#   timestamp: 2026-01-06T20:05:20+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=lambda: TagInput.model_validate([])
    )
    typename__: Literal['ModelInput'] | None = Field('ModelInput', alias='__typename')

Expected behavior

I would expect the field default factory to be something like this:

class ModelInput(GraphQLModel):
    tags: Annotated[
        list[TagInput] | None,
        Field(default_factory=list),
    ]

However, the result is instead:

class ModelInput(BaseModel):
    tags: list[TagInput] | None = Field(
        default_factory=lambda: TagInput.model_validate([])
    )

This results in a failure if we try to instantiate this class without specifying this field (which should be allowed).

In [1]: from scripts.tmp import ModelInput

In [2]: ModelInput()
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[2], line 1
----> 1 ModelInput()

    [... skipping hidden 1 frame]

File tmp.py:32, in ModelInput.<lambda>()
     30 class ModelInput(BaseModel):
     31     tags: list[TagInput] | None = Field(
---> 32         default_factory=lambda: TagInput.model_validate([])
     33     )
     34     typename__: Literal['ModelInput'] | None = Field('ModelInput', alias='__typename')

File .venv/lib/python3.13/site-packages/pydantic/main.py:716, in BaseModel.model_validate(cls, obj, strict, extra, from_attributes, context, by_alias, by_name)
    710 if by_alias is False and by_name is not True:
    711     raise PydanticUserError(
    712         'At least one of `by_alias` or `by_name` must be set to True.',
    713         code='validate-by-alias-and-name-false',
    714     )
--> 716 return cls.__pydantic_validator__.validate_python(
    717     obj,
    718     strict=strict,
    719     extra=extra,
    720     from_attributes=from_attributes,
    721     context=context,
    722     by_alias=by_alias,
    723     by_name=by_name,
    724 )

ValidationError: 1 validation error for TagInput
  Input should be a valid dictionary or instance of TagInput [type=model_type, input_value=[], input_type=list]
    For further information visit https://errors.pydantic.dev/2.12/v/model_type

Version:

  • OS: MacOS 15.6
  • Python version: 3.11
  • datamodel-code-generator version: introduced in 0.44 (but still in as of 0.52.2)

Additional context

For context, the reproducible snippet that I provided is part of a larger definition from a sister team I work with which is generated from a c# implementation + hot chocolate library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions