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:

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)

Note

Special precaution is advised when directly setting the Patch.obj attribute. See the warning note in the class Patch for more details.