Skip to main content

tools to carefully monkeypatch classes

Project description

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.

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

smart_monkeypatching-1.5.1.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smart_monkeypatching-1.5.1-py3-none-any.whl (30.2 kB view details)

Uploaded Python 3

File details

Details for the file smart_monkeypatching-1.5.1.tar.gz.

File metadata

  • Download URL: smart_monkeypatching-1.5.1.tar.gz
  • Upload date:
  • Size: 31.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for smart_monkeypatching-1.5.1.tar.gz
Algorithm Hash digest
SHA256 0ea44de0fc59cc8321999fcd7ee9db4374a2fca07496b1771d747bc671e6e10a
MD5 31eacf613fb223ade484c3ebf2a30ff0
BLAKE2b-256 61d1efde1b3ac060422e4796623dc8895417ba7ce145ab439610753c665d77cd

See more details on using hashes here.

File details

Details for the file smart_monkeypatching-1.5.1-py3-none-any.whl.

File metadata

File hashes

Hashes for smart_monkeypatching-1.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 505a51bdc9af2d9f39a9921ea8071615cd52401da9a8c8683e6acbec671266eb
MD5 f0499ec1ec02c25a64a87032bc699bbc
BLAKE2b-256 83c7fc4584b6e0e0adf3ff2d39f083bd94a0a7903003eb745fcdadf898f71507

See more details on using hashes here.

Supported by

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