Tuesday, January 20, 2009

Understanding hooks in ECP.

Hooks in ECP are Python decorators that allow developers to replace existing methods by hooking into them. In fact, there is enough flexibility to decide at run time if the original invocation should be replaced by something new. Possibly depending on the state of some other object in the system.

The hook() decorator accepts two parameters: object, and method. The object parameter specifies the object in which defines the method to be hooked. The method parameter specifies the method to be hooked. The decorated function, the actual hook, must specify the same operation signature as the original method invocation.

For instance, lets say we want to hook the Package.get_name() method. We would define the hook as follows.
#ECP hook demonstration

from enomalism2.model import Package, hook

@hook(Package, Package.get_name)
def hook_get_name(fn, self):
"""Hook into the Package.get_name() method."""
return 'My Package'
Obviously not the most useful hook in the world, we replace the package name that would have been retrieved with a static string. In fact, the original Package.get_name() call is never actually invoked. It is replaced entirely.

Hooks can also be modeled as event subscriptions, where in the system, each method invocation can be considered a published event. Each defined hook can be considered an event subscription. For example, lets modify the previous example to simulate a pre and post event situation.
#ECP hook pre/post event demonstration 

import logging
from enomalism2.model import Package, hook

@hook(Package, Package.get_name)
def hook_get_name(fn, self):
"""Hook into the Package.get_name() method."""
logging.info("Attempting to get the package name...")
result=fn(self)
logging.info("Success.")
return result
Here, our pre-event functionality logs the fact that we are attempting to retrieve the package name. Our post-event functionality logs the fact that the package name retrieval was successful. To contract the first example, the original method is invoked here. We are simply extending the functionality. What is interesting is the fact that we can do so in any direction. We can add pre-invocation functionality, post-invocation functionality; we can replace the invocation entirely if need be.