Uh oh!
There was an error while loading. Please reload this page.
Fix MutableMapping.update to require string keys - #14099
Conversation
Currently with most type checkers you can do:
```python
d: dict[int, int] = {}
d.update(a=2)
```
The reason for that is that `update` merely requires `MutableMapping[_KT, _VT]`, whereas as soon as you are passing kwargs, you must have a str key type.
AlexWaygood
left a comment
There was a problem hiding this comment.
Could you also update the other methods that need to be kept in line with this method, as per this comment?
Lines 790 to 800 in ec8b6ec
AlexWaygood
commented
May 19, 2025
Oh no scratch that, none of those other methods take keyword arguments |
Diff from mypy_primer, showing the effect of this PR on open source code: aioredis (https://github.com/aio-libs/aioredis)
+ aioredis/client.py:146: note: def update(self, SupportsKeysAndGetItem[Any, Any], /) -> None+ aioredis/client.py:146: note: @overload+ aioredis/client.py:146: note: @overload+ aioredis/client.py:146: note: def update(self, Iterable[tuple[Any, Any]], /) -> None
operator (https://github.com/canonical/operator)
+ ops/model.py:1960: note: def update(self, SupportsKeysAndGetItem[str, str], /) -> None+ ops/model.py:1960: note: @overload+ ops/model.py:1960: note: @overload+ ops/model.py:1960: note: def update(self, Iterable[tuple[str, str]], /) -> None
ibis (https://github.com/ibis-project/ibis)
+ ibis/common/annotations.py:450: note: def update(self, SupportsKeysAndGetItem[Any, Any], /) -> None+ ibis/common/annotations.py:450: note: def update(self, Iterable[tuple[Any, Any]], /) -> None
mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
+ bson/son.py:146: error: Unused "type: ignore" comment [unused-ignore]
werkzeug (https://github.com/pallets/werkzeug)
+ src/werkzeug/datastructures/mixins.py:309: error: No overload variant of "update" of "MutableMapping" matches argument type "dict[str, V]" [call-overload]+ src/werkzeug/datastructures/mixins.py:309: note: Possible overload variants:+ src/werkzeug/datastructures/mixins.py:309: note: def update(self, SupportsKeysAndGetItem[K, V], /) -> None+ src/werkzeug/datastructures/mixins.py:309: note: def update(self, Iterable[tuple[K, V]], /) -> None
discord.py (https://github.com/Rapptz/discord.py)
+ discord/client.py:733: note: def update(self, SupportsKeysAndGetItem[str, int | None], /) -> None+ discord/client.py:733: note: def update(self, Iterable[tuple[str, int | None]], /) -> None+ discord/client.py:759: note: def update(self, SupportsKeysAndGetItem[str, int | None], /) -> None+ discord/client.py:759: note: def update(self, Iterable[tuple[str, int | None]], /) -> None
meson (https://github.com/mesonbuild/meson)
+ mesonbuild/options.py:1149:9: error: No overload variant of "update" of "MutableMapping" matches argument type "dict[str, UserBooleanOption | UserComboOption | UserFeatureOption | UserIntegerOption | UserStdOption | UserStringArrayOption | UserStringOption | UserUmaskOption]" [call-overload]+ mesonbuild/options.py:1149:9: note: Possible overload variants:+ mesonbuild/options.py:1149:9: note: def update(self, SupportsKeysAndGetItem[OptionKey, UserBooleanOption | UserComboOption | UserFeatureOption | UserIntegerOption | UserStdOption | UserStringArrayOption | UserStringOption | UserUmaskOption], /) -> None+ mesonbuild/options.py:1149:9: note: def update(self, Iterable[tuple[OptionKey, UserBooleanOption | UserComboOption | UserFeatureOption | UserIntegerOption | UserStdOption | UserStringArrayOption | UserStringOption | UserUmaskOption]], /) -> None |
AlexWaygood
commented
May 19, 2025
The only two new errors here are in |
| def update(self, m: SupportsKeysAndGetItem[_KT, _VT], /) -> None: ... | ||
| @overload | ||
| def update(self, m: Iterable[tuple[_KT, _VT]], /, **kwargs: _VT) -> None: ... | ||
| def update(self: Mapping[str, _VT], m: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... |
There was a problem hiding this comment.
Should this be:
defupdate(self: Mapping[str, _VT], m: SupportsKeysAndGetItem[str, _VT], /, **kwargs: _VT) ->None: ...It's possible that _KT is constrained to str anyway via the self mapping, but I still think being explicit would be better (and easier on the type checkers).
There was a problem hiding this comment.
I think SupportsKeysAndGetItem is invariant in the first arg so that might cause trouble?
| def update(self, **kwargs: _VT) -> None: ... | ||
| def update(self, m: Iterable[tuple[_KT, _VT]], /) -> None: ... | ||
| @overload | ||
| def update(self: Mapping[str, _VT], m: Iterable[tuple[_KT, _VT]], /, **kwargs: _VT) -> None: ... |
We are confining `_KT` to `str`, so use that everywhere. Equivalent, but simpler.
QEDady
commented
May 19, 2025
Hello, The new annotation will not allow the following, I think. Shouldn't it be: ? |
srittau
commented
May 20, 2025
We should probably add more test cases for these basic collection types, especially for more complex cases like |
srittau
commented
May 20, 2025
That said, the following test case passes: defcheck_update_method() ->None:
d: dict[int, int] = {}
d.update({1:2}, a=3)
d.update([(1, 2)], a=3)But we need the explicit annotation for the dict. |
srittau
commented
May 20, 2025
In fact, while the mypy tests pass, the pyright tests don't: #14104 |
Currently with most type checkers you can do:
The reason for that is that
updatemerely requiresMutableMapping[_KT, _VT], whereas as soon as you are passing kwargs, you must have a str key type.