diff options
author | deshevoy <deshevoy@yandex-team.ru> | 2022-02-10 16:46:57 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:46:57 +0300 |
commit | 28148f76dbfcc644d96427d41c92f36cbf2fdc6e (patch) | |
tree | b83306b6e37edeea782e9eed673d89286c4fef35 /contrib/python/pytest/py3/_pytest/monkeypatch.py | |
parent | e988f30484abe5fdeedcc7a5d3c226c01a21800c (diff) | |
download | ydb-28148f76dbfcc644d96427d41c92f36cbf2fdc6e.tar.gz |
Restoring authorship annotation for <deshevoy@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/monkeypatch.py')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/monkeypatch.py | 418 |
1 files changed, 209 insertions, 209 deletions
diff --git a/contrib/python/pytest/py3/_pytest/monkeypatch.py b/contrib/python/pytest/py3/_pytest/monkeypatch.py index e5d6c036bc..a052f693ac 100644 --- a/contrib/python/pytest/py3/_pytest/monkeypatch.py +++ b/contrib/python/pytest/py3/_pytest/monkeypatch.py @@ -1,9 +1,9 @@ """Monkeypatching and mocking functionality.""" -import os -import re -import sys -import warnings -from contextlib import contextmanager +import os +import re +import sys +import warnings +from contextlib import contextmanager from pathlib import Path from typing import Any from typing import Generator @@ -14,101 +14,101 @@ from typing import overload from typing import Tuple from typing import TypeVar from typing import Union - + from _pytest.compat import final -from _pytest.fixtures import fixture +from _pytest.fixtures import fixture from _pytest.warning_types import PytestWarning - -RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") - - + +RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") + + K = TypeVar("K") V = TypeVar("V") -@fixture +@fixture def monkeypatch() -> Generator["MonkeyPatch", None, None]: """A convenient fixture for monkey-patching. - + The fixture provides these methods to modify objects, dictionaries or os.environ:: - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=False) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) - + monkeypatch.setattr(obj, name, value, raising=True) + monkeypatch.delattr(obj, name, raising=True) + monkeypatch.setitem(mapping, name, value) + monkeypatch.delitem(obj, name, raising=True) + monkeypatch.setenv(name, value, prepend=False) + monkeypatch.delenv(name, raising=True) + monkeypatch.syspath_prepend(path) + monkeypatch.chdir(path) + All modifications will be undone after the requesting test function or fixture has finished. The ``raising`` parameter determines if a KeyError or AttributeError will be raised if the set/deletion operation has no target. - """ - mpatch = MonkeyPatch() - yield mpatch - mpatch.undo() - - + """ + mpatch = MonkeyPatch() + yield mpatch + mpatch.undo() + + def resolve(name: str) -> object: # Simplified from zope.dottedname. - parts = name.split(".") - - used = parts.pop(0) - found = __import__(used) - for part in parts: - used += "." + part - try: - found = getattr(found, part) - except AttributeError: - pass - else: - continue + parts = name.split(".") + + used = parts.pop(0) + found = __import__(used) + for part in parts: + used += "." + part + try: + found = getattr(found, part) + except AttributeError: + pass + else: + continue # We use explicit un-nesting of the handling block in order # to avoid nested exceptions. - try: - __import__(used) - except ImportError as ex: - expected = str(ex).split()[-1] - if expected == used: - raise - else: + try: + __import__(used) + except ImportError as ex: + expected = str(ex).split()[-1] + if expected == used: + raise + else: raise ImportError(f"import error in {used}: {ex}") from ex - found = annotated_getattr(found, part, used) - return found - - + found = annotated_getattr(found, part, used) + return found + + def annotated_getattr(obj: object, name: str, ann: str) -> object: - try: - obj = getattr(obj, name) + try: + obj = getattr(obj, name) except AttributeError as e: - raise AttributeError( + raise AttributeError( "{!r} object at {} has no attribute {!r}".format( type(obj).__name__, ann, name ) ) from e - return obj - - + return obj + + def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: if not isinstance(import_path, str) or "." not in import_path: # type: ignore[unreachable] raise TypeError(f"must be absolute import path string, not {import_path!r}") - module, attr = import_path.rsplit(".", 1) - target = resolve(module) - if raising: - annotated_getattr(target, attr, ann=module) - return attr, target - - + module, attr = import_path.rsplit(".", 1) + target = resolve(module) + if raising: + annotated_getattr(target, attr, ann=module) + return attr, target + + class Notset: def __repr__(self) -> str: - return "<notset>" - - -notset = Notset() - - + return "<notset>" + + +notset = Notset() + + @final class MonkeyPatch: """Helper to conveniently monkeypatch attributes/items/environment @@ -121,47 +121,47 @@ class MonkeyPatch: the fixture is not available. In this case, use :meth:`with MonkeyPatch.context() as mp: <context>` or remember to call :meth:`undo` explicitly. - """ - + """ + def __init__(self) -> None: self._setattr: List[Tuple[object, str, object]] = [] self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = ([]) self._cwd: Optional[str] = None self._savesyspath: Optional[List[str]] = None - + @classmethod - @contextmanager + @contextmanager 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. - + Example: - .. code-block:: python - - import functools + .. code-block:: python + + import functools + + def test_partial(monkeypatch): + with monkeypatch.context() as m: + m.setattr(functools, "partial", 3) - def test_partial(monkeypatch): - with monkeypatch.context() as m: - m.setattr(functools, "partial", 3) - - Useful in situations where it is desired to undo some patches before the test ends, - such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples - of this see `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_. - """ + Useful in situations where it is desired to undo some patches before the test ends, + such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples + of this see `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_. + """ m = cls() - try: - yield m - finally: - m.undo() - + try: + yield m + finally: + m.undo() + @overload def setattr( self, target: str, name: object, value: Notset = ..., raising: bool = ..., ) -> None: ... - + @overload def setattr( self, target: object, name: str, value: object, raising: bool = ..., @@ -177,27 +177,27 @@ class MonkeyPatch: ) -> None: """Set attribute value on target, memorizing the old value. - For convenience you can specify a string as ``target`` which - will be interpreted as a dotted import path, with the last part + For convenience you can specify a string as ``target`` which + will be interpreted as a dotted import path, with the last part being the attribute name. For example, - ``monkeypatch.setattr("os.getcwd", lambda: "/")`` - would set the ``getcwd`` function of the ``os`` module. - + ``monkeypatch.setattr("os.getcwd", lambda: "/")`` + would set the ``getcwd`` function of the ``os`` module. + Raises AttributeError if the attribute does not exist, unless ``raising`` is set to False. - """ - __tracebackhide__ = True - import inspect - + """ + __tracebackhide__ = True + import inspect + if isinstance(value, Notset): if not isinstance(target, str): - raise TypeError( - "use setattr(target, name, value) or " - "setattr(target, value) with target being a dotted " - "import string" - ) - value = name - name, target = derive_importpath(target, raising) + raise TypeError( + "use setattr(target, name, value) or " + "setattr(target, value) with target being a dotted " + "import string" + ) + value = name + name, target = derive_importpath(target, raising) else: if not isinstance(name, str): raise TypeError( @@ -205,17 +205,17 @@ class MonkeyPatch: "setattr(target, value) with target being a dotted " "import string" ) - - oldval = getattr(target, name, notset) - if raising and oldval is notset: + + oldval = getattr(target, name, notset) + if raising and oldval is notset: raise AttributeError(f"{target!r} has no attribute {name!r}") - - # avoid class descriptors like staticmethod/classmethod - if inspect.isclass(target): - oldval = target.__dict__.get(name, notset) - self._setattr.append((target, name, oldval)) - setattr(target, name, value) - + + # avoid class descriptors like staticmethod/classmethod + if inspect.isclass(target): + oldval = target.__dict__.get(name, notset) + self._setattr.append((target, name, oldval)) + setattr(target, name, value) + def delattr( self, target: Union[object, str], @@ -223,55 +223,55 @@ class MonkeyPatch: raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. - - If no ``name`` is specified and ``target`` is a string - it will be interpreted as a dotted import path with the - last part being the attribute name. - + + If no ``name`` is specified and ``target`` is a string + it will be interpreted as a dotted import path with the + last part being the attribute name. + Raises AttributeError it the attribute does not exist, unless ``raising`` is set to False. - """ - __tracebackhide__ = True + """ + __tracebackhide__ = True import inspect if isinstance(name, Notset): if not isinstance(target, str): - raise TypeError( - "use delattr(target, name) or " - "delattr(target) with target being a dotted " - "import string" - ) - name, target = derive_importpath(target, raising) - - if not hasattr(target, name): - if raising: - raise AttributeError(name) - else: + raise TypeError( + "use delattr(target, name) or " + "delattr(target) with target being a dotted " + "import string" + ) + name, target = derive_importpath(target, raising) + + if not hasattr(target, name): + if raising: + raise AttributeError(name) + else: oldval = getattr(target, name, notset) # Avoid class descriptors like staticmethod/classmethod. if inspect.isclass(target): oldval = target.__dict__.get(name, notset) self._setattr.append((target, name, oldval)) - delattr(target, name) - + delattr(target, name) + def setitem(self, dic: MutableMapping[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 - + 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: """Delete ``name`` from dict. - + Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to False. - """ - if name not in dic: - if raising: - raise KeyError(name) - else: - self._setitem.append((dic, name, dic.get(name, notset))) - del dic[name] - + """ + if name not in dic: + if raising: + raise KeyError(name) + else: + self._setitem.append((dic, name, dic.get(name, notset))) + del dic[name] + def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: """Set environment variable ``name`` to ``value``. @@ -279,38 +279,38 @@ class MonkeyPatch: value and prepend the ``value`` adjoined with the ``prepend`` character. """ - if not isinstance(value, str): + if not isinstance(value, str): warnings.warn( # type: ignore[unreachable] PytestWarning( - "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, - ) - value = str(value) - if prepend and name in os.environ: - value = value + prepend + os.environ[name] - self.setitem(os.environ, name, value) - + "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, + ) + value = str(value) + if prepend and name in os.environ: + value = value + prepend + os.environ[name] + self.setitem(os.environ, name, value) + def delenv(self, name: str, raising: bool = True) -> None: """Delete ``name`` from the environment. - + Raises ``KeyError`` if it does not exist, unless ``raising`` is set to False. - """ + """ environ: MutableMapping[str, str] = os.environ self.delitem(environ, name, raising=raising) - + def syspath_prepend(self, path) -> None: """Prepend ``path`` to ``sys.path`` list of import locations.""" from pkg_resources import fixup_namespace_packages - if self._savesyspath is None: - self._savesyspath = sys.path[:] - sys.path.insert(0, str(path)) - + if self._savesyspath is None: + self._savesyspath = sys.path[:] + sys.path.insert(0, str(path)) + # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171 fixup_namespace_packages(str(path)) @@ -328,52 +328,52 @@ class MonkeyPatch: def chdir(self, path) -> None: """Change the current working directory to the specified path. - Path can be a string or a py.path.local object. - """ - if self._cwd is None: - self._cwd = os.getcwd() - if hasattr(path, "chdir"): - path.chdir() - elif isinstance(path, Path): + Path can be a string or a py.path.local object. + """ + if self._cwd is None: + self._cwd = os.getcwd() + if hasattr(path, "chdir"): + path.chdir() + elif isinstance(path, Path): # Modern python uses the fspath protocol here LEGACY - os.chdir(str(path)) - else: - os.chdir(path) - + os.chdir(str(path)) + else: + os.chdir(path) + def undo(self) -> None: """Undo previous changes. - + This call consumes the undo stack. Calling it a second time has no effect unless you do more monkeypatching after the undo call. - There is generally no need to call `undo()`, since it is - called automatically during tear-down. - - Note that the same `monkeypatch` fixture is used across a - single test function invocation. If `monkeypatch` is used both by - the test function itself and one of the test fixtures, - calling `undo()` will undo all of the changes made in - both functions. - """ - for obj, name, value in reversed(self._setattr): - if value is not notset: - setattr(obj, name, value) - else: - delattr(obj, name) - self._setattr[:] = [] + There is generally no need to call `undo()`, since it is + called automatically during tear-down. + + Note that the same `monkeypatch` fixture is used across a + single test function invocation. If `monkeypatch` is used both by + the test function itself and one of the test fixtures, + calling `undo()` will undo all of the changes made in + both functions. + """ + for obj, name, value in reversed(self._setattr): + if value is not notset: + setattr(obj, name, value) + else: + delattr(obj, name) + self._setattr[:] = [] for dictionary, key, value in reversed(self._setitem): - if value is notset: - try: + if value is notset: + try: del dictionary[key] - except KeyError: + except KeyError: pass # Was already deleted, so we have the desired state. - else: + else: dictionary[key] = value - self._setitem[:] = [] - if self._savesyspath is not None: - sys.path[:] = self._savesyspath - self._savesyspath = None - - if self._cwd is not None: - os.chdir(self._cwd) - self._cwd = None + self._setitem[:] = [] + if self._savesyspath is not None: + sys.path[:] = self._savesyspath + self._savesyspath = None + + if self._cwd is not None: + os.chdir(self._cwd) + self._cwd = None |