typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

Change `astuple` and `asdict` factories

Open sobolevn opened this issue 3 years ago • 14 comments

There's a problem with tuple_factory and dict_factory in dataclasses.pyi Why? Because the given callback type does not really match tuple / dict constructors.

Example:

from dataclasses import dataclass, astuple, asdict

@dataclass
class D:
    x: int

astuple(D(1), tuple_factory=tuple)
asdict(D(2), dict_factory=dict)  # Argument "dict_factory" to "asdict" has incompatible type "Type[Dict[Any, Any]]"; expected "Callable[[List[Tuple[str, Any]]], Dict[_KT, _VT]]"

Source: https://github.com/python/cpython/blob/main/Lib/dataclasses.py#L1345-L1349

Closes https://github.com/python/typeshed/issues/8518

sobolevn avatar Aug 10 '22 09:08 sobolevn

Diff from mypy_primer, showing the effect of this PR on open source code:

core (https://github.com/home-assistant/core)
+ homeassistant/components/repairs/websocket_api.py:75: error: Argument "dict_factory" to "asdict" has incompatible type "Callable[[List[Tuple[Any, Any]]], Dict[Any, Any]]"; expected "Callable[[Iterable[Tuple[str, Any]]], Dict[Any, Any]]"  [arg-type]

github-actions[bot] avatar Aug 10 '22 09:08 github-actions[bot]

Diff from mypy_primer, showing the effect of this PR on open source code:

core (https://github.com/home-assistant/core)
+ homeassistant/components/repairs/websocket_api.py:75: error: Argument "dict_factory" to "asdict" has incompatible type "Callable[[List[Tuple[Any, Any]]], Dict[Any, Any]]"; expected "Callable[[Iterable[Tuple[str, Any]]], Dict[Any, Any]]"  [arg-type]

github-actions[bot] avatar Aug 10 '22 09:08 github-actions[bot]

I've added some simple test cases for this. Because in my opinion this annotation is very complex.

This should be something like:

def asdict(obj: Any, *, dict_factory: Callable[[Iterable[Tuple[str, Any]]], _T[str, Any]]) -> _T[str, Any]: ...

sobolevn avatar Aug 10 '22 09:08 sobolevn

Diff from mypy_primer, showing the effect of this PR on open source code:

core (https://github.com/home-assistant/core)
+ homeassistant/components/repairs/websocket_api.py:75: error: Argument "dict_factory" to "asdict" has incompatible type "Callable[[List[Tuple[Any, Any]]], Dict[Any, Any]]"; expected "Callable[[Iterable[Tuple[str, Any]]], Dict[Any, Any]]"  [arg-type]

github-actions[bot] avatar Aug 10 '22 09:08 github-actions[bot]

Diff from mypy_primer, showing the effect of this PR on open source code:

core (https://github.com/home-assistant/core)
+ homeassistant/components/repairs/websocket_api.py:75: error: Argument "dict_factory" to "asdict" has incompatible type "Callable[[List[Tuple[Any, Any]]], Dict[Any, Any]]"; expected "Callable[[Iterable[Tuple[str, Any]]], Dict[Any, Any]]"  [arg-type]

github-actions[bot] avatar Aug 10 '22 11:08 github-actions[bot]

Here's the homeassistant code: https://github.com/home-assistant/core/blob/982d197ff3c493024846f38d8688985b6acd3707/homeassistant/components/repairs/websocket_api.py#L66-L78. The primer error is due to parameter types in a Callable being contravariant, so Callable[[list[tuple[Any, Any]]], Any] is not seen as a valid subtype of Callable[[Iterable[tuple[Any, Any]]], Any]. I think they'll just have to update their annotation from list[tuple[Any, Any]] to Iterable[tuple[Any, Any]], if we merge this.

AlexWaygood avatar Aug 10 '22 16:08 AlexWaygood

I am not sure that list -> Iterable is the proper fix. Because dict is still not allowed, see failing tests 😒

sobolevn avatar Aug 10 '22 17:08 sobolevn

Diff from mypy_primer, showing the effect of this PR on open source code:

core (https://github.com/home-assistant/core)
+ homeassistant/components/repairs/websocket_api.py:75: error: Argument "dict_factory" to "asdict" has incompatible type "Callable[[List[Tuple[Any, Any]]], Dict[Any, Any]]"; expected "Callable[[Iterable[Tuple[str, Any]]], Dict[Any, Any]]"  [arg-type]

github-actions[bot] avatar Aug 10 '22 18:08 github-actions[bot]

Minimal reproducer:

from typing import *
T = TypeVar("T")

def f(dict_factory: Callable[[list[tuple[str, Any]]], T]) -> T:
    ...

f(dict)

Error: Argument 1 to "f" has incompatible type "Type[Dict[Any, Any]]"; expected "Callable[[list[Tuple[str, Any]]], Dict[_KT, _VT]]"

Mypy correctly infers T to be the return type of dict, viewed as a callable, which is dict[_KT, _VT]. But it doesn't realize that _KT can be solved to a string. Changing tuple[str, Any] to tuple[Any, Any] helps, and would be a better fix than changing list:

  • The user doesn't have to be prepared to handle arbitrary iterables. At runtime it is always a list.
  • It seems like if the user types their function with tuple[str, Any] instead of tuple[Any, Any], it would still work even though list is invariant.

We could also try tuple[str | Any, Any].

Akuli avatar Aug 10 '22 19:08 Akuli

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Aug 10 '22 21:08 github-actions[bot]

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Aug 13 '22 03:08 github-actions[bot]

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Aug 15 '22 10:08 github-actions[bot]

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Sep 05 '22 23:09 github-actions[bot]

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

github-actions[bot] avatar Sep 06 '22 06:09 github-actions[bot]

Closing for now due to lack of activity and to keep the number of open PRs manageable. Happy to reopen if you're still interested in working on this! :)

AlexWaygood avatar Nov 25 '22 10:11 AlexWaygood

I have 0 ideas on how to fix it.

sobolevn avatar Nov 25 '22 11:11 sobolevn