smart-monkeypatching
Tools to aid in monkeypatching classes. Two decorators:
@add_new_class(ExistingClass)
class MyClass: ...
will (in the module where ExistingClass is defined)
- Create
orig_ExistingClass, as a backup ofExistingClass - Create
new_ExistingClass, as a backup ofMyClass - Make
ExistingClassbenew_ExistingClass(that is,MyClass) fixing__name__and__qualname__betweennew_ExistingClassandorig_ExistingClassas well
Thenrestore_class(ExistingClass)will restore the original, andrestore_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;
- Create
ExistingClass.orig_fn, as a backup ofExistingClass.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 (withrestore_method(ExistingClass.fn)).- Avoids order dependence when patching the same function in a class and one of it's ancestors
- Create
ExistingClass.new_fn, as a backup offn(so you can switch back to the new version if you have restored the old, withrestore_new_method(ExistingClass.fn)). - Fixing
__name__and__qualname__between versions ofExistingClass.fn, so that whetherfnis actuallyorig_fnornew_fn, it's name will befn. - 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. - Specifically deals with condition that
ExistingClass.fnwas actually defined in a parent class ofExistingClass, as we don't want to change the parent class when adjusting names, by copyingfnintoExistingClass. - Specifically deals with condition that
ExistingClass.fnis abuiltin, and can't be copied in the same way (or modified at all, even__name__) - Specifically deals with condition that
ExistingClass.fnis a Descriptor type inherited from a parent (and__code__cannot be accessed for copying). - Modifies the
__doc__to ensure mention of the fact that the function is monkeypatched. - in submodule util there is a specific function to "copy" class methods so that they will have the same interface.
- Added a context manager
PatchingContextwhich 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)
| File | Size | Uploaded | |
|---|---|---|---|
| smart_monkeypatching-1.5.1.tar.gz | 31.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|