Skip to content

Commit 3b5b3cf

Browse files
authored
monkeypatch: add support for TypedDict (#11000)
1 parent 23e343a commit 3b5b3cf

File tree

4 files changed

+28
-7
lines changed

4 files changed

+28
-7
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Abdeali JK
88
Abdelrahman Elbehery
99
Abhijeet Kasurde
1010
Adam Johnson
11+
Adam Stewart
1112
Adam Uhlir
1213
Ahn Ki-Wook
1314
Akiomi Kamakura

changelog/10999.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments.

src/_pytest/monkeypatch.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Any
88
from typing import Generator
99
from typing import List
10+
from typing import Mapping
1011
from typing import MutableMapping
1112
from typing import Optional
1213
from typing import overload
@@ -129,7 +130,7 @@ class MonkeyPatch:
129130

130131
def __init__(self) -> None:
131132
self._setattr: List[Tuple[object, str, object]] = []
132-
self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = []
133+
self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = []
133134
self._cwd: Optional[str] = None
134135
self._savesyspath: Optional[List[str]] = None
135136

@@ -290,12 +291,13 @@ def delattr(
290291
self._setattr.append((target, name, oldval))
291292
delattr(target, name)
292293

293-
def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None:
294+
def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None:
294295
"""Set dictionary entry ``name`` to value."""
295296
self._setitem.append((dic, name, dic.get(name, notset)))
296-
dic[name] = value
297+
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
298+
dic[name] = value # type: ignore[index]
297299

298-
def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None:
300+
def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None:
299301
"""Delete ``name`` from dict.
300302
301303
Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to
@@ -306,7 +308,8 @@ def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> N
306308
raise KeyError(name)
307309
else:
308310
self._setitem.append((dic, name, dic.get(name, notset)))
309-
del dic[name]
311+
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
312+
del dic[name] # type: ignore[attr-defined]
310313

311314
def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
312315
"""Set environment variable ``name`` to ``value``.
@@ -401,11 +404,13 @@ def undo(self) -> None:
401404
for dictionary, key, value in reversed(self._setitem):
402405
if value is notset:
403406
try:
404-
del dictionary[key]
407+
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
408+
del dictionary[key] # type: ignore[attr-defined]
405409
except KeyError:
406410
pass # Was already deleted, so we have the desired state.
407411
else:
408-
dictionary[key] = value
412+
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
413+
dictionary[key] = value # type: ignore[index]
409414
self._setitem[:] = []
410415
if self._savesyspath is not None:
411416
sys.path[:] = self._savesyspath

testing/typing_checks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing_extensions import assert_type
1010

1111
import pytest
12+
from pytest import MonkeyPatch
1213

1314

1415
# Issue #7488.
@@ -29,6 +30,19 @@ def check_parametrize_ids_callable(func) -> None:
2930
pass
3031

3132

33+
# Issue #10999.
34+
def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None:
35+
from typing import TypedDict
36+
37+
class Foo(TypedDict):
38+
x: int
39+
y: float
40+
41+
a: Foo = {"x": 1, "y": 3.14}
42+
monkeypatch.setitem(a, "x", 2)
43+
monkeypatch.delitem(a, "y")
44+
45+
3246
def check_raises_is_a_context_manager(val: bool) -> None:
3347
with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo:
3448
pass

0 commit comments

Comments
 (0)