Skip to main content

Generic hooking mechanism for Python

Project description

License: MIT PyPI package version Downloads

Hooks

HardMediaGroup, CC BY-SA 3.0, via Wikimedia Commons

Pyrustic Hooking

Generic hooking mechanism for Python

This project is part of the Pyrustic Open Ecosystem.

Modules documentation

Table of contents

Overview

This library, written in Python, implements an intuitive and minimalist hooking mechanism. It exposes a decorator to tag methods and functions (targets), so when called, user-defined hooks will be executed upstream or downstream according to the spec (either BEFORE or AFTER) provided by the user.

Arguments to targets are passed to hooks which can modify them or replace the targets themselves with an arbitrary callable or None.

Thanks to the tagging mechanism, hooks are not directly tied to targets but to tags (either user-defined or derived from functions or methods themselves). Thus, hooks are loosely coupled to targets and dynamically bound to tags.

Tagging mechanism

The H.tag class method allows you to tag a function or a method:

from hooking import H

@H.tag
def my_func(*args, **kwargs):
    pass

class MyClass:
    @H.tag
    def my_method(self, *args, **kwargs):
        pass

H.tag accepts a label string as argument. By default, when this argument isn't provided, the library uses the qualified name of the method or function as the label.

Here we provide the label argument:

from hooking import H

@H.tag("my_func")
def my_func(*args, **kwargs):
    pass

class MyClass:
    @H.tag("MyClass.my_method")
    def my_method(self, *args, **kwargs):
        pass

Bind hooks

Hooks are not directly bound to functions or methods but to tags. The H.bind class method allows the user to bind a hook to a tag and specify with the spec parameter whether the hook should be run upstream or downstream.

from hooking import H, BEFORE, AFTER

@H.tag("target")
def my_func(*args, **kwargs):
    pass

def my_hook1(context):
    pass

def my_hook2(context):
    pass

# bind my_hook1 to "target" and run it upstream
H.bind("target", my_hook1) # by default, spec == BEFORE

# bind my_hook1 to "target" and run it downstream
hook_id = H.bind("target", my_hook2, spec=AFTER)

The H.bind class method returns a Hook ID (HID) which could be used later to unbind the hook:

from hooking import H

def hook(context):
    pass

# bind
hid = H.bind("tag", hook)

# unbind
H.unbind(hid)

Multiple hooks can be unbound in a single statement:

from hooking import H

def hook1(context):
    pass

def hook2(context):
    pass

# bind
hid1 = H.bind("tag", hook1)
hid2 = H.bind("tag", hook2)

# unbind multiple hooks manually
H.unbind(hid1, hid2)

# unbind all hooks automatically
H.unbind()

Anatomy of a hook

A hook is a callable that accepts an instance of hooking.Context that exposes the following attributes:

  • hid: the Hook ID (HID) as returned by H.bind;
  • tag: the label string used to tag a function or method;
  • spec: one of the BEFORE or AFTER constants;
  • target: the function or method tagged with the H.tag decorator;
  • args: tuple representing the arguments passed to the target;
  • kwargs: dictionary representing the keyword arguments passed to the target;
  • result: when spec is set to AFTER, this attribute contains the value returned by the target.
from hooking import H, BEFORE, AFTER

@H.tag("target")
def my_func(*args, **kwargs):
    pass

def my_hook(context):
    if context.tag != "target":
        raise Exception("Wrong tag !")

H.bind("target", my_hook)

Chain break

This library exposes an exception subclass to allow the programmer to break the execution of a chain of hooks:

from hooking import H, ChainBreak

@H.tag("target")
def my_func(*args, **kwargs):
    pass

def hook1(context):
    pass

def hook2(context):
    raise ChainBreak

def hook3(context):
    pass

# bind hook1, hook2 and hook3 to 'target'
for hook in (hook1, hook2, hook3):
    H.bind("target", hook)

# call the target
my_func()

# since the target was called,
# the chain of hooks (hook1, hook2, hook3)
# must be executed.

# hook2 having used ChainBreak,
# the chain of execution will be broken
# and hook3 will be ignored

Freeze tags

We could freeze a tag and thus prevent the execution of hooks bound to this tag:

from hooking import H, BEFORE, AFTER

@H.tag
def my_func(*args, **kwargs):
    pass

H.freeze("my_func")

# from now on hooks bound to `my_func` will no longer be executed

The H.freeze class method can freeze multiple tags at once, or the entire hooking mechanism:

from hooking import H, BEFORE, AFTER

# freeze all tags manually
H.freeze("tag1", "tag2", "tag3", "tagx")

# freeze the entire hooking mechanism
H.freeze()

# from now, no hook will be executed anymore

To unfreeze specific tags or the entire hooking mechanism, use the H.unfreeze class method:

from hooking import H, BEFORE, AFTER

# unfreeze all tags manually
H.unfreeze("tag1", "tag2", "tag3", "tagx")

# unfreeze the entire hooking mechanism
H.unfreeze()

# from now, hooks will be executed when needed

Exposed variables

The H class exposes the following class variables:

  • hooks: dict, keys are HIDs (Hook IDs), values are instances of HookInfo;
  • tags: dict to hold relationship between tags and HIDs. Keys are tags, and values are sets;
  • frozen: boolean to tell whether the hooking mechanism is frozen or not;
  • frozen_tags: set containing frozen tags.

Clear data

The H.clear class method resets the following class variables: H.hooks, H.tags, H.frozen, H.frozen_tags.

Miscellaneous

Whenever threads are introduced into a program, the state shared between threads becomes vulnerable to corruption. To avoid this situation, this library uses threading.Lock as a synchronization tool.

Installation

Hooking is cross-platform and should work on Python 3.5 or newer.

For the first time

$ pip install hooking

Upgrade

$ pip install hooking --upgrade --upgrade-strategy eager

Show information

$ pip show hooking



Back to top

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hooking-0.0.3.tar.gz (8.9 kB view hashes)

Uploaded Source

Built Distribution

hooking-0.0.3-py3-none-any.whl (9.9 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page