Skip to content
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

monkeypatch: add support for TypedDict #11000

Merged
merged 14 commits into from
May 14, 2023
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Abdeali JK
Abdelrahman Elbehery
Abhijeet Kasurde
Adam Johnson
Adam Stewart
Adam Uhlir
Ahn Ki-Wook
Akiomi Kamakura
Expand Down
1 change: 1 addition & 0 deletions changelog/10999.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed bug where monkeypatch setitem/delitem type annotations didn't support typing.TypedDict.
adamjstewart marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 10 additions & 5 deletions src/_pytest/monkeypatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Any
from typing import Generator
from typing import List
from typing import MutableMapping
from typing import Mapping
from typing import Optional
from typing import overload
from typing import Tuple
Expand Down Expand Up @@ -129,7 +129,7 @@ class MonkeyPatch:

def __init__(self) -> None:
self._setattr: List[Tuple[object, str, object]] = []
self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = []
self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = []
self._cwd: Optional[str] = None
self._savesyspath: Optional[List[str]] = None

Expand Down Expand Up @@ -290,12 +290,17 @@ def delattr(
self._setattr.append((target, name, oldval))
delattr(target, name)

def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None:
def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None:
"""Set dictionary entry ``name`` to value."""
self._setitem.append((dic, name, dic.get(name, notset)))
dic[name] = value

def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None:
def delitem(
self,
dic: Mapping[K, V],
name: K,
raising: bool = True,
) -> None:
adamjstewart marked this conversation as resolved.
Show resolved Hide resolved
"""Delete ``name`` from dict.

Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to
Expand Down Expand Up @@ -336,7 +341,7 @@ def delenv(self, name: str, raising: bool = True) -> None:
Raises ``KeyError`` if it does not exist, unless ``raising`` is set to
False.
"""
environ: MutableMapping[str, str] = os.environ
environ: Mapping[str, str] = os.environ
adamjstewart marked this conversation as resolved.
Show resolved Hide resolved
self.delitem(environ, name, raising=raising)

def syspath_prepend(self, path) -> None:
Expand Down
18 changes: 18 additions & 0 deletions testing/typing_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
none of the code triggers any mypy errors.
"""
import contextlib
import sys
from typing import Optional

from typing_extensions import assert_type

import pytest
from _pytest.monkeypatch import MonkeyPatch
adamjstewart marked this conversation as resolved.
Show resolved Hide resolved


# Issue #7488.
Expand All @@ -29,6 +31,22 @@ def check_parametrize_ids_callable(func) -> None:
pass


# Issue #10999.
@pytest.mark.skipif(
adamjstewart marked this conversation as resolved.
Show resolved Hide resolved
sys.version_info < (3, 8), reason="TypedDict introduced in Python 3.8"
)
def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None:
from typing import TypedDict

class Foo(TypedDict):
x: int
y: float

a: Foo = {"x": 1, "y": 3.14}
monkeypatch.setitem("x", 2)
monkeypatch.delitem("y")


def check_raises_is_a_context_manager(val: bool) -> None:
with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo:
pass
Expand Down