This is a pre-production deployment of Warehouse, however changes made here WILL affect the production instance of PyPI.
Latest Version Dependencies status unknown Test status unknown Test coverage unknown
Project Description

Patch the source of python functions at runtime (not monkey-patching - actual patch-patching).

A quick example:

>>> def sample():
...    return 1
>>> patchy.patch(sample, """\
...     @@ -1,2 +1,2 @@
...      def sample():
...     -    return 1
...     +    return 9001
...     """)
>>> sample()
9001

Installation

Use pip:

pip install patchy

Tested on Python 2.7, 3.4, and 3.5.

Why?

If you’re monkey-patching an external library to add or fix some functionality, you will probably forget to check the monkey patch when you upgrade it. By using a patch against its source code, you can specify some context that you expect to remain the same in the function that will be checked before the source is applied.

I found this with some small but important patches to Django for a project. Since it takes a lot of energy to maintain a fork, writing monkey patches was the chosen quick solution, but then writing actual patches would be better.

The patches are applied with the standard patch commandline utility.

Why not?

There are of course a lot of reasons against:

  • It’s (relatively) slow (since it writes the source to disk and calls the patch command)
  • If you have a patch file, why not just fork the library and apply it?
  • At least with monkey-patching you know what end up with, rather than having the changes being done at runtime to source that may have changed.

All are valid arguments. However once in a while this might be the right solution.

How?

The standard library function inspect.getsource() is used to retrieve the source code of the function, the patch is applied with the commandline utility patch, the code is recompiled, and the function’s code object is replaced the new one. Because nothing tends to poke around at code objects apart from dodgy hacks like this, you don’t need to worry about chasing any references that may exist to the function, unlike mock.patch.

A little special treatment is given to instancemethod, classmethod, and staticmethod objects to make sure the underlying function is what gets patched and that you don’t have to worry about the details.

API

patch(func, patch_text)

Apply the patch patch_text to the source of function func.

If the patch is invalid, for example the context lines don’t match, ValueError will be raised, with a message that includes all the output from the patch utility.

Note that patch_text will be textwrap.dedent()’ed, but leading whitespace will not be removed. Therefore the correct way to include the patch is with a triple-quoted string with a backslash - """\ - which starts the string and avoids including the first newline. A final newline is not required and will be automatically added if not present.

Example:

import patchy

def sample():
    return 1

patchy.patch(sample, """\
    @@ -2,2 +2,2 @@
    -    return 1
    +    return 2""")

print(sample())  # prints 2

mc_patchface(func, patch_text)

An alias for patch, so you can meme it up by calling patchy.mc_patchface().

unpatch(func, patch_text)

Unapply the patch patch_text from the source of function func. This is the reverse of patch()ing it, and calls patch --reverse.

The same error and formatting rules apply as in patch().

Example:

import patchy

def sample():
    return 2

patchy.unpatch(sample, """\
    @@ -2,2 +2,2 @@
    -    return 1
    +    return 2""")

print(sample())  # prints 1

temp_patch(func, patch_text)

Usable as a context manager or function decorator to wrap code with a call to patch before and unpatch after.

Context manager example:

def sample():
    return 1234

patch_text = """\
    @@ -1,2 +1,2 @@
     def sample():
    -    return 1234
    +    return 5678
    """

with patchy.temp_patch(sample, patch_text):
    print(sample())  # prints 5678

Decorator example, using the same sample and patch_text:

@patchy.temp_patch(sample, patch_text)
def my_func():
    return sample() == 5678

print(my_func())  # prints True

How to Create a Patch

  1. Save the source of the function of interest (and nothing else) in a .py file, e.g. before.py:

    def foo():
        print("Change me")
    

    Make sure you dedent it so there is no whitespace before the def, i.e. d is the first character in the file. For example if you wanted to patch the bar() method below:

    class Foo():
        def bar(self, x):
            return x * 2
    

    …you would put just the method in a file like so:

    def bar(self, x):
        return x * 2
    

    However we’ll continue with the first example before.py since it’s simpler.

  2. Copy that .py file, to e.g. after.py, and make the changes you want, such as:

    def foo():
        print("Changed")
    
  3. Run diff, e.g. diff before.py after.py. You will get output like:

    diff --git a/Users/chainz/tmp/before.py b/Users/chainz/tmp/after.py
    index e6b32c6..31fe8d9 100644
    --- a/Users/chainz/tmp/before.py
    +++ b/Users/chainz/tmp/after.py
    @@ -1,2 +1,2 @@
     def foo():
    -    print("Change me")
    +    print("Changed")
    
  4. The filenames are not necessary for patchy to work. Take only from the first @@ line onwards into the multiline string you pass to patchy.patch():

    patchy.patch(foo, """\
        @@ -1,2 +1,2 @@
         def foo():
        -    print("Change me")
        +    print("Changed")
        """)
    

History

Pending Release

1.3.1 (2016-05-24)

  • Fixed setup.py to not fix the version of six required.
  • Fixed install instruction in README.
  • Added patchy.mc_patchface() as an alias for patchy.patch() because memes.

1.3.0 (2015-12-09)

  • Remove dependency on pylru by using a simpler caching strategy

1.2.0 (2015-07-23)

  • Pirate mascot!
  • Patching caches the patched and unpatched versions, so unpatching and repeat patching/unpatching are both faster
  • Patching doesn’t attach an attribute to the function object any more

1.1.0 (2015-06-16)

  • Fixed code compilation to use the __future__ flags from the function that is being patched
  • Added unpatch method
  • Added temp_patch context manager/decorator

1.0.0 (2015-06-09)

  • First release on PyPI, featuring patch function.
Release History

Release History

1.3.1

This version

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.3.0

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.2.0

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.1.0

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.0.0

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

Download Files

Download Files

TODO: Brief introduction on what you do with files - including link to relevant help section.

File Name & Checksum SHA256 Checksum Help Version File Type Upload Date
patchy-1.3.1-py2.py3-none-any.whl (10.4 kB) Copy SHA256 Checksum SHA256 py2.py3 Wheel May 24, 2016
patchy-1.3.1.tar.gz (12.1 kB) Copy SHA256 Checksum SHA256 Source May 24, 2016

Supported By

WebFaction WebFaction Technical Writing Elastic Elastic Search Pingdom Pingdom Monitoring Dyn Dyn DNS HPE HPE Development Sentry Sentry Error Logging CloudAMQP CloudAMQP RabbitMQ Heroku Heroku PaaS Kabu Creative Kabu Creative UX & Design Fastly Fastly CDN DigiCert DigiCert EV Certificate Rackspace Rackspace Cloud Servers DreamHost DreamHost Log Hosting