Skip to main content

Support for applying monkey patches late in the startup cycle by using ZCML configuration actions

Project description

https://travis-ci.com/plone/collective.monkeypatcher.svg?branch=master https://coveralls.io/repos/github/zopefoundation/collective.monkeypatcher/badge.svg?branch=master Current version on PyPI Supported Python versions

Introduction

Sometimes, a monkey patch is a necessary evil.

This package makes it easier to apply a monkey patch during Zope startup. It uses the ZCML configuration machinery to ensure that patches are loaded “late” in the startup cycle, so that the original code has had time to be fully initialised and configured. This is similar to using the initialize() method in a product’s __init__.py, except it does not require that the package be a full-blown Zope product with a persistent Control_Panel entry.

Installation

To install collective.monkeypatcher into the global Python environment (or a working environment), using a traditional Zope instance, you can do this:

  • When you’re reading this you have probably already run pip install collective.monkeypatcher.

  • Create a file called collective.monkeypatcher-configure.zcml in the /path/to/instance/etc/package-includes directory. The file should only contain this:

    <include package="collective.monkeypatcher" />

Alternatively, if you are using zc.buildout and the plone.recipe.zope2instance recipe to manage your project, you can do this:

  • Add collective.monkeypatcher to the list of eggs to install, e.g.:

    [buildout]
    ...
    eggs =
        ...
        collective.monkeypatcher
  • Tell the plone.recipe.zope2instance recipe to install a ZCML slug:

    [instance]
    recipe = plone.recipe.zope2instance
    ...
    zcml =
        collective.monkeypatcher
  • Re-run buildout, e.g. with:

    $ ./bin/buildout

You can skip the ZCML slug if you are going to explicitly include the package from another package’s configure.zcml file.

Applying a monkey patch

Here’s an example:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:monkey="http://namespaces.plone.org/monkey"
    i18n_domain="collective.monkeypatcher">

    <include package="collective.monkeypatcher" />

    <monkey:patch
        description="This works around issue http://some.tracker.tld/ticket/123"
        class="Products.CMFPlone.CatalogTool.CatalogTool"
        original="searchResults"
        replacement=".catalog.patchedSearchResults"
        />

</configure>

In this example, we patch Plone’s CatalogTool’s searchResults() function, replacing it with our own version in catalog.py. To patch a module level function, you can use module instead of class. The original class and function/method name and the replacement symbol will be checked to ensure that they actually exist.

If patching happens too soon (or too late), use the order attribute to specify a higher (later) or lower (earlier) number. The default is 1000.

By default, DocFinderTab and other TTW API browsers will emphasize the monkey patched methods/functions, appending the docstring with “Monkey patched with ‘my.monkeypatched.function’”. If you don’t want this, you could set the docstringWarning attribute to false.

If you want to do more than just replace one function with another, you can provide your own patcher function via the handler attribute. This should be a callable like:

def apply_patch(scope, original, replacement):
    ...

Here, scope is the class/module that was specified. original is the string name of the function to replace, and replacement is the replacement function.

Full list of options:

  • class The class being patched

  • module The module being patched (see Patching module level functions)

  • handler A function to perform the patching. Must take three parameters: class/module, original (string), and replacement

  • original Method or function to replace

  • replacement Method or function to replace with

  • preservedoc Preserve docstrings?

  • preserveOriginal Preserve the original function so that it is reachable view prefix _old_. Only works for default handler

  • preconditions Preconditions (multiple, separated by space) to be satisfied before applying this patch. Example: Products.LinguaPlone-=1.4.3 or Products.TextIndexNG3+=3.3.0

  • ignoreOriginal Ignore if the original function isn’t present on the class/module being patched

  • docstringWarning Add monkey patch warning in docstring

  • description Some comments about your monkey patch

  • order Execution order

Handling monkey patches events

Applying a monkey patch fires an event. See the interfaces.py module. If you to handle such event add this ZCML bunch:

...
<subscriber
  for="collective.monkeypatcher.interfaces.IMonkeyPatchEvent"
  handler="my.component.events.myHandler"
  />
...

And add such Python:

def myHandler(event):
    """see collective.monkeypatcher.interfaces.IMonkeyPatchEvent"""
    ...

Patching module level functions

If you want to patch the method do_something located in patched.package.utils which is imported in a package like this

