Search code examples
pythonpython-typingpython-attrs

Annotating function argument accepting an instance of a class decorated with @define


I'm using pythons attrs package to @define some classes containing some members with little logic for isolation purposes. However, when I run the following stripped down example:

from attrs import define

@define
class DefTest:
    attr1: int = 123
    attr2: int = 456
    attr3: str = "abcdef"


x = DefTest()
print(type(x))

it justs outputs <class '__main__.DefTest'>, with now hint which type it is derived from. Even type(x).mro() outputs [<class '__main__.DefTest'>, <class 'object'>], with no reference to some internally defined attrs class.

Now, imagine a function which takes an attrs class instance and performs some action on it. How can I add a proper type annotation, instead of the placeholder?

from attrs import asdict, define
import json


def asjson(attrsclass: "DefineDecoratedClass"):
    return json.dumps(asdict(attrsclass))

I tried annotating using attrs.AttrsInstance which seems to be wrong from the linters perspective. Any idea?


Solution

  • I'm an attrs maintainer and I sometimes work on the attrs Mypy plugin.

    attrs will not inject a class into your MRO; this is entirely by design to let you have complete control over your class tree. The way to recognize an attrs class is to look for the presence of an __attrs_attrs__ class attribute, which is how the AttrsInstance protocol is defined in the first place.

    Note that you need to use type[AttrsInstance] if you're looking for an attrs class, as opposed to an instance of an attrs class.

    The Mypy plugin correctly generates the __attrs_attrs__ marker, so this protocol should work with the latest version of Mypy. Pyright doesn't, and since PEP 681 (dataclass transforms) makes no provisions for this and Pyright has no plugin system, I believe there's nothing we can do about it.