This commit is contained in:
Iliyan Angelov
2025-12-06 03:27:35 +02:00
parent 7667eb5eda
commit 5a8ca3c475
2211 changed files with 28086 additions and 37066 deletions

View File

@@ -1,27 +1,24 @@
# mypy: allow-untyped-defs
"""Monkeypatching and mocking functionality."""
from __future__ import annotations
from collections.abc import Generator
from collections.abc import Mapping
from collections.abc import MutableMapping
from contextlib import contextmanager
import os
from pathlib import Path
import re
import sys
from typing import Any
from typing import final
from typing import overload
from typing import TypeVar
import warnings
from contextlib import contextmanager
from typing import Any
from typing import Generator
from typing import List
from typing import Mapping
from typing import MutableMapping
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TypeVar
from typing import Union
from _pytest.deprecated import MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES
from _pytest.compat import final
from _pytest.fixtures import fixture
from _pytest.warning_types import PytestWarning
RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
@@ -30,7 +27,7 @@ V = TypeVar("V")
@fixture
def monkeypatch() -> Generator[MonkeyPatch]:
def monkeypatch() -> Generator["MonkeyPatch", None, None]:
"""A convenient fixture for monkey-patching.
The fixture provides these methods to modify objects, dictionaries, or
@@ -92,12 +89,14 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object:
obj = getattr(obj, name)
except AttributeError as e:
raise AttributeError(
f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}"
"{!r} object at {} has no attribute {!r}".format(
type(obj).__name__, ann, name
)
) from e
return obj
def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]:
def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]:
if not isinstance(import_path, str) or "." not in import_path:
raise TypeError(f"must be absolute import path string, not {import_path!r}")
module, attr = import_path.rsplit(".", 1)
@@ -130,14 +129,14 @@ class MonkeyPatch:
"""
def __init__(self) -> None:
self._setattr: list[tuple[object, str, object]] = []
self._setitem: list[tuple[Mapping[Any, Any], object, object]] = []
self._cwd: str | None = None
self._savesyspath: list[str] | None = None
self._setattr: List[Tuple[object, str, object]] = []
self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = []
self._cwd: Optional[str] = None
self._savesyspath: Optional[List[str]] = None
@classmethod
@contextmanager
def context(cls) -> Generator[MonkeyPatch]:
def context(cls) -> Generator["MonkeyPatch", None, None]:
"""Context manager that returns a new :class:`MonkeyPatch` object
which undoes any patching done inside the ``with`` block upon exit.
@@ -169,7 +168,8 @@ class MonkeyPatch:
name: object,
value: Notset = ...,
raising: bool = ...,
) -> None: ...
) -> None:
...
@overload
def setattr(
@@ -178,12 +178,13 @@ class MonkeyPatch:
name: str,
value: object,
raising: bool = ...,
) -> None: ...
) -> None:
...
def setattr(
self,
target: str | object,
name: object | str,
target: Union[str, object],
name: Union[object, str],
value: object = notset,
raising: bool = True,
) -> None:
@@ -254,8 +255,8 @@ class MonkeyPatch:
def delattr(
self,
target: object | str,
name: str | Notset = notset,
target: Union[object, str],
name: Union[str, Notset] = notset,
raising: bool = True,
) -> None:
"""Delete attribute ``name`` from ``target``.
@@ -310,7 +311,7 @@ class MonkeyPatch:
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
del dic[name] # type: ignore[attr-defined]
def setenv(self, name: str, value: str, prepend: str | None = None) -> None:
def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
"""Set environment variable ``name`` to ``value``.
If ``prepend`` is a character, read the current environment variable
@@ -320,8 +321,10 @@ class MonkeyPatch:
if not isinstance(value, str):
warnings.warn( # type: ignore[unreachable]
PytestWarning(
f"Value of environment variable {name} type should be str, but got "
f"{value!r} (type: {type(value).__name__}); converted to str implicitly"
"Value of environment variable {name} type should be str, but got "
"{value!r} (type: {type}); converted to str implicitly".format(
name=name, value=value, type=type(value).__name__
)
),
stacklevel=2,
)
@@ -341,6 +344,7 @@ class MonkeyPatch:
def syspath_prepend(self, path) -> None:
"""Prepend ``path`` to ``sys.path`` list of import locations."""
if self._savesyspath is None:
self._savesyspath = sys.path[:]
sys.path.insert(0, str(path))
@@ -348,26 +352,8 @@ class MonkeyPatch:
# https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
# this is only needed when pkg_resources was already loaded by the namespace package
if "pkg_resources" in sys.modules:
import pkg_resources
from pkg_resources import fixup_namespace_packages
# Only issue deprecation warning if this call would actually have an
# effect for this specific path.
if (
hasattr(pkg_resources, "_namespace_packages")
and pkg_resources._namespace_packages
):
path_obj = Path(str(path))
for ns_pkg in pkg_resources._namespace_packages:
if ns_pkg is None:
continue
ns_pkg_path = path_obj / ns_pkg.replace(".", os.sep)
if ns_pkg_path.is_dir():
warnings.warn(
MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES, stacklevel=2
)
break
fixup_namespace_packages(str(path))
# A call to syspathinsert() usually means that the caller wants to
@@ -381,7 +367,7 @@ class MonkeyPatch:
invalidate_caches()
def chdir(self, path: str | os.PathLike[str]) -> None:
def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None:
"""Change the current working directory to the specified path.
:param path: