How to type annotate decorators for classes?

Viewed 213

I have the following code:

class Mixin:
    def foo(self) -> int:
        return 1

def add_mixin(cls):
    class WithMixin(cls, Mixin):
        __name__ = cls.__name__
        __doc__ = cls.__doc__
    return WithMixin

@add_mixin
class B:
    def bar(self) -> int:
        return 2

b = B()
print(b.foo())
print(b.bar())

I understand, that in this particular case I can just write B(Mixin) and avoid the decorator, but in reality there's a lot of logic in add_mixin, which chooses/customizes the Mixin class (think @dataclass).

The code works the way you'd expect: prints 1 and 2. However, it blinds my pyright - I no longer can jump to bar, as pyright doesn't understand that WithMixin inherits from both Mixin and B.

Furthermore, mypy gets confused the other way:

mypy type_checking.py
type_checking.py:17: error: "B" has no attribute "foo"
Found 1 error in 1 file (checked 1 source file)

I'm annotating the core of an existing library, so lists of @overloads or some outright magic is fine with me - I just want the annotation to be correct.

I would like some help with two things:

  1. How do I properly annotate add_mixin (or rewrite it to have the same effect) so that pyright / mypy work correctly?
  2. Is there a comprehensive resource on typing? Preferably something that explains the implementations of the typing module (which is confusing!) and the way type checkers use these annotations. I've read the reference for typing, and both PEP 483 and PEP 484, and I think I have a decent grasp on all the covariance/contravariance/bound types stuff. However, when there's a somehwat complicated case I haven't seen before (like here), I feel completely lost. Googling around mostly leads to the same reference-style or surface-level articles (or PEPs).

I went through quite a few failed attempts, the last one being:

from typing import *

class Mixin:
    def foo(self) -> int:
        return 1

_T1 = TypeVar("_T1")
class WithMixin(Type[_T1], Mixin):
    ...


def add_mixin(cls: Type[_T1]) -> WithMixin[_T1]:
    return type(cls.__name__, (cls, Mixin), {})

@add_mixin
class B:
    def bar(self) -> int:
        return 2

b = B()
print(b.foo())
print(b.bar())

pyright gets confused at the type(cls.__name__, (cls, Mixin), {}) line, while mypy says inheriting from Type[_T1] is not allowed (which it isn't, but I thought that the type checker might pick it up anyway).

0 Answers
Related