This package is designed for temporary, recursive monkeypatching. That is to replace an object within a module with another object, across the entire module hierarchy. With this package, you can temporarily change a deeply nested behaviour in a module across the whole module.
Python modules are often structured to import functions from nested submodules. If you need to monkeypatch a function, it's often not sufficient to simply patch it at its function definition, since the function can persist from other import expressions. For example, if module.submodule.function is used within module.othersubmodule and is imported as:
from .submoduleimportfunctionPatching module.submodule.function won't replace module.othersubmodule.function and won't affect its usage. Recursive monkeypatching, however, ensures that the object is replaced throughout the module hierarchy.
Monkeypatching can introduce ambiguity and result in unpredictable behavior. When you modify an object in an imported module, this change affects not only the current scope but also any other modules that rely on it. Such alterations can lead to unintended consequences and complicate system maintenance and debugging.
However, in the context of temporary monkeypatching, you can alter a module's behavior for a specific scope without affecting the underlying implementation. This is analogous to class inheritance, where a subclass can extend functionality without modifying the parent class.
With temporary monkeypatching, as soon as you exit the context (e.g., a with block), the original behavior is restored, minimizing the chances of unintended ripple effects across your codebase.
To install Monkeypatching, use:
pip install monkeypatchingSpecific attributes within a module can temporarily be replaced using monkeypatch_setattr:
frommonkeypatchingimportmonkeypatch_setattrimportjsondefmock_loads(data, *args, **kwargs):
return {"mocked": True}
withmonkeypatch_setattr(json, "loads", mock_loads):
print(json.loads('{"key": "value"}')) # Output: {"mocked": True}An example of temporary monkeypatching:
frommonkeypatchingimportmonkeypatch_module_objectimportjsonfromjsonimportdumpsdefmock_dumps(data, *args, **kwargs):
kwargs['indent'] =4# Force indentation to 4 charactersreturndumps(data, *args, **kwargs)
# Monkeypatch json.dumps in the json module and submoduleswithmonkeypatch_module_object(json, json.dumps, mock_dumps):
print(json.dumps({"key": "value"})) # Output: # {# "key": "value"# }# After the block, json.dumps reverts to its originalprint(json.dumps({"key": "value"})) # Output: {"key": "value"}For a lasting effect:
frommonkeypatchingimportmonkeypatch_module_objectimportjsonmonkeypatch_module_object(json, json.dumps, mock_dumps)
print(json.dumps({"key": "value"})) # Output: # {# "key": "value"# }For repeated monkeypatching at identical locations:
# With caching enabledwithmonkeypatch_module_object(json, json.dumps, mock_dumps, cached=True):
print(json.dumps({"key": "value"})) # Output: # {# "key": "value"# }"Caution: Activate caching only if monkeypatch locations remain consistent and the object hasn't been replaced elsewhere.
You can override global variables of a function during the execution of that function call.
frommonkeypatchingimportoverride_globals_during_function_callpi=3classPretendModule:
@staticmethoddefget_tau():
return2*pi# Override 'pi' during a call to 'get_tau'withoverride_globals_during_function_call(PretendModule, 'get_tau', {'pi': 3.14159265358979}):
print(PretendModule.get_tau())
# Output: 6.28318530717958print(PretendModule.get_tau())
# Output: 3module: Target module.obj_original: The object that will be replaced.obj_replacement: The object that will replaceobj_original.cached: Whether to cache the monkeypatch locations; default isFalse.
A context manager. Within a with block, obj_original gets temporarily replaced by obj_replacement across the specified module and submodules. Outside a with block, the replacement is permanent.
module: Target module.attr_name: Attribute to be set viasetattr(module, attr_name, obj_replacement).obj_replacement: Replacement object.
A context manager. Within a with block, the specific attribute in the module is replaced by obj_replacement. Outside a with block, the replacement is permanent and identical to writing setattr(module, attr_name, obj_replacement).
override_globals_during_function_call(module, function_name, replacements, module_wide = False, module_wide_cached = False)
module: Target module.function_name: The name of the function within the module.replacements: A dictionary or object defining the global variables to be overridden.module_wide: If set to True, the override acts asmonkeypatch_module_object, otherwise asmonkeypatch_setattr.module_wide_cached: Whether to cache the monkeypatch locations; default isFalse.
A context manager. Within a with block, obj_original gets temporarily replaced by obj_replacement across the specified module and submodules. Outside a with block, the replacement is permanent.
Contributions are always welcome! For bugs, features, or documentation updates, please open an issue or submit a pull request.
This project is under the MIT License.
Thank you for using Monkeypatching! For more information or queries, raise an issue or reach out with a pull request.