diff --git a/pydantic/_internal/_generics.py b/pydantic/_internal/_generics.py index 876b5e7be5..5015ce7aec 100644 --- a/pydantic/_internal/_generics.py +++ b/pydantic/_internal/_generics.py @@ -15,7 +15,7 @@ from ._core_utils import get_type_ref from ._forward_ref import PydanticRecursiveRef from ._typing_extra import TypeVarType, typing_base -from ._utils import all_identical, is_basemodel +from ._utils import all_identical, is_model_class if sys.version_info >= (3, 10): from typing import _UnionGenericAlias # type: ignore[attr-defined] @@ -197,7 +197,7 @@ def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]: """ if isinstance(v, TypeVar): yield v - elif is_basemodel(v): + elif is_model_class(v): yield from v.__pydantic_generic_metadata__['parameters'] elif isinstance(v, (DictValues, list)): for var in v: @@ -316,7 +316,7 @@ def replace_types(type_: Any, type_map: Mapping[Any, Any] | None) -> Any: # We handle pydantic generic models separately as they don't have the same # semantics as "typing" classes or generic aliases - if not origin_type and is_basemodel(type_): + if not origin_type and is_model_class(type_): parameters = type_.__pydantic_generic_metadata__['parameters'] if not parameters: return type_ diff --git a/pydantic/_internal/_utils.py b/pydantic/_internal/_utils.py index 16d921e9c3..5e6182aaa7 100644 --- a/pydantic/_internal/_utils.py +++ b/pydantic/_internal/_utils.py @@ -79,17 +79,13 @@ def lenient_issubclass(cls: Any, class_or_tuple: Any) -> bool: # pragma: no cov raise # pragma: no cover -def is_basemodel(cls: Any) -> TypeGuard[type[BaseModel]]: - """We can remove this function and go back to using lenient_issubclass, but this is nice because it - ensures that we get proper type-checking, which lenient_issubclass doesn't provide. - - Would be nice if there was a lenient_issubclass-equivalent in typing_extensions, or otherwise - a way to define such a function that would support proper type-checking; maybe we should bring it up - at the typing summit.. +def is_model_class(cls: Any) -> TypeGuard[type[BaseModel]]: + """Returns true if cls is a _proper_ subclass of BaseModel, and provides proper type-checking, + unlike raw calls to lenient_issubclass. """ from ..main import BaseModel - return lenient_issubclass(cls, BaseModel) + return lenient_issubclass(cls, BaseModel) and cls is not BaseModel def is_valid_identifier(identifier: str) -> bool: diff --git a/tests/test_generics.py b/tests/test_generics.py index 512b411cb1..434035d40f 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -2551,3 +2551,13 @@ class ParamSpecGenericModel(BaseModel, Generic[P]): my_generic: MyGenericParamSpecClass[P] model_config = dict(arbitrary_types_allowed=True) + + +def test_parametrize_with_basemodel(): + T = TypeVar('T') + + class SimpleGenericModel(BaseModel, Generic[T]): + pass + + class Concrete(SimpleGenericModel[BaseModel]): + pass