from patched.package.utils import do_something

the reference to this function is loaded before collective.monkeypatcher will patch the original method.

See also this related thread on the plone mailing list.

Workaround

Do the patching in __init__.py of your package:

from patched.package import utils

def do_it_different():
    return 'foo'

utils.do_something = do_it_different

Changelog

3.0.0 (2026-05-14)

Internal:

  • Make final release, no further changes.

3.0.0a1 (2025-12-09)

Breaking changes:

  • Replace pkg_resources namespace with PEP 420 native namespace. Support only Plone 6.2 and Python 3.10+. (#3928)

2.0.1 (2025-09-24)

Internal:

  • Update configuration files. [plone devs]

2.0.0 (2025-03-21)

Breaking changes:

  • Drop support for Plone 5.2 and for Python 3.8 and lower. @gforcada (#4126)

Bug fixes:

  • Replace pkg_resources with importlib.metadata @gforcada @mauritsvanrees (#4126)

Internal:

  • Update configuration files. [plone devs]

1.2.2 (2024-11-30)

Bug fixes:

  • Fix removed unittest.makeSuite in python 3.13. [petschki] (#14)

1.2.1 (2020-03-21)

Bug fixes:

  • Minor packaging updates. [various] (#1)

1.2 (2018-12-10)

New features:

  • Include installation instructions in the README.

  • Update test infrastructure.

1.1.6 (2018-10-31)

Bug fixes:

  • Prepare for Python 2 / 3 compatibility [frapell]

1.1.5 (2018-06-18)

Bug fixes:

  • Fix import for Python 3 in the tests module [ale-rt]

1.1.4 (2018-04-08)

Bug fixes:

  • Fix import for Python 3 [pbauer]

1.1.3 (2017-11-26)

New features:

  • Document possible problems when patching module level functions [frisi]

1.1.2 (2016-08-10)

Fixes:

  • Use zope.interface decorator. [gforcada]

1.1.1 (2015-03-27)

  • Fix typo. [gforcada]

1.1 - 2014-12-10

1.0.1 - 2011-01-25

  • Downgrade standard log message to debug level. [hannosch]

1.0 - 2010-07-01

  • Avoid a zope.app dependency. [hannosch]

  • Added new parameter preconditions that only patches if preconditions are met like version of a specific package. [spamsch]

  • Added new parameter preserveOriginal. Setting this to true makes it possible to access the patched method via _old_``name of patched method`` [spamsch]

1.0b2 - 2009-06-18

  • Add the possibility to ignore the error if the original function isn’t present on the class/module being patched [jfroche]

  • Check if the docstring exists before changing it [jfroche]

  • Add buildout.cfg for test & test coverage [jfroche]

1.0b1 - 2009-04-17

  • Fires an event when a monkey patch is applied. See interfaces.py. [glenfant]

  • Added ZCML attributes “docstringWarning” and “description”. [glenfant]

  • Added unit tests. [glenfant]

1.0a1 - 2009-03-29

  • Initial release [optilude]

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

collective_monkeypatcher-3.0.0.tar.gz (17.0 kB view details)

Uploaded Source

Built Distribution

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

collective_monkeypatcher-3.0.0-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file collective_monkeypatcher-3.0.0.tar.gz.

File metadata

  • Download URL: collective_monkeypatcher-3.0.0.tar.gz
  • Upload date:
  • Size: 17.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for collective_monkeypatcher-3.0.0.tar.gz
Algorithm Hash digest
SHA256 f6175c1612a40f2683be41c1d38f1ddccffd79adc432e1ccdbf8dcc62d26bfb0
MD5 dd9f99cda079adbcb8f86095b59f20af
BLAKE2b-256 bb998e82d98daa100b48652f70a27072c691d16335fa252b78988bf5bff10a8e

See more details on using hashes here.

File details

Details for the file collective_monkeypatcher-3.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for collective_monkeypatcher-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cde7dc584d69bd10493c4698cdbe3a1b4ad8fd3f3897ee6e6ea8ebb4ca3cf138
MD5 756fd0d299a138a1f6fd3e514ac091a7
BLAKE2b-256 5fa6dbd6723efe9b19f11db47343e5e68c1f31d5d7b37c0b2bfb6bd60a441f4f

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