-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix MutableMapping.update to require string keys #14099
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
# Various mapping classes have __ior__ methods that should be kept roughly in line with .update(): | |
# -- dict.__ior__ | |
# -- os._Environ.__ior__ | |
# -- collections.UserDict.__ior__ | |
# -- collections.ChainMap.__ior__ | |
# -- peewee.attrdict.__add__ | |
# -- peewee.attrdict.__iadd__ | |
# -- weakref.WeakValueDictionary.__ior__ | |
# -- weakref.WeakKeyDictionary.__ior__ | |
@overload | |
def update(self, m: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) -> None: ... |
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
|
The only two new errors here are in |
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be:
def update(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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think SupportsKeysAndGetItem is invariant in the first arg so that might cause trouble?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#14100 for these cleanups.
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: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
We are confining `_KT` to `str`, so use that everywhere. Equivalent, but simpler.
Hello, The new annotation will not allow the following, I think.
Shouldn't it be:
? |
We should probably add more test cases for these basic collection types, especially for more complex cases like |
That said, the following test case passes: def check_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. |
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
update
merely requiresMutableMapping[_KT, _VT]
, whereas as soon as you are passing kwargs, you must have a str key type.