Package: collective.monkeypatcher
IntroductionSometimes, 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 theinitialize()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.InstallationTo installcollective.monkeypatcherinto 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 runpip install collective.monkeypatcher.Create a file calledcollective.monkeypatcher-configure.zcmlin the/path/to/instance/etc/package-includesdirectory.  The file
should only contain this:<include package="collective.monkeypatcher" />Alternatively, if you are usingzc.buildoutand theplone.recipe.zope2instancerecipe to manage your project, you can do this:Addcollective.monkeypatcherto the list of eggs to install, e.g.:[buildout]
...
eggs =
    ...
    collective.monkeypatcherTell the plone.recipe.zope2instance recipe to install a ZCML slug:[instance]
recipe = plone.recipe.zope2instance
...
zcml =
    collective.monkeypatcherRe-run buildout, e.g. with:$ ./bin/buildoutYou can skip the ZCML slug if you are going to explicitly include the package
from another package’s configure.zcml file.Applying a monkey patchHere’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 usemoduleinstead ofclass. 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 theorderattribute to
specify a higher (later) or lower (earlier) number. The default is 1000.By default,DocFinderTaband 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 thedocstringWarningattribute tofalse.If you want to do more than just replace one function with another, you can
provide your own patcher function via thehandlerattribute. This should
be a callable like:def apply_patch(scope, original, replacement):
    ...Here,scopeis the class/module that was specified.originalis the string
name of the function to replace, andreplacementis the replacement function.Full list of options:classThe class being patchedmoduleThe module being patched (seePatching module level functions)handlerA function to perform the patching. Must take three parameters: class/module, original (string), and replacementoriginalMethod or function to replacereplacementMethod or function to replace withpreservedocPreserve docstrings?preserveOriginalPreserve the original function so that it is reachable view prefix _old_. Only works for default handlerpreconditionsPreconditions (multiple, separated by space) to be satisified before applying this patch. Example: Products.LinguaPlone-=1.4.3 or Products.TextIndexNG3+=3.3.0ignoreOriginalIgnore if the orginal function isn’t present on the class/module being patcheddocstringWarningAdd monkey patch warning in docstringdescriptionSome comments about your monkey patchorderExecution orderHandling monkey patches eventsApplying a monkey patch fires an event. See theinterfaces.pymodule. 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 functionsAttention!Be aware that patching module level functions will likely not work.If you want to patch the methoddo_somethinglocated inpatched.package.utilswhich is imported in a package like thisfrom patched.package.utils import do_somethingthe reference to this function is loadedbeforecollective.monkeypatcherwill patch the original method.See alsothis related thread on the plone mailing list.WorkaroundDo the patching in__init__.pyof your package:from patched.package import utils

def do_it_different():
    return 'foo'

utils.do_something = do_it_differentChangelog1.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-10Fix the case where the replacement object does not have a __module__
attribute (seehttps://github.com/plone/collective.monkeypatcher/pull/3).
[mitchellrj, fRiSi]1.0.1 - 2011-01-25Downgrade standard log message to debug level.
[hannosch]1.0 - 2010-07-01Avoid 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-18Add 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-17Fires 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-29Initial release
[optilude]