Functional Hooks

Functional hooks are hooks which wrap a function. They fire before and after the execution of a function automatically.

They are created using decorators on the function.

Usage on Class Instance methods

from pytapable import CreateHook, HookableMixin, create_hook_name

# 1. Class extends `HookableMixin` to initialize hooks on instance
class Car(HookableMixin):
   HOOK_ON_MOVE = create_hook_name('on_move')

   # 2. Mark this method as hookable
   @CreateHook(name=HOOK_ON_MOVE)
   def move(self, speed=10):
      return f"Moving at {speed}Mph"

c = Car()
c.hooks[Car.HOOK_ON_MOVE].tap(
   'log_metric_speed',
   lambda context, fn_kwargs, fn_output, is_before: ...,
   before=False
)

How it works

When a method is decorated using the CreateHook() decorator, the wrapped function is marked. The class must extend the HookableMixin class. This is necessary because when the Car class is initialized, the hookable mixin goes through all the marked methods and constructs a FunctionalHook` for each of them.

These newly created hooks are stored on the instance.hooks attribute which is defined by the HookableMixin class. instance.hooks is a super class of a dict HookMapping

Callback Arguments

  • Arguments passed to callbacks from a FunctionalHook are predefined unlike Hook (inline hook). See FunctionalHook

  • Hooked function args are converted to kwargs so a call to a hooked function like obj.fn(1, 2, c=3, d=4) will be passed to the callback as fn_kwargs={'a': 1, 'b': 2, 'c': 3, 'd': 4}. See CreateHook

Inheritance

HookableMixin allows you to inherit hooks from other classes that implement the HookableMixin

class MyClass(HookableMixin):

   def __init__(self):
      super(MyClass, self).__init__()
      self.car = Car()

      self.hooks.inherit_hooks(self.car)

my_class = MyClass()
my_class.hooks[Car.HOOK_ON_MOVE].tap(...)

Functional Hooks Documentation

FunctionalHook

class pytapable.FunctionalHook(name=None, interceptor=None)[source]

Functional hooks are created when CreateHook is used to decorate a class function. When a functional hook is tapped, a FunctionalTap is created. Look at FunctionalHook.call() to see how taps are called

call(fn_kwargs, is_before, fn_output=None)[source]

Triggers all taps installed on this hook.

Taps receive predefined arguments (context, fn_args, fn_output)

# Arguments to a callback

fn_kwargs: **kwargs

fn_output = Optional[Any]

context = {
  'hook': FunctionalHook,
  'tap': FunctionalTap,
  'is_before': is_before
}
Parameters
  • fn_kwargs (dict) – The kwargs the hooked function was called with. *args should be converted to **kwargs. See utils.merge_args_to_kwargs

  • is_before (bool) – True if the hook is being called after the hooked function has executed

  • fn_output (Optional[Any]) – The return value of the hooked function if any. None otherwise

tap(name, fn, before=True, after=True)[source]

Creates a FunctionalTap for this hook

Parameters
  • name (str) – Name of the tap

  • fn (Callable) – This will be called when the hook triggers

  • before (bool) – If true, this tap will be called before the hooked function executes

  • after (bool) – If true, this tap will be called after the hooked function executes

CreateHook

@pytapable.CreateHook(name, interceptor=None)[source]

Decorator used for creating Hooks on instance methods. It takes in a name and optionally an instance of a HookInterceptor.

Note

This decorator doesn’t actually create the hook. It just annotates the method. The hooks are created by the HookableMixin upon instantiation

Note

The wrapped function may be called with different combinations of positional and named args which would make it difficult for the callback function owner to know whether to read values from *args or **kwargs. We instead convert all positional args to named args to remove any ambiguity

See utils.merge_args_to_kwargs() for implementation details

HookableMixin

class pytapable.HookableMixin(*args, **kwargs)[source]

Mixin which instantiates all the decorated class methods. This is needed for decorated class methods

Instantiates an instance property self.hook which is a HookMapping

HookMapping

class pytapable.HookMapping[source]

A dict like object with helper methods to inherit hooks and add hooks

add_hook(hook)[source]

Adds the passed in hook to the hooks mapping dict

Parameters

hook (BaseHook) – Hook to add to the mapping

inherit_hooks(hookable_instance)[source]

Given an instance which extends the HookableMixin class, inherits all hooks from it to expose it on top level

Parameters

hookable_instance (HookableMixin) – Instance from which to inherit hooks

create_hook_name

pytapable.create_hook_name(name='')[source]

Utility to create a unique hook name. Optionally takes in a name. The output string is the name prefixed with a UUID. This is useful to prevent collision in hook names when one class with hooks inherits hooks from another class

Example

>>> create_hook_name()
>>> '7087eefc-8e94-4f0a-b7d3-453062bb7a34'
>>> create_hook_name('my_hook')
>>> '7087eefc-8e94-4f0a-b7d3-453062bb7a34:my_hook'
Parameters

name (Optional[str]) – Name of the hook

create_hook_names

pytapable.create_hook_names(*names)[source]

Useful shortcut to create multiple unique hook names in one statement

Example

>>> HOOK_MY, HOOK_UNIQUE, HOOK_HOOK = create_hook_names('my', 'unique', 'hook')
>>> HOOK_ONE, HOOK_TWO, HOOK_THREE = create_hook_names(*range(3))
Parameters

*names – Argument of hook names

Returns

iterable which can be deconstructed across constants.

Return type

Iterable