Gorilla’s Documentation¶
Welcome! If you are just getting started, a recommended first read is the Overview as it shortly covers the why, what, and how’s of this library. From there, the Installation then the Tutorial sections should get you up to speed with the basics required to use it.
Looking how to use a specific function, class, or method? The whole public interface is described in the API Reference section.
Please report bugs and suggestions on GitHub.
User’s Guide¶
Overview¶
Monkey patching is the process of modifying module and class attributes at runtime with the purpose of replacing or extending third-party code.
Although not a recommended practice, it is sometimes useful to fix or modify the behaviour of a piece of code from a third-party library, or to extend its public interface while making the additions feel like they are built-in into the library.
The Python language makes monkey patching extremely easy but the advantages of Gorilla are multiple, not only in assuring a consistent behaviour on both Python 2 and Python 3 versions, but also in preventing common source of errors, and making the process both intuitive and convenient even when faced with large numbers of patches to create.
Features¶
- intuitive and convenient decorator approach to create patches.
- can create patches for all class or module members at once.
- compatible with both Python 2 and Python 3.
- customizable behaviour.
Usage¶
Thanks to the dynamic nature of Python that makes monkey patching possible, the process happens at runtime without ever having to directly modify the source code of the third-party library:
>>> import gorilla
>>> import destination
>>> @gorilla.patches(destination.Class)
... class MyClass(object):
... def method(self):
... print("Hello")
... @classmethod
... def class_method(cls):
... print("world!")
The code above creates two patches, one for each member of the class
MyClass
, but does not apply them yet. In other words, they define the
information required to carry on the operation but are not yet inserted into
the specified destination class destination.Class
.
Such patches created with the decorators can then be automatically retrieved by recursively scanning a package or a module, then applied:
>>> import gorilla
>>> import mypackage
>>> patches = gorilla.find_patches([mypackage])
>>> for patch in patches:
... gorilla.apply(patch)
See also
The Tutorial section for more detailed examples and explanations on how to use Gorilla.
Installation¶
Gorilla doesn’t have any requirement outside of the Python interpreter. Any of the following Python versions is supported: 2.7, 3.3, 3.4, 3.5, and 3.6.
Installing pip¶
The recommended [1] approach for installing a Python package such as Gorilla
is to use pip
, a package manager for projects written in Python. If pip
is not already installed on your system, you can do so by following these
steps:
- Download get-pip.py.
- Run
python get-pip.py
in a shell.
Note
The installation commands described in this page might require sudo
privileges to run successfully.
System-Wide Installation¶
Installing globally the most recent version of Gorilla can be done with
pip
:
$ pip install gorilla
Or using easy_install
(provided with setuptools
):
$ easy_install gorilla
Virtualenv¶
If you’d rather make Gorilla only available for your specific project, an
alternative approach is to use virtualenv
. First, make sure that it is
installed:
$ pip install virtualenv
Then, an isolated environment needs to be created for your project before installing Gorilla in there:
$ mkdir myproject
$ cd myproject
$ virtualenv env
New python executable in /path/to/myproject/env/bin/python
Installing setuptools, pip, wheel...done.
$ source env/bin/activate
$ pip install gorilla
At this point, Gorilla is available for the project myproject
as long as
the virtual environment is activated.
To exit the virtual environment, run:
$ deactivate
Note
Instead of having to activate the virtual environment, it is also possible
to directly use the env/bin/python
, env/bin/pip
, and the other
executables found in the folder env/bin
.
Note
For Windows, some code samples might not work out of the box. Mainly,
activating virtualenv
is done by running the command
env\Scripts\activate
instead.
Development Version¶
To stay cutting edge with the latest development progresses, it is possible to directly retrieve the source from the repository with the help of Git:
$ git clone https://github.com/christophercrouzet/gorilla.git
$ cd gorilla
$ pip install --editable .[dev]
Note
The [dev]
part installs additional dependencies required to assist
development on Gorilla.
[1] | See the Python Packaging User Guide |
Tutorial¶
In the end Gorilla is nothing more than a fancy wrapper around Python’s
setattr()
function and thus requires to define patches, represented by the
class Patch
, containing the destination object, the attribute name at
the destination, and the actual value to set.
The Patch
class can be used directly if the patching information are
only known at runtime, as described in the section Dynamic Patching, but
otherwise a set of decorators are available to make the whole process more
intuitive and convenient.
The recommended approach involving decorators is to be done in two steps:
- create a single patch with the
patch()
decorator and/or multiple patches usingpatches()
.- find and apply the patches through the
find_patches()
andapply()
functions.
Creating a Single Patch¶
In order to make a function my_function()
available from within a
third-party module destination
, the first step is to create a new patch by
decorating our function:
>>> import gorilla
>>> import destination
>>> @gorilla.patch(destination)
... def my_function():
... print("Hello world!")
This step only creates the Patch
object containing the patch
information but does not inject the function into the destination module just
yet. The apply()
function needs to be called for that to happen, as shown
in the section Finding and Applying the Patches.
The defaut behaviour is for the patch to inject the function at the destination
using the name of the decorated object, that is 'my_function'
. If a
different name is desired but changing the function name is not possible, then
it can be done via the parameter name
:
>>> import gorilla
>>> import destination
>>> @gorilla.patch(destination, name='better_function')
... def my_function():
... print("Hello world!")
After applying the patch, the function will become accessible through a call to
destination.better_function()
.
A patch’s destination can not only be a module as shown above, but also an existing class:
>>> import gorilla
>>> import destination
>>> @gorilla.patch(destination.Class)
... def my_method(self):
... print("Hello")
>>> @gorilla.patch(destination.Class)
... @classmethod
... def my_class_method(cls):
... print("world!")
Creating Multiple Patches at Once¶
As the number of patches grows, the process of defining a decorator for each
individual patch can quickly become cumbersome. Instead, another decorator
patches()
is available to create a batch of patches
(tongue-twister challenge: repeat “batch of patches” 10 times):
>>> import gorilla
>>> import destination
>>> @gorilla.patches(destination.Class)
... class MyClass(object):
... def method(self):
... print("Hello")
... @classmethod
... def class_method(cls):
... print("world")
... @staticmethod
... def static_method():
... print("!")
The patches()
decorator iterates through all the members of the
decorated class, by default filtered using the default_filter()
function,
while creating a patch for each of them.
Each patch created in this manner inherits the properties defined by the root
decorator but it is still possible to override them using any of the
destination()
, name()
, settings()
, and filter()
modifier decorators:
>>> import gorilla
>>> import destination
>>> @gorilla.patches(destination.Class)
... class MyClass(object):
... @gorilla.name('better_method')
... def method(self):
... print("Hello")
... @gorilla.settings(allow_hit=True)
... @classmethod
... def class_method(cls):
... print("world")
... @gorilla.filter(False)
... @staticmethod
... def static_method():
... print("!")
In the example above, the method’s name is overriden to 'better_method'
,
the class method is allowed to overwrite an attribute with the same name at the
destination, and the static method is to be filtered out during the discovery
process described in Finding and Applying the Patches, leading to no patch being
created for it.
Note
The same operation can also be used to create a patch for each member of a
module but, since it is not possible to decorate a module, the function
create_patches()
needs to be directly used instead.
Overwriting Attributes at the Destination¶
If there was to be an attribute at the patch’s destination already existing
with the patch’s name, then the patching process can optionally override the
original attribute after storing a copy of it. This way, the original attribtue
remains accessible from within our code with the help of the
get_original_attribute()
function:
>>> import gorilla
>>> import destination
>>> settings = gorilla.Settings(allow_hit=True)
>>> @gorilla.patch(destination, settings=settings)
... def function():
... print("Hello world!")
... # We're overwriting an existing function here,
... # preserve its original behaviour.
... original = gorilla.get_original_attribute(destination, 'function')
... return original()
Note
The default settings of a patch do not allow attributes at the destination
to be overwritten. For such a behaviour, the attribute
Settings.allow_hit
needs to be set to True
.
Stack Ordering¶
The order in which the decorators are applied does matter. The
patch()
decorator can only be aware of the decorators defined below it.
>>> import gorilla
>>> import destination
>>> @gorilla.patch(destination.Class)
... @staticmethod
... def my_static_method_1():
... print("Hello")
>>> @staticmethod
... @gorilla.patch(destination.Class)
... def my_static_method_2():
... print("world!")
Here, only the static method my_static_method_1()
will be injected as
expected with the decorator staticmethod
while the other one will result
in an invalid definition since it will be interpreted as a standard method but
doesn’t define any parameter referring to the class object such as self
.
Finding and Applying the Patches¶
Once that the patches are created with the help of the decorators, the next
step is to (recursively) scan the modules and packages to retrieve them. This
is easily achieved with the find_patches()
function.
Finally, each patch can be applied using the apply()
function.
>>> import gorilla
>>> import mypackage
>>> patches = gorilla.find_patches([mypackage])
>>> for patch in patches:
... gorilla.apply(patch)
Dynamic Patching¶
In the case where patches need to be created dynamically, meaning that the
patch source objects and/or destinations are not known until runtime, then it
is possible to directly use the Patch
class.
>>> import gorilla
>>> import destination
>>> def my_function():
... print("Hello world!")
>>> patch = gorilla.Patch(destination, 'better_function', my_function)
>>> gorilla.apply(patch)
A Word of Caution¶
The process of Monkey Patching is at the same time both incredibly powerful and dangerous. It makes it easy to improve things on the surface but makes it even easier to cause troubles if done inappropriately.
Mostly, inserting new attributes by prefixing their name to avoid (future?) name clashes is usually fine, but replacing existing attributes should be avoided like the plague unless you really have to and know what you are doing. That is, if you do not want ending up being fired because you broke everyone else’s code.
As a safety measure, Gorilla has its Settings.allow_hit
attribute set
to False
by default, which raises an exception whenever it detects an
attempt at overwriting an existing attribute.
If you still want to go ahead with allowing hits, a second measure enabled
by default through the Settings.store_hit
attribute is to store the
overwriten attribute under a different name to have it still accessible using
the function get_original_attribute()
.
But still, avoid it if you can.
You’ve been warned.
API Reference¶
The whole public interface of Gorilla is described here.
All of the library’s content is accessible from within the only module
gorilla
.
The classes Settings
, Patch
, and the function apply
form
the core of the library and cover all the requirements for monkey
patching.
For intuitivity and convenience reasons, decorators and utility functions are also provided.
Core¶
Settings |
Define the patching behaviour. |
Patch |
Describe all the information required to apply a patch. |
apply |
Apply a patch. |
-
class
gorilla.
Settings
(**kwargs)[source]¶ Define the patching behaviour.
-
allow_hit
¶ A hit occurs when an attribute at the destination already exists with the name given by the patch. If
False
, the patch process won’t allow setting a new value for the attribute by raising an exception. Defaults toFalse
.Type: bool
-
-
class
gorilla.
Patch
(destination, name, obj, settings=None)[source]¶ Describe all the information required to apply a patch.
-
destination
¶ Patch destination.
Type: obj
-
name
¶ Name of the attribute at the destination.
Type: str
-
obj
¶ Attribute value.
Type: obj
-
settings
¶ Settings. If
None
, the default settings are used.Type: gorilla.Settings or None
Warning
It is highly recommended to use the output of the function
get_attribute()
for setting the attributeobj
. This will ensure that the descriptor protocol is bypassed instead of possibly retrieving attributes invalid for patching, such as bound methods.-
__init__
(destination, name, obj, settings=None)[source]¶ Constructor.
Parameters: - destination (object) – See the
destination
attribute. - name (str) – See the
name
attribute. - obj (object) – See the
obj
attribute. - settings (gorilla.Settings) – See the
settings
attribute.
- destination (object) – See the
-
-
gorilla.
apply
(patch, id='default')[source]¶ Apply a patch.
The patch’s
obj
attribute is injected into the patch’sdestination
under the patch’sname
.This is a wrapper around calling
setattr(patch.destination, patch.name, patch.obj)
.Parameters: - patch (gorilla.Patch) – Patch.
- id (str) – When applying a stack of patches on top of a same attribute, this identifier allows to pinpoint a specific original attribute if needed.
Raises: RuntimeError
– Overwriting an existing attribute is not allowed when the settingSettings.allow_hit
is set toTrue
.Note
If both the attributes
Settings.allow_hit
andSettings.store_hit
areTrue
but that the target attribute seems to have already been stored, then it won’t be stored again to avoid losing the original attribute that was stored the first time around.
-
gorilla.
revert
(patch)[source]¶ Revert a patch.
Parameters: patch (gorilla.Patch) – Patch. Note
This is only possible if the attribute
Settings.store_hit
was set toTrue
when applying the patch and overriding an existing attribute.
Decorators¶
patch |
Decorator to create a patch. |
patches |
Decorator to create a patch for each member of a module or a class. |
destination |
Modifier decorator to update a patch’s destination. |
name |
Modifier decorator to update a patch’s name. |
settings |
Modifier decorator to update a patch’s settings. |
filter |
Modifier decorator to force the inclusion or exclusion of an attribute. |
-
gorilla.
patch
(destination, name=None, settings=None)[source]¶ Decorator to create a patch.
The object being decorated becomes the
obj
attribute of the patch.Parameters: - destination (object) – Patch destination.
- name (str) – Name of the attribute at the destination.
- settings (gorilla.Settings) – Settings.
Returns: The decorated object.
Return type: object
See also
-
gorilla.
patches
(destination, settings=None, traverse_bases=True, filter=<function default_filter>, recursive=True, use_decorators=True)[source]¶ Decorator to create a patch for each member of a module or a class.
Parameters: - destination (object) – Patch destination.
- settings (gorilla.Settings) – Settings.
- traverse_bases (bool) – If the object is a class, the base classes are also traversed.
- filter (function) – Attributes for which the function returns
False
are skipped. The function needs to define two parameters:name
, the attribute name, andobj
, the attribute value. IfNone
, no attribute is skipped. - recursive (bool) – If
True
, and a hit occurs due to an attribute at the destination already existing with the given name, and both the member and the target attributes are classes, then instead of creating a patch directly with the member attribute value as is, a patch for each of its own members is created with the target as new destination. - use_decorators (bool) – Allows to take any modifier decorator into consideration to allow for more granular customizations.
Returns: The decorated object.
Return type: object
Note
A ‘target’ differs from a ‘destination’ in that a target represents an existing attribute at the destination about to be hit by a patch.
See also
-
gorilla.
destination
(value)[source]¶ Modifier decorator to update a patch’s destination.
This only modifies the behaviour of the
create_patches()
function and thepatches()
decorator, given that their parameteruse_decorators
is set toTrue
.Parameters: value (object) – Patch destination. Returns: The decorated object. Return type: object
-
gorilla.
name
(value)[source]¶ Modifier decorator to update a patch’s name.
This only modifies the behaviour of the
create_patches()
function and thepatches()
decorator, given that their parameteruse_decorators
is set toTrue
.Parameters: value (object) – Patch name. Returns: The decorated object. Return type: object
-
gorilla.
settings
(**kwargs)[source]¶ Modifier decorator to update a patch’s settings.
This only modifies the behaviour of the
create_patches()
function and thepatches()
decorator, given that their parameteruse_decorators
is set toTrue
.Parameters: kwargs – Settings to update. See Settings
for the list.Returns: The decorated object. Return type: object
-
gorilla.
filter
(value)[source]¶ Modifier decorator to force the inclusion or exclusion of an attribute.
This only modifies the behaviour of the
create_patches()
function and thepatches()
decorator, given that their parameteruse_decorators
is set toTrue
.Parameters: value (bool) – True
to force inclusion,False
to force exclusion, andNone
to inherit from the behaviour defined bycreate_patches()
orpatches()
.Returns: The decorated object. Return type: object
Utilities¶
default_filter |
Attribute filter. |
create_patches |
Create a patch for each member of a module or a class. |
find_patches |
Find all the patches created through decorators. |
get_attribute |
Retrieve an attribute while bypassing the descriptor protocol. |
get_original_attribute |
Retrieve an overriden attribute that has been stored. |
DecoratorData |
Decorator data. |
get_decorator_data |
Retrieve any decorator data from an object. |
-
gorilla.
default_filter
(name, obj)[source]¶ Attribute filter.
It filters out module attributes, and also methods starting with an underscore
_
.This is used as the default filter for the
create_patches()
function and thepatches()
decorator.Parameters: - name (str) – Attribute name.
- obj (object) – Attribute value.
Returns: Whether the attribute should be returned.
Return type: bool
-
gorilla.
create_patches
(destination, root, settings=None, traverse_bases=True, filter=<function default_filter>, recursive=True, use_decorators=True)[source]¶ Create a patch for each member of a module or a class.
Parameters: - destination (object) – Patch destination.
- root (object) – Root object, either a module or a class.
- settings (gorilla.Settings) – Settings.
- traverse_bases (bool) – If the object is a class, the base classes are also traversed.
- filter (function) – Attributes for which the function returns
False
are skipped. The function needs to define two parameters:name
, the attribute name, andobj
, the attribute value. IfNone
, no attribute is skipped. - recursive (bool) – If
True
, and a hit occurs due to an attribute at the destination already existing with the given name, and both the member and the target attributes are classes, then instead of creating a patch directly with the member attribute value as is, a patch for each of its own members is created with the target as new destination. - use_decorators (bool) –
True
to take any modifier decorator into consideration to allow for more granular customizations.
Returns: The patches.
Return type: list of gorilla.Patch
Note
A ‘target’ differs from a ‘destination’ in that a target represents an existing attribute at the destination about to be hit by a patch.
See also
-
gorilla.
find_patches
(modules, recursive=True)[source]¶ Find all the patches created through decorators.
Parameters: - modules (list of module) – Modules and/or packages to search the patches in.
- recursive (bool) –
True
to search recursively in subpackages.
Returns: Patches found.
Return type: list of gorilla.Patch
Raises: TypeError
– The input is not a valid package or module.
-
gorilla.
get_attribute
(obj, name)[source]¶ Retrieve an attribute while bypassing the descriptor protocol.
As per the built-in
getattr()
function, if the input object is a class then its base classes might also be searched until the attribute is found.Parameters: - obj (object) – Object to search the attribute in.
- name (str) – Name of the attribute.
Returns: The attribute found.
Return type: object
Raises: AttributeError
– The attribute couldn’t be found.
-
gorilla.
get_original_attribute
(obj, name, id='default')[source]¶ Retrieve an overriden attribute that has been stored.
Parameters: - obj (object) – Object to search the attribute in.
- name (str) – Name of the attribute.
- id (str) – Identifier of the original attribute to retrieve from the stack.
Returns: The attribute found.
Return type: object
Raises: AttributeError
– The attribute couldn’t be found.See also
-
class
gorilla.
DecoratorData
[source]¶ Decorator data.
-
patches
¶ Patches created through the decorators.
Type: list of gorilla.Patch
-
override
¶ Any overriding value defined by the
destination()
,name()
, andsettings()
decorators.Type: dict
-
-
gorilla.
get_decorator_data
(obj, set_default=False)[source]¶ Retrieve any decorator data from an object.
Parameters: - obj (object) – Object.
- set_default (bool) – If no data is found, a default one is set on the object and returned,
otherwise
None
is returned.
Returns: The decorator data or
None
.Return type:
Developer’s Guide¶
Running the Tests¶
After making any code change in Gorilla, tests need to be evaluated to ensure that the library still behaves as expected.
Note
Some of the commands below are wrapped into make
targets for
convenience, see the file Makefile
.
unittest¶
The tests are written using Python’s built-in unittest
module. They are
available in the tests
directory and can be fired through the
tests/run.py
file:
$ python tests/run.py
It is possible to run specific tests by passing a space-separated list of partial names to match:
$ python tests/run.py ThisTestClass and_that_function
The unittest
’s command line interface is also supported:
$ python -m unittest discover -s tests -v
Finally, each test file is a standalone and can be directly executed.
tox¶
Test environments have been set-up with tox
to allow testing Gorilla against
each supported version of Python:
$ tox
coverage¶
The package coverage
is used to help localize code snippets that could
benefit from having some more testing:
$ coverage run --source gorilla -m unittest discover -s tests
$ coverage report
$ coverage html
In no way should coverage
be a race to the 100% mark since it is not
always meaningful to cover each single line of code. Furthermore, having some
code fully covered isn’t synonym to having quality tests. This is our
responsability, as developers, to write each test properly regardless of the
coverage status.
Additional Information¶
Changelog¶
Version numbers comply with the Sementic Versioning Specification (SemVer).
v0.4.0 (2021-04-17)¶
Added¶
- Implement a new public function to revert a patch.
- Support applying stacks of patches.
- Include the utf-8 shebang to all source files.
- Enforce Python 3 compatibility with the
__future__
module. - Testing with Python versions 3.7, 3,8, and 3.9.
- Set the
__all__
attribute. - Make use of styling and linting tools.
Removed¶
- Testing with Python version 3.3.
- Testing of the representation outputs.
Changed¶
- Update the setup file.
- Rework the project’s metadata.
- Shorten docstrings for non-public functions.
- Make minor tweaks to the code.
- Use the ‘new’ string formatting method.
- Update the contact’s email.
Fixed¶
- Fix
__weakref__
showing up in the doc. - Fix the changelog reference.
v0.3.0 (2017-01-18)¶
Added¶
- Add the decorator data to the public interface.
- Add support for coverage and tox.
- Add continuous integration with Travis and coveralls.
- Add a few bling-bling badges to the readme.
- Add a Makefile to regroup common actions for developers.
Changed¶
- Improve the documentation.
- Improve the unit testing workflow.
- Remove the
__slots__
attribute from theSettings
andPatch
classes. - Refocus the content of the readme.
- Define the ‘long_description’ and ‘extras_require’ metadata to setuptools’ setup.
- Update the documentation’s Makefile with a simpler template.
- Rework the ‘.gitignore’ files.
- Rename the changelog to ‘CHANGELOG’!
- Make minor tweaks to the code.
Fixed¶
- Fix the settings not being properly inherited.
- Fix the decorator data not supporting class inheritance.
v0.2.0 (2016-12-20)¶
Changed¶
- Rewrite everything from scratch. Changes are not backwards compatible.
v0.1.0 (2014-06-29)¶
Added¶
- Add settings to modify the behaviour of the patching process.
- Added a FAQ section to the doc.
Changed¶
- Refactor the class
ExtensionSet
towards using anadd()
method. - Clean-up the
Extension.__init__()
method from the parameters not required to construct the class. - Get the
ExtensionsRegistrar.register_extensions()
function to return a singleExtensionSet
object. - Make minor tweaks to the code and documentation.
v0.0.1 (2014-06-21)¶
- Initial release.
Versioning¶
Version numbers comply with the Sementic Versioning Specification (SemVer).
In summary, version numbers are written in the form MAJOR.MINOR.PATCH
where:
- incompatible API changes increment the MAJOR version.
- functionalities added in a backwards-compatible manner increment the MINOR version.
- backwards-compatible bug fixes increment the PATCH version.
Major version zero (0.y.z) is considered a special case denoting an initial development phase. Anything may change at any time without the MAJOR version being incremented.
License¶
The MIT License (MIT)
Copyright (c) 2014-2017 Christopher Crouzet
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.