2026, Jan 03 19:00

Fixing mypy attr-defined errors in Python generics: avoid TypeVar shadowing with PEP 695 or Generic

Learn why mypy reports 'type[T] has no attribute' when mixing Python generic styles. See how TypeVar shadowing occurs and fix it using Generic or PEP 695.

When wiring up a generic factory that takes a model class and calls a known classmethod on it, it’s easy to trip over Python’s two ways of declaring generics. The symptom looks like a confusing mypy complaint that a type parameter has no attribute you clearly defined on the base class. The root cause is subtle: two different type variables with the same name that don’t actually refer to each other.

Problem example

The snippet below compiles and runs, but mypy reports that the class-level attribute isn’t there. The intent is to ensure the type argument is a subclass of the base, so the classmethod is always present.

from typing import TypeVar, Type
class Schema:
    @classmethod
    def model_validate(cls):
        print('Done')
T = TypeVar('T', bound=Schema)
class Wrapper[T]:
    def __init__(self, model: Type[T]):
        self.model = model
    def run(self) -> None:
        print(self.model.model_validate())
Wrapper[Schema](Schema).run()

mypy responds with the following:

error: "type[T]" has no attribute "model_validate" [attr-defined]

Why the error happens

The class header class Wrapper[T]: introduces a brand-new type parameter named T with no constraints. That new T shadows the previously declared T = TypeVar('T', bound=Schema). As a result, inside Wrapper the type parameter is unconstrained, and mypy cannot infer that model_validate is available on type[T]. The code at runtime works, but the type checker is correct: it can’t assume the attribute exists on an unconstrained type.

Fix: use one generic style consistently

You can fix this by picking a single approach to generics and sticking to it. Both of the following are accepted by mypy.

First, the classic typing.Generic style with a bound TypeVar:

from typing import Generic, TypeVar
class Schema:
    @classmethod
    def model_validate(cls) -> None:
        print('Done')
U = TypeVar('U', bound=Schema)
class Holder(Generic[U]):
    def __init__(self, model: type[U]) -> None:
        self.model = model
    def run(self) -> None:
        print(self.model.model_validate())
Holder[Schema](Schema).run()

Second, the PEP 695 style where the bound is declared inline on the type parameter:

class Schema:
    @classmethod
    def model_validate(cls) -> None:
        print('Done')
class Holder[V: Schema]:
    def __init__(self, model: type[V]) -> None:
        self.model = model
    def run(self) -> None:
        print(self.model.model_validate())
Holder[Schema](Schema).run()

In both versions the type parameter that the class uses is the same one that carries the bound, so mypy can verify that the classmethod is present on the supplied model. Also note that you don’t need typing.Type if you’re using a non-EOL Python version; the built-in type should be used instead since 3.9.

Why this matters

Using a single, consistent generic declaration prevents shadowing, keeps type information intact, and gives you the guarantees you expect from static analysis. You get reliable attribute checking on class objects, clearer intent for readers, and fewer surprises when switching between legacy and modern generic syntax.

Conclusion

Pick one generic style per class and ensure the type parameter with the bound is the same one the class actually uses. If you prefer the older Generic approach, bind the TypeVar and pass type[T]. If you prefer PEP 695, declare the bound inline in the class header. And where available, favor the built-in type over typing.Type. Do this, and mypy will happily confirm that your factory’s classmethod calls are safe.