Skip to content

Commit ea69e69

Browse files
Viicoskc0506
andauthored
Make sure the type reference is removed from the seen references (#11145)
When parametrizing generic models, the `generic_recursion_self_type` context manager is entered, and is used to avoid infinite recursions if the same generic model happens to be parametrized again with the same args during the first parametrization. However, upon exiting the context manager (and thus when the parametrized model is fully created), we forgot to remove the type ref from the set. This happened only if we were already in the process of parametrizing another model, as otherwise the `_generic_recursion_cache` would be reset (see the `if token` condition). In theory, this couldn't cause issues because parametrized models are cached, and the cache is checked *before* entering the context manager. However, because we have custom `mro()` implementation on the `BaseModel` metaclass, this ends up causing issues is some really specific scenarios. Co-authored-by: kc0506 <[email protected]>
1 parent a07c31e commit ea69e69

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

pydantic/_internal/_generics.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ def generic_recursion_self_type(
426426
yield self_type
427427
else:
428428
previously_seen_type_refs.add(type_ref)
429-
yield None
429+
yield
430+
previously_seen_type_refs.remove(type_ref)
430431
finally:
431432
if token:
432433
_generic_recursion_cache.reset(token)

tests/test_generics.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3099,3 +3099,41 @@ class Holder(BaseModel, Generic[T]):
30993099
holder2 = Holder(inner=Inner(inner=1))
31003100
# implies that validation succeeds for both
31013101
assert holder1 == holder2
3102+
3103+
3104+
def test_generic_mro_multi_level():
3105+
"""Pass another generic model as an arg.
3106+
3107+
See https://github.com/pydantic/pydantic/issues/11024.
3108+
"""
3109+
3110+
T = TypeVar('T')
3111+
3112+
class GenericBaseModel(BaseModel, Generic[T]): ...
3113+
3114+
class EnumerableModel(GenericBaseModel[T]):
3115+
values: List[T]
3116+
3117+
T1 = TypeVar('T1')
3118+
T2 = TypeVar('T2')
3119+
3120+
class CombineModel(BaseModel, Generic[T1, T2]):
3121+
field_1: T1
3122+
field_2: T2
3123+
3124+
class EnumerableCombineModel(EnumerableModel[CombineModel[T1, T2]]): ...
3125+
3126+
m = EnumerableCombineModel[int, int]
3127+
3128+
mro = (
3129+
EnumerableCombineModel[int, int],
3130+
EnumerableCombineModel,
3131+
EnumerableModel[CombineModel[int, int]],
3132+
EnumerableModel,
3133+
GenericBaseModel[CombineModel[int, int]],
3134+
GenericBaseModel,
3135+
BaseModel,
3136+
Generic,
3137+
object,
3138+
)
3139+
assert m.__mro__ == tuple(HasRepr(repr(m)) for m in mro)

0 commit comments

Comments
 (0)