monkeypatch.py
Overview
The [monkeypatch.py](/projects/286/67458) file provides functionality for **monkeypatching**—a technique widely used in testing to dynamically modify or replace attributes, dictionary items, environment variables, or system paths during test execution. This enables tests to isolate behavior by mocking dependencies, altering environment conditions, or temporarily changing the runtime state without permanent side effects.
The core offering is the `MonkeyPatch` class, which tracks all modifications and supports automatic rollback (undo) of changes after test completion. This functionality is exposed as a pytest fixture named `monkeypatch` and can also be used as a standalone helper class.
Main Entities and Their Functionality
1. monkeypatch Fixture
Type: Pytest fixture
Purpose: Provides a convenient and safe way to apply monkeypatching within tests.
Usage: Automatically yields a
MonkeyPatchinstance; all changes are undone after the test.
**Example:**
def test_os_getcwd(monkeypatch):
monkeypatch.setattr("os.getcwd", lambda: "/tmp")
assert os.getcwd() == "/tmp"
# After test, original os.getcwd is restored automatically
2. Utility Functions
resolve(name: str) -> object
Resolves a dotted import path string (e.g.,
"os.path.join") to the actual Python object.Handles nested attribute imports with error handling for import failures.
**Usage:**
obj = resolve("os.path.join")
result = obj("a", "b") # Calls os.path.join("a", "b")
annotated_getattr(obj: object, name: str, ann: str) -> object
A safer
getattrthat raises a detailedAttributeErrorincluding the annotation (usually the module or import path).
derive_importpath(import_path: str, raising: bool) -> tuple[str, object]
Splits a dotted import path into
(attribute_name, target_object).Ensures the module and attribute exist (depending on
raisingflag).
3. Sentinel Class: Notset
Represents a unique sentinel value indicating "no value set".
Used internally to track prior attribute or dict item states.
notset = Notset()
4. Class: MonkeyPatch
A `final` class that provides methods to monkeypatch:
Attributes on objects or modules
Items in dictionaries or mappings
Environment variables (
os.environ)Python import path (
sys.path)Current working directory (
os.getcwd())
It tracks all changes and can undo them reliably.
Constructor: __init__(self)
Initializes internal stacks to remember patched attributes and items.
Prepares storage for original system path and working directory.
Context Manager: MonkeyPatch.context()
Returns a new
MonkeyPatchinstance as a context manager.Automatically undoes all patches upon context exit.
**Example:**
import functools
with MonkeyPatch.context() as m:
m.setattr(functools, "partial", lambda x: x)
# patches active here
# patches undone here
Method: setattr
setattr(
target: str | object,
name: object | str,
value: object = notset,
raising: bool = True,
) -> None
Purpose: Monkeypatch an attribute on a target object or module.
Parameters:
target: Either an object or a string representing a full dotted import path (e.g.,"os.getcwd").name: The attribute name (iftargetis an object) or the value to set (iftargetis a string import path).value: The new value to assign to the attribute.raising: Whether to raiseAttributeErrorif the attribute does not exist.
Behavior:
Supports syntax sugar: when
targetis a string,nameis treated as the value, and the last component oftargetis inferred as the attribute name.Saves original attribute value or sentinel if not set.
Applies the new attribute value.
Raises:
AttributeErrorifraising=Trueand attribute does not exist.
**Example:**
monkeypatch.setattr(os, "getcwd", lambda: "/tmp")
# or equivalently
monkeypatch.setattr("os.getcwd", lambda: "/tmp")
Method: delattr
delattr(
target: object | str,
name: str | Notset = notset,
raising: bool = True,
) -> None
Deletes an attribute from an object or module.
When
nameis omitted andtargetis a string,targetis interpreted as a dotted import path.Stores the deleted attribute value to allow undo.
Raises
AttributeErrorif the attribute does not exist andraising=True.
Method: setitem
setitem(self, dic: Mapping[K, V], name: K, value: V) -> None
Sets a key-value pair in a dictionary or mapping.
Stores the original value or sentinel for undo.
Method: delitem
delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None
Deletes a key from a dictionary or mapping.
Raises
KeyErrorif key is missing andraising=True.Stores the original value for undo.
Method: setenv
setenv(self, name: str, value: str, prepend: str | None = None) -> None
Sets an environment variable
nametovalueinos.environ.If
prependis provided, prepends the new value plus the separator character to the existing environment variable value.Converts non-string values to string with a warning.
Method: delenv
delenv(self, name: str, raising: bool = True) -> None
Deletes an environment variable.
Raises
KeyErrorif missing andraising=True.
Method: syspath_prepend
syspath_prepend(self, path) -> None
Inserts
pathat the front ofsys.path.Saves original
sys.pathto restore later.Calls
pkg_resources.fixup_namespace_packagesif needed.Invalidates import caches to ensure new paths are recognized.
Method: chdir
chdir(self, path: str | os.PathLike[str]) -> None
Changes the current working directory to
path.Saves original working directory for restoration.
Method: undo
undo(self) -> None
Reverts all monkeypatch changes recorded:
Restores attributes and deletes newly added attributes.
Restores dictionary items or deletes newly added keys.
Restores
sys.pathto original state.Restores current working directory.
The undo stack is cleared after this operation.
Usually invoked automatically at test teardown; manual use is optional.
Important Implementation Details
Tracking Changes:
MonkeyPatchmaintains two internal lists:_setattr: List of(object, attribute_name, original_value)tuples._setitem: List of(mapping, key, original_value)tuples.
These enable exact reversal of changes.
Sentinel Value
notset:
Used to detect whether an attribute or dict key originally existed.Import Path Resolution:
Methods support patching via string import paths (e.g.,"os.path.join"). Theresolvefunction dynamically imports and traverses attributes, providing informative error messages.Safe Undoing:
Theundomethod carefully reverses changes in reverse order of application to avoid conflicts, e.g., restoring attributes before removing new keys.Integration with pytest:
Themonkeypatchfixture integrates tightly with pytest's lifecycle, automatically undoing patches after test execution to prevent side-effects leaking between tests.
Interaction with Other System Components
pytest Framework:
This module is designed as a plugin-like fixture for pytest, providing test authors a reliable monkeypatching API.Python Standard Library:
Interacts heavily with:osmodule (environment variables, working directory)sysmodule (sys.pathmanagement)importlib(cache invalidation)collections.abc(typing for mappings)
pkg_resourcesmodule:
When patchingsys.path, it callsfixup_namespace_packagesto maintain namespace package integrity.Warnings System:
Emits warnings when environment variables are set with non-string types.
Usage Summary
def test_example(monkeypatch):
# Patch an attribute on an object
monkeypatch.setattr("os.getcwd", lambda: "/tmp")
# Patch a dict item
monkeypatch.setitem(os.environ, "MY_ENV", "value")
# Delete an attribute
monkeypatch.delattr(os.path, "join")
# Change environment variable with prepending
monkeypatch.setenv("PATH", "/my/path", prepend=":")
# Prepend a directory to sys.path
monkeypatch.syspath_prepend("/my/project")
# Change current working directory
monkeypatch.chdir("/tmp")
# Undo all changes manually (optional)
# monkeypatch.undo()
Mermaid Class Diagram
classDiagram
class MonkeyPatch {
-_setattr: list[tuple[object, str, object]]
-_setitem: list[tuple[Mapping, object, object]]
-_cwd: str | None
-_savesyspath: list[str] | None
+__init__()
+setattr(target: str|object, name: object|str, value: object=notset, raising: bool=True) void
+delattr(target: object|str, name: str|Notset=notset, raising: bool=True) void
+setitem(dic: Mapping, name: object, value: object) void
+delitem(dic: Mapping, name: object, raising: bool=True) void
+setenv(name: str, value: str, prepend: str | None=None) void
+delenv(name: str, raising: bool=True) void
+syspath_prepend(path: object) void
+chdir(path: str | os.PathLike) void
+undo() void
+context() contextmanager[MonkeyPatch]
}
class Notset {
+__repr__() str
}
MonkeyPatch --> Notset : uses
Summary
This file is a core utility in pytest's ecosystem enabling safe, reversible monkeypatching for testing. It abstracts complex patching logic, import resolution, and rollback management behind a straightforward API. By supporting patching for attributes, dictionary entries, environment variables, and system paths, it provides comprehensive control over the test environment, improving test isolation and reliability.