.. module:: firebird.base.hooks
   :synopsis: Hook manager

####################
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
=======

.. code-block:: python

   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
=========

register_class
--------------
.. autofunction:: register_class

register_name
-------------
.. autofunction:: register_name

add_hook
--------
.. autofunction:: add_hook

get_callbacks
-------------
.. autofunction:: get_callbacks

HookManager
===========
.. autoclass:: HookManager

Globals
=======
.. autodata:: hook_manager
