typeshed
typeshed copied to clipboard
Change `astuple` and `asdict` factories
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
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]
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]
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]: ...
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]
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]
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.
I am not sure that list -> Iterable is the proper fix.
Because dict is still not allowed, see failing tests 😒
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]
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 oftuple[Any, Any], it would still work even thoughlistis invariant.
We could also try tuple[str | Any, Any].
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉
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! :)
I have 0 ideas on how to fix it.