Skip to content

Fix MutableMapping.update to require string keys - #14099

Merged
AlexWaygood merged 1 commit into
python:mainfrom
ndmitchell:patch-1
May 19, 2025
Merged

Fix MutableMapping.update to require string keys#14099
AlexWaygood merged 1 commit into
python:mainfrom
ndmitchell:patch-1

Conversation

@ndmitchell

Copy link
Copy Markdown
Contributor

Currently with most type checkers you can do:

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.

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.

@AlexWaygoodAlexWaygood left a comment

Copy link
Copy Markdown
Member

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?

# 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
defupdate(self, m: SupportsKeysAndGetItem[_KT, _VT], /, **kwargs: _VT) ->None: ...

@AlexWaygood

Copy link
Copy Markdown
Member

Oh no scratch that, none of those other methods take keyword arguments

@github-actions

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Member

The only two new errors here are in werkzeug and meson. The werkzeug one already has a type: ignore comment on the method (just a different line) and the meson one has a FIXME: this method must be deleted comment immediately above it. So I think we're good to go!

@AlexWaygood
AlexWaygood merged commit 929aca4 into python:mainMay 19, 2025
Comment threadstdlib/typing.pyi
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: ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#14100 for these cleanups.

Comment threadstdlib/typing.pyi
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: ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

@ndmitchell
ndmitchell deleted the patch-1 branch May 19, 2025 16:34
ndmitchell added a commit to ndmitchell/typeshed that referenced this pull request May 19, 2025
We are confining `_KT` to `str`, so use that everywhere. Equivalent, but simpler.
srittau pushed a commit that referenced this pull request May 19, 2025
We are confining `_KT` to `str`, so use that everywhere. Equivalent, but simpler.
@QEDady

Copy link
Copy Markdown

Hello,

The new annotation will not allow the following, I think.

d = {}
d.update({1:2}, a=3)
d.update([(1, 2), a=3)

Shouldn't it be:

 def update(self: Mapping[str | _KT, _VT], m: SupportsKeysAndGetItem[str | _KT, _VT], /, **kwargs: _VT) -> None: ...
def update(self: Mapping[str | _KT, _VT], m: Iterable[tuple[str | _KT, _VT]], /, **kwargs: _VT) -> None: ...

?

@srittau

Copy link
Copy Markdown
Collaborator

We should probably add more test cases for these basic collection types, especially for more complex cases like MutableMapping.update.

@srittau

Copy link
Copy Markdown
Collaborator

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 added a commit to srittau/typeshed that referenced this pull request May 20, 2025
@srittau

Copy link
Copy Markdown
Collaborator

In fact, while the mypy tests pass, the pyright tests don't: #14104

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@ndmitchell@AlexWaygood@QEDady@srittau@JelleZijlstra