hooks - Hook manager

Overview

This module provides a general framework for callbacks and “hookable” events.

Architecture

The callback extension mechanism is based on the following:

  • The Event source provides one or more “hookable events” that work like connection points. The event source represents “origin of event” and is always identified by class, class instance or name. Event sources that are identified by classes (or their instances) must be registered along with events they provide.

  • Event is typically linked to particular event source, but it’s not mandatory and it’s possible to define global events. Event is represented as value of any type, that must be unique in used context (particular event source or global).

    Each event should be properly documented along with required signature for callback function.

  • Event provider is a class or function that implements the event for event source, and asks the hook_manager for list of event consumers (callbacks) registered for particular event and source.

  • Event consumer is a function or class method that implements the callback for particular event. The callback must be registered in hook_manager before it could be called by event providers.

The architecture supports multiple usage strategies:

  • If event provider uses class instance to identify the event source, it’s possible to register callbacks to all instances (by registering to class), or particular instance(s).

  • It’s possible to register callback to particular instance by name, if instance is associated with name by register_name() function.

  • It’s possible to register callback to ANY event from particular source, or particular event from ANY source, or even to ANY event from ANY source.

Example

from __future__ import annotations
from enum import Enum, auto
from firebird.base.types import *
from firebird.base.hooks import hook_manager

class MyEvents(Enum):
    "Sample definition of events"
    CREATE = auto()
    ACTION = auto()

class MyHookable:
    "Example of hookable class, i.e. a class that calls hooks registered for events."
    def __init__(self, name: str):
        self.name: str = name
        for hook in hook_manager.get_callbacks(MyEvents.CREATE, self):
            try:
                hook(self, MyEvents.CREATE)
            except Exception as e:
                print(f"{self.name}.CREATE hook call outcome: ERROR ({e.args[0]})")
            else:
                print(f"{self.name}.CREATE hook call outcome: OK")
    def action(self):
        print(f"{self.name}.ACTION!")
        for hook in hook_manager.get_callbacks(MyEvents.ACTION, self):
            try:
                hook(self, MyEvents.ACTION)
            except Exception as e:
                print(f"{self.name}.ACTION hook call outcome: ERROR ({e.args[0]})")
            else:
                print(f"{self.name}.ACTION hook call outcome: OK")

class MyHook:
    "Example of hook implementation"
    def __init__(self, name: str):
        self.name: str = name
    def callback(self, subject: MyHookable, event: MyEvents):
        print(f"Hook {self.name} event {event.name} called by {subject.name}")
    def err_callback(self, subject: MyHookable, event: MyEvents):
        self.callback(subject, event)
        raise Exception("Error in hook")


# Example code that installs and uses hooks

hook_manager.register_class(MyHookable, MyEvents)
hook_A: MyHook = MyHook('Hook-A')
hook_B: MyHook = MyHook('Hook-B')
hook_C: MyHook = MyHook('Hook-C')

print("Install hooks")
hook_manager.add_hook(MyEvents.CREATE, MyHookable, hook_A.callback)
hook_manager.add_hook(MyEvents.CREATE, MyHookable, hook_B.err_callback)
hook_manager.add_hook(MyEvents.ACTION, MyHookable, hook_C.callback)

print("Create event sources, emits CREATE")
src_A: MyHookable = MyHookable('Source-A')
src_B: MyHookable = MyHookable('Source-B')

print("Install instance hooks")
hook_manager.add_hook(MyEvents.ACTION, src_A, hook_A.callback)
hook_manager.add_hook(MyEvents.ACTION, src_B, hook_B.callback)

print("And action!")
src_A.action()
src_B.action()

Output from sample code:

Install hooks
Create event sources, emits CREATE
Hook Hook-A event CREATE called by Source-A
Source-A.CREATE hook call outcome: OK
Hook Hook-B event CREATE called by Source-A
Source-A.CREATE hook call outcome: ERROR (Error in hook)
Hook Hook-A event CREATE called by Source-B
Source-B.CREATE hook call outcome: OK
Hook Hook-B event CREATE called by Source-B
Source-B.CREATE hook call outcome: ERROR (Error in hook)
Install instance hooks
And action!
Source-A.ACTION!
Hook Hook-A event ACTION called by Source-A
Source-A.ACTION hook call outcome: OK
Hook Hook-C event ACTION called by Source-A
Source-A.ACTION hook call outcome: OK
Source-B.ACTION!
Hook Hook-B event ACTION called by Source-B
Source-B.ACTION hook call outcome: OK
Hook Hook-C event ACTION called by Source-B
Source-B.ACTION hook call outcome: OK

Functions

firebird.base.hooks.register_class(cls: Type, events: Type[Enum] | Set = None) None

shortcut for hook_manager.register_class()

Parameters:
Return type:

None

firebird.base.hooks.register_name(instance: Any, name: str) None

shortcut for hook_manager.register_name()

Parameters:
  • instance (Any) –

  • name (str) –

Return type:

None

firebird.base.hooks.add_hook(event: Any, source: Any, callback: Callable) None

shortcut for hook_manager.add_hook()

Parameters:
Return type:

None

firebird.base.hooks.get_callbacks(event: Any, source: Any) List

shortcut for hook_manager.get_callbacks()

Parameters:
  • event (Any) –

  • source (Any) –

Return type:

List

Classes

class firebird.base.hooks.HookManager(*args, **kwargs)[source]

Bases: Singleton

Hook manager.

add_hook(event: Any, source: Any, callback: Callable) None[source]

Add new hook.

Parameters:
  • event (Any) – Event identificator.

  • source (Any) – Hookable class or instance, or instance name.

  • callback (Callable) – Callback function.

Return type:

None

Important

The signature of callback must conform to requirements for particular hookable event.

Raises:
  • TypeError – When subject is not registered as hookable.

  • ValueError – When event is not supported by specified subject.

Parameters:
Return type:

None

get_callbacks(event: Any, source: Any) List[source]

Returns list of all callbacks installed for specified event and hookable subject.

Parameters:
  • event (Any) – Event identificator.

  • source (Any) – Hookable class or instance, or name.

Return type:

List

register_class(cls: Type, events: Type[Enum] | Set = None) None[source]

Register hookable class.

Parameters:
  • cls (Type) – Class that supports hooks.

  • events (Type[Enum] | Set) – Supported events.

Return type:

None

Events could be specified using an Enum type or set of event identificators. When Enum is used (recommended), all enum values are registered as hookable events.

register_name(instance: Any, name: str) None[source]

Associate name with hookable instance.

Parameters:
  • instance (Any) – Instance of registered hookable class.

  • name (str) – Unique name assigned to instance.

Return type:

None

remove_all_hooks() None[source]

Removes all installed hooks.

Return type:

None

remove_hook(event: Any, source: Any, callback: Callable) None[source]

Remove hook callback installed by add_hook().

Parameters:
  • event (Any) – Event identificator.

  • source (Any) – Hookable class or instance.

  • callback (Callable) – Callback function.

Return type:

None

Important

For successful removal, the argument values must be exactly the same as used in add_hook() call.

The method does nothing if described hook is not installed.

reset() None[source]

Removes all installed hooks and unregisters all hookable classes and instances.

Return type:

None

Globals

firebird.base.hooks.hook_manager: HookManager

Hook manager