Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing with converters not working #519

Open
berquist opened this issue Mar 15, 2019 · 5 comments
Open

Typing with converters not working #519

berquist opened this issue Mar 15, 2019 · 5 comments
Labels
Bug Typing Typing/stub/Mypy/PyRight related bugs.

Comments

@berquist
Copy link

berquist commented Mar 15, 2019

Using attrs 19.1.0 on Python 3.6.7,

from typing import Any, Iterable, List, Tuple, TypeVar

from attr import attrs, attrib

T = TypeVar("T")

def to_tuple(val: Iterable[T]) -> Tuple[T, ...]:
    return tuple(val)

def to_list(val: Iterable[T]) -> List[T]:
    return list(val)

@attrs
class C:
    elements: Tuple[str, ...] = attrib(converter=to_tuple)

@attrs
class D:
    elements: Tuple[str, ...] = attrib(converter=tuple)

@attrs
class E:
    elements: List[str] = attrib(converter=list)

@attrs
class F:
    elements: List[str] = attrib(converter=to_list)


if __name__ == "__main__":
    l = ["a", "b", "c"]
    c = C(l)
    d = D(l)
    e = E(l)
    f = F(l)

gives

32: error: Argument 1 to "C" has incompatible type "List[str]"; expected "Iterable[T]"
33: error: Argument 1 to "D" has incompatible type "List[str]"; expected "Iterable[_T_co]"
34: error: Argument 1 to "E" has incompatible type "List[str]"; expected "Iterable[_T]"
35: error: Argument 1 to "F" has incompatible type "List[str]"; expected "Iterable[T]"

This may be related to python/mypy#5738, but the issue goes back as far as mypy 0.610.

@berquist berquist changed the title Typing with class-name-based converters not working Typing with converters not working Mar 15, 2019
@hynek
Copy link
Member

hynek commented Mar 21, 2019

That looks like exactly @euresti's comment in that mypy bug?

@hynek hynek added the Typing Typing/stub/Mypy/PyRight related bugs. label Mar 21, 2019
@wsanchez wsanchez added the Bug label May 3, 2019
gabbard pushed a commit to isi-vista/immutablecollections that referenced this issue Jul 25, 2019
@samueljsb
Copy link

samueljsb commented Sep 1, 2023

That mypy bug appears to have been fixed, but I'm seeing something that looks like a similar problem with dict. The example in @berquist's original comment is also still failing for me.

@berquist, did you get anywhere with a workaround? Or have you seen any other mypy issues that look relevant? (I'm struggling to find any)

my minimal example with `dict`
import attrs


@attrs.frozen
class C:
    foo: dict = attrs.field(factory=dict, converter=dict)


C(foo={"a": 1})
$ mypy t.py
t.py:9: error: Argument "foo" to "C" has incompatible type "dict[str, int]"; expected "_VT | SupportsKeysAndGetItem[_KT, _VT] | SupportsKeysAndGetItem[str, _VT] | Iterable[tuple[_KT, _VT]] | Iterable[tuple[str, _VT]] | Iterable[list[str]] | Iterable[list[bytes]]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
$ python --version
Python 3.11.5

$ pip freeze
attrs==23.1.0
mypy==1.5.1
mypy-extensions==1.0.0
typing_extensions==4.7.1

@bskinn
Copy link

bskinn commented Jan 5, 2024

I just ran into the same problem in an essentially analogous situation to @berquist's. (Hiya, Eric! Been a while, hope all is well.)

I was able to satisfy mypy by wrapping the list conversion in a lambda, instead of just passing the raw type itself.

Toy example:

import attrs


@attrs.define(kw_only=True)
class ClassConverter:
    my_list: list[int] = attrs.field(converter=list)

    def evolve_my_list(self, new_list: list[int]):
        return self.__class__(my_list=new_list)


@attrs.define(kw_only=True)
class LambdaConverter:
    my_list: list[int] = attrs.field(converter=lambda v: list(v))

    def evolve_my_list(self, new_list: list[int]):
        return self.__class__(my_list=new_list)

mypy output:

9: error: Argument "my_list" to "ClassConverter" has incompatible type "list[int]"; expected "Iterable[_T]"  [arg-type]

And no typing error on LambdaConverter.

Python 3.11.7 on Debian WSL2
attrs 23.1.0
mypy 1.8.0 (compiled: yes)

I don't know if this is a good or right way to address this, but it's semantically correct and makes mypy happy, sooooo.... ¯\_(ツ)_/¯

@sscherfke
Copy link
Contributor

sscherfke commented Jan 5, 2024

Hi @bskinn, you need to use a dedicate converter function for typing to work correctly, because list is not Callable[[Any], list[int]]. You should also use the @classmethod decorator for your factory functions.

The following code works with Python 3.12 and mypy 1.8:

from typing import Any, Self, Type

import attrs


def to_int(v: Any) -> list[int]:
    return [int(item) for item in v]


@attrs.define(kw_only=True)
class ClassConverter:
    my_list: list[int] = attrs.field(converter=to_int)

    @classmethod
    def evolve_my_list(cls: Type[Self], new_list: list[int]) -> Self:
        return cls(my_list=new_list)


@attrs.define(kw_only=True)
class LambdaConverter:
    my_list: list[int] = attrs.field(converter=lambda v: list(v))

    @classmethod
    def evolve_my_list(cls: Type[Self], new_list: list[int]) -> Self:
        return cls(my_list=new_list)

@bskinn
Copy link

bskinn commented Jan 5, 2024

Thanks, @sscherfke!

I was wondering if a function with an explicit internal int cast would be the most correct way to do it.

And you're right, for that toy example, @classmethod is the way to go. For my actual use-case (private code, unfortunately), I'm defining custom __add__ and __mul__, so as best I understand I do need instance methods and self.__class__(...).

(FWIW I also need runtime checks to be sure that the incoming argument is actually isinstance(addend, int), so there's yet further difference there, but that's all implementation-specific. Didn't want to clutter my proposed solution with all that.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Typing Typing/stub/Mypy/PyRight related bugs.
Projects
None yet
Development

No branches or pull requests

6 participants