Skip to content

RootModel with Generics with self-referencing fails to compile #11748

@leonardzou

Description

@leonardzou

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

Hi Pydantic team, absolutely love your work!

Hopefully this isn't too gnarly of an issue to debug 😅

When I run the code below it works for pydantic v2.10.6 but with v2.11.3 it results in this error

AttributeError: __pydantic_fields__. Did you mean: '__pydantic_fields_set__'?

Full stack trace:

Traceback (most recent call last):
  File "/Users/first.last/Downloads/tmp/./tmp.py", line 16, in <module>
    class Branch(BaseModel):
    ...<2 lines>...
        children: "PydList[Leaf|Branch]"
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py", line 221, in __new__
    set_model_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py", line 544, in set_model_fields
    fields, class_vars = collect_model_fields(cls, config_wrapper, ns_resolver, typevars_map=typevars_map)
                         ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_fields.py", line 118, in collect_model_fields
    type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_typing_extra.py", line 329, in get_model_type_hints
    hints[name] = try_eval_type(value, globalns, localns)
                  ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_typing_extra.py", line 378, in try_eval_type
    return eval_type_backport(value, globalns, localns), True
           ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_typing_extra.py", line 429, in eval_type_backport
    return _eval_type_backport(value, globalns, localns, type_params)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_typing_extra.py", line 466, in _eval_type_backport
    return _eval_type(value, globalns, localns, type_params)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_typing_extra.py", line 496, in _eval_type
    return typing._eval_type(  # type: ignore
           ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
        value, globalns, localns, type_params=type_params
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/first.last/.pyenv/versions/3.13.1/lib/python3.13/typing.py", line 474, in _eval_type
    return t._evaluate(globalns, localns, type_params, recursive_guard=recursive_guard)
           ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/.pyenv/versions/3.13.1/lib/python3.13/typing.py", line 1081, in _evaluate
    eval(self.__forward_code__, globalns, localns),
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 1, in <module>
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/main.py", line 909, in __class_getitem__
    submodel = _generics.create_generic_submodel(model_name, origin, args, params)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generics.py", line 128, in create_generic_submodel
    created_model = meta(
        model_name,
    ...<8 lines>...
        **kwds,
    )
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py", line 237, in __new__
    complete_model_class(
    ~~~~~~~~~~~~~~~~~~~~^
        cls,
        ^^^^
    ...<3 lines>...
        create_model_module=_create_model_module,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py", line 597, in complete_model_class
    schema = gen_schema.generate_schema(cls)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 706, in generate_schema
    schema = self._generate_schema_inner(obj)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 984, in _generate_schema_inner
    return self._model_schema(obj)
           ~~~~~~~~~~~~~~~~~~^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 802, in _model_schema
    root_field = self._common_field_schema('root', fields['root'], decorators)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 1352, in _common_field_schema
    schema = self._apply_annotations(
        source_type,
        annotations + validators_from_decorators,
    )
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 2264, in _apply_annotations
    schema = get_inner_schema(source_type)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_schema_generation_shared.py", line 83, in __call__
    schema = self._handler(source_type)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 2246, in inner_handler
    schema = self._generate_schema_inner(obj)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 989, in _generate_schema_inner
    return self.match_type(obj)
           ~~~~~~~~~~~~~~~^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 1103, in match_type
    return self._match_generic_type(obj, origin)
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 1130, in _match_generic_type
    return self._list_schema(self._get_first_arg_or_any(obj))
           ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 367, in _list_schema
    return core_schema.list_schema(self.generate_schema(items_type))
                                   ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 706, in generate_schema
    schema = self._generate_schema_inner(obj)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 989, in _generate_schema_inner
    return self.match_type(obj)
           ~~~~~~~~~~~~~~~^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 1103, in match_type
    return self._match_generic_type(obj, origin)
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 1126, in _match_generic_type
    return self._union_schema(obj)
           ~~~~~~~~~~~~~~~~~~^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 1414, in _union_schema
    choices.append(self.generate_schema(arg))
                   ~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 706, in generate_schema
    schema = self._generate_schema_inner(obj)
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 984, in _generate_schema_inner
    return self._model_schema(obj)
           ~~~~~~~~~~~~~~~~~~^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py", line 745, in _model_schema
    fields = rebuild_model_fields(
        cls,
        ns_resolver=self._ns_resolver,
        typevars_map=self._typevars_map or {},
    )
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_fields.py", line 318, in rebuild_model_fields
    for f_name, field_info in cls.__pydantic_fields__.items():
                              ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py", line 271, in __getattr__
    raise AttributeError(item)
AttributeError: __pydantic_fields__. Did you mean: '__pydantic_fields_set__'?

Example Code

"""Works for pydantic 2.10.6 but not for 2.11.3"""

from typing import TypeVar
from pydantic import BaseModel, RootModel


T = TypeVar('T')

class PydList(RootModel[list[T]]):
    """Pydantic List Model that supports .model_dump()"""


class Leaf(BaseModel):
    """Leaf Model"""
    text: str


class Branch(BaseModel):
    """Branch Model"""
    val: str # using `val` instead of `text` like `Leaf` model to hopefully help debug / avoid red herring
    children: "PydList[Leaf|Branch]" # <- self reference


a = {
    "val": "asdf",
    "children": [
        {
            "text": "asdf"
        },
        {
            "val": "asdf",
            "children": [
                {
                    "text": "asdf"
                }
            ]
        }
    ]
}

b = Branch.model_validate(a)

Python, Pydantic & OS Version

pydantic version: 2.11.3
        pydantic-core version: 2.33.1
          pydantic-core build: profile=release pgo=false
                 install path: /Users/first.last/Downloads/tmp/env/lib/python3.13/site-packages/pydantic
               python version: 3.13.1 (main, Jan 28 2025, 14:38:43) [Clang 16.0.0 (clang-1600.0.26.6)]
                     platform: macOS-15.4-arm64-arm-64bit-Mach-O
             related packages: typing_extensions-4.13.2
                       commit: unknown

Metadata

Metadata

Assignees

Labels

bug V2Bug related to Pydantic V2topic-genericsRelated to generics

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions