Skip to main content

A monkey patching library that uses only Python standard library

Project description

Monkey Patching Reliably: PatchyMcPatchFace

A monkey patching library that uses only Python standard library

pip install patchymcpatchface

What patch configuration files you need to create in your project

  1. patch_manifest.py (optional file on project root)
  2. any_other_patch_manifests_of_any_name_you_create.py (placed anywhere in your project)

How a patch module will look like:

def patch_function():
    printout = "I'm the patched function\n"
    print(printout)
    return printout

# define this patch_hook (reserved function name) for patchymcpatchface to pick up
from patchymcpatchface import patch_apply
def patch_hook():  
    # put in the full module ancestry and the patch function as parameters
    # note that you should include the package, module and object ancestry as a string
    patch_apply(
        "patching_example.mypackage.foo.target_function", patch_function 
    )  

Define patch modules in patch_manifest.py on project root and/or similar patch manifest modules placed elsewhere in your source
The patch modules listed in patch_manifest.py on project root will be patched automatically when you import patchymcpatchface To have patches invoke in a delayed manner, or if you would like the patches manifest to be placed else in your project, create other manifest module(s) at any location in your source, that contains a manifest variable of type List[ModuleType] as an export
See the next section below to see how this custom manifest variable should be applied

# import your patch modules here and document them in PATCH_MODULES below
import patching_example.patch_package.baz as baz
import patching_example.patch_package.foobaz as foobaz
from typing import List
from types import ModuleType

# update this list with modules that contain patch_hook
PATCH_MODULES: List[ModuleType] = [
    baz,
]

How to apply patches automatically:

# import patchymcpatchface before imports of other modules that should be patched 
# it automatically invokes all patch hooks when imported
import patchymcpatchface as pf

pf

To delay invocation of certain patches, you may define other patch manifest modules that has an exportable List[ModuleType] variable containing patch modules with the patch_hook defined.
Then, in the point of your code where you would like the patches to be invoked:

import patchymcpatchface as pf 
from where.you.placed.your.custom.manifest.module import YOUR_CUSTOM_PATCH_HOOKS_LIST

...
...
...
# code you are running before you want to invoke the patch
pf.invoke_patch_hooks(YOUR_CUSTOM_PATCH_HOOKS_LIST)
#code you are running before you want to invoke the patch
...
...
...

How this works

https://realpython.com/python-import/#import-internals

To quote real python:

The details of the Python import system are described in the official documentation. At a high level, three things happen when you import a module (or package). The module is:  

- Searched for
- Loaded
- Bound to a namespace

For the usual imports—those done with the import statement—all three steps happen automatically. When you use importlib, however, only the first two steps are automatic. You need to bind the module to a variable or namespace yourself.  

After importing patching_example.mypackage.foo module in the patch module, the imported module will be loaded and bounded to the global namespace with the following keys. Yes, multiple keys for a single module import!

Afterwards, the patch is robust against how the other modules import this function!

filter_sys_modules("patching_example.mypackage"): {'patching_example.mypackage': <module 'patching_example.mypackage' (namespace)>,
                                      'patching_example.mypackage.foo': <module 'patching_example.mypackage.foo' from '/Users/foorx/Developer/python_patching_experiment/patching_example.mypackage/foo.py'>}

Testing various methods of importing the target function to be patched in module foo yields a consistent result:

__main__
Running target_function_direct()
I'm the patched function

__main__
Running patching_example.mypackage.foo.target_function()
I'm the patched function

patching_example.running_package.foo
from patching_example.mypackage.foo import target_function
Running target_function()
I'm the patched function

patching_example.running_package.bar
import patching_example.mypackage
Running patching_example.mypackage.foo.target_function()
I'm the patched function

patching_example.running_package.baz
import patching_example.mypackage.foo
Running patching_example.mypackage.foo.target_function()
I'm the patched function


patching_example.running_package.foobar
from patching_example.mypackage.foo import *
Running target_function()
I'm the patched function

patching_example.running_package.bazbar
import patching_example.mypackage.foo
Running patching_example.mypackage.foo.target_function()
I'm the other patched function

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

patchymcpatchface-0.1.6.tar.gz (4.4 kB view hashes)

Uploaded Source

Built Distribution

patchymcpatchface-0.1.6-py3-none-any.whl (4.8 kB view hashes)

Uploaded Python 3

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