attrs icon indicating copy to clipboard operation
attrs copied to clipboard

Type annotation for attr.ib wrappers functions

Open williamjamir opened this issue 4 years ago • 8 comments

It's possible to type a method that wraps an attr.ib attribute?

Example:

import attr
from typing import Any

def typed() -> Any:
    return attr.ib(type=str)

@attr.s
class TestingParams:
    foo = attr.ib(type=str)
    acme = typed()

TestingParams(foo='1', acme='3')

Executing mypy (0.812) with Python (3.7.3) and attrs (20.3.0) I got the following output

error: Unexpected keyword argument "acme" for "TestingParams"  [call-arg]

williamjamir avatar Mar 11 '21 18:03 williamjamir

Sadly, the plugin works by looking for certain hard coded names. attr.ib and others.
But if you're using Python 3 you can probably use

@attr.define
class TestingParams:
   foo: str
   acme: str

and forget about attr.ib

euresti avatar Mar 12 '21 23:03 euresti

Does it work if you write it as acme = Factory(typed)?

wsanchez avatar Apr 09 '21 18:04 wsanchez

(That said, I'd probably go with @attr.define and use acme: str = typed().)

wsanchez avatar Apr 09 '21 18:04 wsanchez

Sorry for the late answer

@euresti Thanks for the input, using @attr.define indeed works with mypy (without this, mypy fails to recognize the type).

Just for disclosure, my actual usage is a bit more "heavy" than the sample. Think about around 60 attr decorated the class with multiple attributes defined with converters, validators, and different default values according to parameters informed, and for this reason, I'm trying to type in a way that mypy recognizes it.

About the @wsanchez question:

Does it work if you write it as acme = Factory(typed)?

No =/

Perhaps I'm doing it wrong but I testes in this way.

@attr.s
class TestingParams:
    foo = attr.ib(type=str)
    acme =Factory(typed)

error: Unexpected keyword argument "acme" for "TestingParams"

Just adding new information to the discussion, while testing attr.define I noticed that mypy and wrapped attributes works fine;

@attr.define
class TestingParams:
    foo: str 
    acme: str = typed()

But without the auto_attrib mypy fails to recognize the attribute that has a wrapped function.

@attr.s
class TestingParams:
    foo: str = attr.ib()
    acme: str = typed()

>>> attr.fields_dict(TestingParams)['acme'].type
<class 'str'>

>>> mypy test.py
error: Unexpected keyword argument "acme" for "TestingParams"

So I believe that besides the limitation from the mypy plugin that cannot get the type from the wrapped function there is this bug with mypy failing to recognize the type from the acme parameter.

williamjamir avatar Apr 09 '21 20:04 williamjamir

Hrm… I'm getting a different result:

wsanchez$ python3.9 -m venv test
wsanchez$ ./test/bin/pip install -q attrs mypy
wsanchez$ ./test/bin/pip freeze
attrs==20.3.0
mypy==0.812
mypy-extensions==0.4.3
typed-ast==1.4.2
typing-extensions==3.7.4.3
import attr
from typing import Any

def typed() -> Any:
    return attr.ib(type=str)

@attr.define
class TestingParams:
    foo: str 
    acme: str = attr.Factory(typed)

TestingParams(foo='1', acme='3')
wsanchez$ mypy test.py 
Success: no issues found in 1 source file

wsanchez avatar Apr 09 '21 21:04 wsanchez

Ah wait, you said that does work, but only with auto_attribs=True.

wsanchez avatar Apr 09 '21 21:04 wsanchez

You can always use a "plugin" to tell the attrs plugin that typed is an "attrib_maker" See https://github.com/python-attrs/attrs/issues/630#issuecomment-607281253

euresti avatar Apr 09 '21 21:04 euresti

There's even proper docs: https://www.attrs.org/en/stable/extending.html#wrapping-the-decorator

hynek avatar Apr 10 '21 06:04 hynek