Skip to content

Commit 5a22e02

Browse files
authored
Remove custom MRO implementation of Pydantic models (#11195)
1 parent 5bd3a65 commit 5a22e02

3 files changed

Lines changed: 8 additions & 235 deletions

File tree

pydantic/_internal/_model_construction.py

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from abc import ABCMeta
1212
from functools import lru_cache, partial
1313
from types import FunctionType
14-
from typing import Any, Callable, Generic, Literal, NoReturn, TypeVar, cast
14+
from typing import Any, Callable, Generic, Literal, NoReturn, cast
1515

1616
from pydantic_core import PydanticUndefined, SchemaSerializer
1717
from typing_extensions import TypeAliasType, dataclass_transform, deprecated, get_args
@@ -131,8 +131,6 @@ def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None:
131131

132132
namespace['__class_vars__'] = class_vars
133133
namespace['__private_attributes__'] = {**base_private_attributes, **private_attributes}
134-
if __pydantic_generic_metadata__:
135-
namespace['__pydantic_generic_metadata__'] = __pydantic_generic_metadata__
136134

137135
cls = cast('type[BaseModel]', super().__new__(mcs, cls_name, bases, namespace, **kwargs))
138136
BaseModel_ = import_cached_base_model()
@@ -255,60 +253,6 @@ def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None:
255253
namespace.get('__annotations__', {}).clear()
256254
return super().__new__(mcs, cls_name, bases, namespace, **kwargs)
257255

258-
def mro(cls) -> list[type[Any]]:
259-
original_mro = super().mro()
260-
261-
if cls.__bases__ == (object,):
262-
return original_mro
263-
264-
generic_metadata: PydanticGenericMetadata | None = cls.__dict__.get('__pydantic_generic_metadata__')
265-
if not generic_metadata:
266-
return original_mro
267-
268-
assert_err_msg = 'Unexpected error occurred when generating MRO of generic subclass. Please report this issue on GitHub: https://github.com/pydantic/pydantic/issues.'
269-
270-
origin: type[BaseModel] | None
271-
origin, args = (
272-
generic_metadata['origin'],
273-
generic_metadata['args'],
274-
)
275-
if not origin:
276-
return original_mro
277-
278-
target_params = origin.__pydantic_generic_metadata__['parameters']
279-
param_dict = dict(zip(target_params, args))
280-
281-
indexed_origins = {origin}
282-
283-
new_mro: list[type[Any]] = [cls]
284-
for base in original_mro[1:]:
285-
base_origin: type[BaseModel] | None = getattr(base, '__pydantic_generic_metadata__', {}).get('origin')
286-
base_params: tuple[TypeVar, ...] = getattr(base, '__pydantic_generic_metadata__', {}).get('parameters', ())
287-
288-
if base_origin in indexed_origins:
289-
continue
290-
elif base not in indexed_origins and base_params:
291-
assert set(base_params) <= param_dict.keys(), assert_err_msg
292-
new_base_args = tuple(param_dict[param] for param in base_params)
293-
new_base = base[new_base_args] # type: ignore
294-
new_mro.append(new_base)
295-
296-
indexed_origins.add(base_origin or base)
297-
298-
if base_origin is not None:
299-
# dropped previous indexed origins
300-
continue
301-
else:
302-
indexed_origins.add(base_origin or base)
303-
304-
# Avoid redundunt case such as
305-
# class A(BaseModel, Generic[T]): ...
306-
# A[T] is A # True
307-
if base is not new_mro[-1]:
308-
new_mro.append(base)
309-
310-
return new_mro
311-
312256
if not typing.TYPE_CHECKING: # pragma: no branch
313257
# We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access
314258

pydantic/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,6 @@ def __class_getitem__(
786786
) # use dict as ordered set
787787

788788
with _generics.generic_recursion_self_type(origin, args) as maybe_self_type:
789-
# Check cached first otherwise `mro` may return `PydanticRecursiveRef`
790789
cached = _generics.get_cached_generic_type_late(cls, typevar_values, origin, args)
791790
if cached is not None:
792791
return cached

tests/test_generics.py

Lines changed: 7 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -476,10 +476,7 @@ class M(BaseModel):
476476
Working = B[m]
477477
generics.append(Working)
478478

479-
# A[int, C] -> 2 caches
480-
# A[int, C][M] -> 3 caches // this is generated by `mro`
481-
# B[M] -> 3 caches
482-
target_size = cache_size + count_create_models * 3 * 2 + 2
479+
target_size = cache_size + count_create_models * 3 + 2
483480
assert len(_GENERIC_TYPES_CACHE) < target_size + _LIMITED_DICT_SIZE
484481
del models
485482
del generics
@@ -2871,139 +2868,13 @@ class AllowExtraGeneric(BaseModel, Generic[T], extra='allow'):
28712868
data: T
28722869

28732870

2874-
def test_generic_mro_single():
2875-
T = TypeVar('T')
2876-
2877-
# 1.
2878-
class A(BaseModel, Generic[T]): ...
2879-
2880-
class B(A, Generic[T]): ...
2881-
2882-
assert B[int].__mro__ == (B[int], B, A[int], A, BaseModel, Generic, object)
2883-
2884-
# 2.
2885-
class A(BaseModel, Generic[T]): ...
2886-
2887-
class B(A[T]): ...
2888-
2889-
class C(B[int]): ...
2890-
2891-
assert B.__mro__ == (B, A, BaseModel, Generic, object)
2892-
assert C.__mro__ == (C, B[int], B, A[int], A, BaseModel, Generic, object)
2893-
2894-
# 3.
2895-
class A(BaseModel, Generic[T]): ...
2896-
2897-
class B(A[T], Generic[T]): ...
2898-
2899-
class C(B[T], Generic[T]): ...
2900-
2901-
assert B.__mro__ == (B, A, BaseModel, Generic, object)
2902-
assert B[int].__mro__ == (B[int], B, A[int], A, BaseModel, Generic, object)
2903-
assert C[int].__mro__ == (C[int], C, B[int], B, A[int], A, BaseModel, Generic, object)
2904-
2905-
# 4.
2906-
class A(BaseModel, Generic[T]): ...
2907-
2908-
class B(A[int], Generic[T]): ...
2909-
2910-
class C(B[str], Generic[T]): ...
2911-
2912-
assert B.__mro__ == (B, A[int], A, BaseModel, Generic, object)
2913-
assert C.__mro__ == (C, B[str], B, A[int], A, BaseModel, Generic, object)
2914-
2915-
2916-
def test_generic_mro_multi():
2917-
T1 = TypeVar('T1')
2918-
T2 = TypeVar('T2')
2919-
T3 = TypeVar('T3')
2920-
2921-
# 1.
2922-
class A(BaseModel, Generic[T1, T2]): ...
2923-
2924-
class B(A[int, T2], BaseModel, Generic[T2]): ...
2925-
2926-
assert A[int, T2][str].__mro__ == (A[int, T2][str], A, BaseModel, Generic, object)
2927-
assert B[str].__mro__ == (B[str], B, A[int, T2][str], A, BaseModel, Generic, object)
2928-
2929-
# 2.
2930-
class A(BaseModel, Generic[T1, T2]): ...
2931-
2932-
B = A[int, T2]
2933-
C = B[str]
2934-
2935-
assert B.__mro__ == (A[int, T2], A, BaseModel, Generic, object)
2936-
assert C.__mro__ == (B[str], A, BaseModel, Generic, object)
2937-
2938-
# 3.
2939-
2940-
class A(BaseModel, Generic[T1]): ...
2941-
2942-
class B1(A[T1], Generic[T1, T2]): ...
2943-
2944-
class B2(A[T2], Generic[T1, T2]): ...
2945-
2946-
assert B1[T1, str].__mro__ == (B1[T1, str], B1, A, BaseModel, Generic, object)
2947-
assert B2[T1, str].__mro__ == (B2[T1, str], B2, A[str], A, BaseModel, Generic, object)
2948-
2949-
assert B1[T1, str][int].__mro__ == (B1[T1, str][int], B1, A[int], A, BaseModel, Generic, object)
2950-
assert B2[T1, str][int].__mro__ == (B2[T1, str][int], B2, A[str], A, BaseModel, Generic, object)
2951-
2952-
assert B1[int, str].__mro__ == (B1[int, str], B1, A[int], A, BaseModel, Generic, object)
2953-
assert B2[int, str].__mro__ == (B2[int, str], B2, A[str], A, BaseModel, Generic, object)
2954-
2955-
# 4.
2956-
2957-
class A(BaseModel, Generic[T1]): ...
2958-
2959-
class B(A[T2], Generic[T2]): ...
2960-
2961-
class C1(B[T3], Generic[T3, T1]): ...
2962-
2963-
class C2(B[T3], Generic[T3, T2]): ...
2964-
2965-
assert C1[int, str].__mro__ == (C1[int, str], C1, B[int], B, A[int], A, BaseModel, Generic, object)
2966-
assert C2[int, str].__mro__ == (C2[int, str], C2, B[int], B, A[int], A, BaseModel, Generic, object)
2967-
2968-
2969-
def test_generic_mro_circular_typevar():
2970-
T1 = TypeVar('T1')
2971-
T2 = TypeVar('T2')
2972-
T3 = TypeVar('T3')
2973-
2974-
class A(BaseModel, Generic[T1]): ...
2975-
2976-
class B(A[T2], Generic[T2]): ...
2977-
2978-
class C(B[T3], Generic[T3]): ...
2979-
2980-
class D(C[T1], Generic[T1]): ...
2981-
2982-
class E(D[T2], Generic[T2]): ...
2983-
2984-
assert E[int].__mro__ == (E[int], E, D[int], D, C[int], C, B[int], B, A[int], A, BaseModel, Generic, object)
2985-
2986-
2987-
def test_generic_mro_swap():
2988-
T1 = TypeVar('T1')
2989-
T2 = TypeVar('T2')
2990-
2991-
class A(BaseModel, Generic[T1]): ...
2992-
2993-
class B(A[T1], Generic[T1, T2]): ...
2994-
2995-
class BSwap(B[T2, T1], Generic[T1, T2]): ...
2996-
2997-
C = BSwap[T1, str]
2998-
2999-
assert C.__mro__ == (C, BSwap, B[str, T1], B, A[str], A, BaseModel, Generic, object)
3000-
assert C[int].__mro__ == (C[int], BSwap, B[str, int], B, A[str], A, BaseModel, Generic, object)
3001-
3002-
assert BSwap[int, str].__mro__[1:] == C[int].__mro__[1:]
3003-
3004-
30052871
def test_generic_field():
3006-
# https://github.com/pydantic/pydantic/issues/10039
2872+
"""Test for https://github.com/pydantic/pydantic/issues/10039.
2873+
2874+
This was originally fixed by defining a custom MRO for Pydantic models,
2875+
but the fix from https://github.com/pydantic/pydantic/pull/10666 seemed
2876+
better. Test is still kept for historical purposes.
2877+
"""
30072878

30082879
T = TypeVar('T')
30092880

@@ -3018,9 +2889,6 @@ class Model(BaseModel):
30182889

30192890
Model(input_bool=C())
30202891

3021-
assert C.__mro__ == (C, B[bool], B, A[bool], A, BaseModel, Generic, object)
3022-
assert issubclass(C, A[bool]) and issubclass(C, A[int]) is False
3023-
30242892

30252893
def test_generic_any_or_never() -> None:
30262894
T = TypeVar('T')
@@ -3099,41 +2967,3 @@ class Holder(BaseModel, Generic[T]):
30992967
holder2 = Holder(inner=Inner(inner=1))
31002968
# implies that validation succeeds for both
31012969
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)