Skip to main content

smart-monkeypatching

Tools to aid in monkeypatching classes. Two decorators:

@add_new_class(ExistingClass)
class MyClass: ...

will (in the module where ExistingClass is defined)

  1. Create orig_ExistingClass, as a backup of ExistingClass
  2. Create new_ExistingClass, as a backup of MyClass
  3. Make ExistingClass be new_ExistingClass (that is, MyClass) fixing __name__ and __qualname__ between new_ExistingClass and orig_ExistingClass as well
    Then restore_class(ExistingClass) will restore the original, and restore_new_class(ExistingClass) will put back the new version again.
@add_to_class(ExistingClass)
def fn():...

or

@add_to_class(ExistingClass)
def new_fn():...

Both will (in ExistingClass) add a method fn. If fn already exists, the the original is backed up as orig_fn. new_fn is created and names are fixed as for @add_new_class.

@add_to_class can be combined with one of @classmethod, @staticmethod, or @property (and if property, then fn in the local namespace will be the property, so you can then do

@add_to_class(ExistingClass)
@fn@setter
def fn():...

This takes care of the following potential issues;

  1. Create ExistingClass.orig_fn, as a backup of ExistingClass.fn. This way you can delegate part of the work back to the original function (calling orig_fn in your code), or undo the monkeypatching (with restore_method(ExistingClass.fn)).
    1. Avoids order dependence when patching the same function in a class and one of it's ancestors
  2. Create ExistingClass.new_fn, as a backup of fn (so you can switch back to the new version if you have restored the old, with restore_new_method(ExistingClass.fn)).
  3. Fixing __name__ and __qualname__ between versions of ExistingClass.fn, so that whether fn is actually orig_fn or new_fn, it's name will be fn.
  4. Specifically deals with special issues for @classmethod, @staticmethod, and @property. If the function you are replacing is a classmethod, staticmethod, or property, automatically modifies your function appropriately.
  5. Specifically deals with condition that ExistingClass.fn was actually defined in a parent class of ExistingClass, as we don't want to change the parent class when adjusting names, by copying fn into ExistingClass.
  6. Specifically deals with condition that ExistingClass.fn is a builtin, and can't be copied in the same way (or modified at all, even __name__)
  7. Specifically deals with condition that ExistingClass.fn is a Descriptor type inherited from a parent (and __code__ cannot be accessed for copying).
  8. Modifies the __doc__ to ensure mention of the fact that the function is monkeypatched.
  9. in submodule util there is a specific function to "copy" class methods so that they will have the same interface.
  10. Added a context manager PatchingContext which undoes patches when the context exits (even if due to an exception).
from smart_monkeypatching.context import PatchingContext
# Assuming
class C:
    def fn1(self, a:int) -> float: ...
    def fn2(self, a:int) -> float: ...
	pass

# then you can do
with PatchingContext() as cm :
    @add_to_class(C, cm)
	def fn1(self, a:int) -> float: ...
	
    @add_to_class(C, cm)
	def fn2(self, a:int) -> float: ...

	# Do stuff with modified C
	
	pass
# Now C is restored, even if an exception occurred

You should know that monkeypatching will wreack havoc with mypy, since it does stuff you shouldn't generally do. You can get an idea of the kind of mypy issues that arise by checking out the test code # test: ignore[] annotations.

For examples, use help(...), and/or download the source package and look at the tests.

The library can be built with hatchling build, tested with tests/test_tools.py -v, and should pass mypy.

Metadata

Release files for smart-monkeypatching 1.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for smart-monkeypatching 1.5.1
File Size Uploaded
smart_monkeypatching-1.5.1.tar.gz 31.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for smart-monkeypatching 1.5.1
File Interpreter ABI Platform
smart_monkeypatching-1.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 62.2 kB

Release files / smart_monkeypatching-1.5.1.tar.gz

Download URL smart_monkeypatching-1.5.1.tar.gz
Size 31.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0ea44de0fc59cc8321999fcd7ee9db4374a2fca07496b1771d747bc671e6e10a
BLAKE2b-256 checksum
How to use checksums
61d1efde1b3ac060422e4796623dc8895417ba7ce145ab439610753c665d77cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / smart_monkeypatching-1.5.1-py3-none-any.whl

Download URL smart_monkeypatching-1.5.1-py3-none-any.whl
Size 30.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
505a51bdc9af2d9f39a9921ea8071615cd52401da9a8c8683e6acbec671266eb
BLAKE2b-256 checksum
How to use checksums
83c7fc4584b6e0e0adf3ff2d39f083bd94a0a7903003eb745fcdadf898f71507
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

1.5.1 This release

2 release files

1.5.0

2 release files

1.4.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page