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 unlikeHook
(inline hook). See FunctionalHookHooked 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 asfn_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, aFunctionalTap
is created. Look atFunctionalHook.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 instantiationNote
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 aHookMapping
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