Metadata-Version: 2.4
Name: smart-monkeypatching
Version: 1.5.0
Summary: tools to carefully monkeypatch classes
Project-URL: repository, https://codeberg.org/Pusher2531/smart-monkeypatching.git
Project-URL: issues, https://codeberg.org/Pusher2531/smart-monkeypatching/issues
Author-email: Doug Sojourner <doug@sojournings.org>
License: GNU General Public License v3 (GPLv3)
License-File: LICENSE.md
Keywords: class modification,debug,debuging,decorator,monkeypatching,typed
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: <4.0,>=3.12.11
Requires-Dist: objwatch
Requires-Dist: packaging
Requires-Dist: project-version-finder
Description-Content-Type: text/markdown

### `smart-monkeypatching`
Tools to aid in monkeypatching classes. Two decorators:
```python
@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.  

```python
@add_to_class(ExistingClass)
def fn():...
```
or
```python
@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  
```python
@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)`).  
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`, so 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 note 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.

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