Skip to main content

A Python package designed for temporary recursive monkeypatching.

Project description

Monkeypatching

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.

Why recursive monkeypatching?

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 .submodule import function

Patching 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.

Why temporary monkeypatching?

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.

Installation

To install Monkeypatching, use:

pip install monkeypatching

Usage

Temporary attribute replacement

Specific attributes within a module can temporarily be replaced using monkeypatch_setattr:

from monkeypatching import monkeypatch_setattr
import json

def mock_loads(data, *args, **kwargs):
    return {"mocked": True}

with monkeypatch_setattr(json, "loads", mock_loads):
    print(json.loads('{"key": "value"}')) 
    # Output: {"mocked": True}

Temporary module patching

An example of temporary monkeypatching:

from monkeypatching import monkeypatch_module_object
import json
from json import dumps

def mock_dumps(data, *args, **kwargs):
    kwargs['indent'] = 4  # Force indentation to 4 characters
    return dumps(data, *args, **kwargs)

# Monkeypatch json.dumps in the json module and submodules
with monkeypatch_module_object(json, json.dumps, mock_dumps):
    print(json.dumps({"key": "value"}))  
    # Output: 
    # {
    #     "key": "value"
    # }

# After the block, json.dumps reverts to its original
print(json.dumps({"key": "value"})) 
# Output: {"key": "value"}

Permanent module patching

For a lasting effect:

from monkeypatching import monkeypatch_module_object
import json

monkeypatch_module_object(json, json.dumps, mock_dumps)
print(json.dumps({"key": "value"}))  
# Output: 
# {
#     "key": "value"
# }

Caching

For repeated monkeypatching at identical locations:

# With caching enabled
with monkeypatch_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.

Overriding globals during function call

You can override global variables of a function during the execution of that function call.

from monkeypatching import override_globals_during_function_call

pi = 3

class PretendModule:
    @staticmethod
    def get_tau():
        return 2 * pi

# Override 'pi' during a call to 'get_tau'
with override_globals_during_function_call(PretendModule, 'get_tau', {'pi': 3.14159265358979}):
    print(PretendModule.get_tau())
    # Output: 6.28318530717958

print(PretendModule.get_tau())
# Output: 3

API documentation

monkeypatch_module_object(module, obj_original, obj_replacement, cached=False)

Parameters

  • module: Target module.
  • obj_original: The object that will be replaced.
  • obj_replacement: The object that will replace obj_original.
  • cached: Whether to cache the monkeypatch locations; default is False.

Returns

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.

monkeypatch_setattr(module, attr_name, obj_replacement)

Parameters

  • module: Target module.
  • attr_name: Attribute to be set via setattr(module, attr_name, obj_replacement).
  • obj_replacement: Replacement object.

Returns

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)

Parameters

  • 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 as monkeypatch_module_object, otherwise as monkeypatch_setattr.
  • module_wide_cached: Whether to cache the monkeypatch locations; default is False.

Returns

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.

Contributing

Contributions are always welcome! For bugs, features, or documentation updates, please open an issue or submit a pull request.

License

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

monkeypatching-0.2.0.tar.gz (11.5 kB view details)

Uploaded Source

Built Distribution

monkeypatching-0.2.0-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

File details

Details for the file monkeypatching-0.2.0.tar.gz.

File metadata

  • Download URL: monkeypatching-0.2.0.tar.gz
  • Upload date:
  • Size: 11.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.9.19

File hashes

Hashes for monkeypatching-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2ccf764f7fb47ae535d2f2c84e877d1af9d74fcc03f528dde9fe7a508d963c0c
MD5 59f1372247d0467e15db808016c17ad1
BLAKE2b-256 9750da13b8963726da1d320e52485566280f5947978603c7f88c42e52b1829cd

See more details on using hashes here.

File details

Details for the file monkeypatching-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for monkeypatching-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6cc6d9002e57050bf9885e20e6602d20713d054eec123c98f297f17548037d37
MD5 0572d9ca8844863ba6dfd88b09499722
BLAKE2b-256 b845deec36cb300891d8fe6aebe8aef4457d87a96b5baea50c929b4758706492

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page