Consider:
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [ "pydantic>=2.10.5,<3" ]
# requires-python = ">=3.12,<3.13"
# ///
import pydantic
from typing import Annotated
from collections import defaultdict
from uuid import UUID
class Repro(pydantic.BaseModel):
value: Annotated[
defaultdict[UUID, defaultdict[UUID, dict]],
pydantic.Field(default_factory=lambda: defaultdict[UUID, defaultdict[UUID, dict]](lambda: defaultdict(dict))),
]
# to validate that the typechecker thinks the above are valid:
instance = Repro(value=defaultdict[UUID, defaultdict[UUID, dict]](lambda: defaultdict(dict)))
The above passes type checking with pyright, but at runtime, the class definition fails while pydantic is trying to introspect the object's schema:
pydantic.errors.PydanticSchemaGenerationError: Unable to infer a default factory for keys of type <class 'dict'>. Only str, int, bool, list, dict, frozenset, tuple, float, set are supported, other types require an explicit default factory set using `DefaultDict[..., Annotated[..., Field(default_factory=...)]]
Thinking it might be introspection of the inner defaultdict
triggering the error message, I tried adding a pydantic.Field
to its type via an inner annotation:
class Repro(pydantic.BaseModel):
value: Annotated[
defaultdict[
UUID, Annotated[defaultdict[UUID, dict], pydantic.Field(default_factory=dict)]
],
pydantic.Field(default_factory=lambda: defaultdict[UUID, defaultdict[UUID, dict]](lambda: defaultdict(dict))),
]
...but with identical effect.
The above is observed with Python 3.12 and Pydantic 2.11.0, type-checked with pyright 1.1.382.
The full stack trace follows:
Traceback (most recent call last):
File "/Users/chaduffy/repro.py", line 13, in <module>
class Repro(pydantic.BaseModel):
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py", line 237, in __new__
complete_model_class(
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py", line 597, in complete_model_class
schema = gen_schema.generate_schema(cls)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 706, in generate_schema
schema = self._generate_schema_inner(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 999, in _generate_schema_inner
return self._model_schema(obj)
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 832, in _model_schema
{k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1201, in _generate_md_field_schema
common_field = self._common_field_schema(name, field_info, decorators)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1367, in _common_field_schema
schema = self._apply_annotations(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 2279, in _apply_annotations
schema = get_inner_schema(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py", line 83, in __call__
schema = self._handler(source_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 2261, in inner_handler
schema = self._generate_schema_inner(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1004, in _generate_schema_inner
return self.match_type(obj)
^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1118, in match_type
return self._match_generic_type(obj, origin)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1157, in _match_generic_type
return self._mapping_schema(origin, *self._get_first_two_args_or_any(obj))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 571, in _mapping_schema
values_schema = self.generate_schema(values_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 706, in generate_schema
schema = self._generate_schema_inner(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1004, in _generate_schema_inner
return self.match_type(obj)
^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1118, in match_type
return self._match_generic_type(obj, origin)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 1157, in _match_generic_type
return self._mapping_schema(origin, *self._get_first_two_args_or_any(obj))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py", line 583, in _mapping_schema
default_default_factory = get_defaultdict_default_default_factory(values_type)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_validators.py", line 482, in get_defaultdict_default_default_factory
default_default_factory = infer_default()
^^^^^^^^^^^^^^^
File "/Users/chaduffy/.cache/uv/archive-v0/L2nssVviE9tesQFv5NzrQ/lib/python3.12/site-packages/pydantic/_internal/_validators.py", line 466, in infer_default
raise PydanticSchemaGenerationError(
pydantic.errors.PydanticSchemaGenerationError: Unable to infer a default factory for keys of type <class 'dict'>. Only str, int, bool, list, dict, frozenset, tuple, float, set are supported, other types require an explicit default factory set using `DefaultDict[..., Annotated[..., Field(default_factory=...)]]`
For further information visit https://errors.pydantic.dev/2.11/u/schema-for-unknown-type
Use Dict
from typing
in the annotation. By using Dict
in the type annotation but defaultdict
in the default factory, we're separating the static type checking from the runtime behavior. Pydantic struggles with generating schemas for nested defaultdict
, but it handles regular Dict
types well.
import pydantic
from typing import Annotated, Dict
from collections import defaultdict
from uuid import UUID
class Repro(pydantic.BaseModel):
value: Annotated[
Dict[UUID, Dict[UUID, dict]],
pydantic.Field(default_factory=lambda: defaultdict[UUID, defaultdict[UUID, dict]](lambda: defaultdict(dict))),
]
# to validate that the typechecker thinks the above are valid:
instance = Repro(value=defaultdict[UUID, defaultdict[UUID, dict]](lambda: defaultdict(dict)))