Source code for bsb.config._hooks

def _dunder(*hooks):
    return "__" + "_".join(hooks) + "__"


[docs] def run_hook(obj, hook, *args, **kwargs): """ Execute the ``hook`` hook of ``obj``. Runs the ``hook`` method ``obj`` but also looks through the class hierarchy for essential hooks with the name ``__<hook>__``. .. note:: Essential hooks are only ran if the method is called using ``run_hook`` while non-essential hooks are wrapped around the method and will always be executed when the method is called (see https://github.com/dbbs-lab/bsb/issues/158). """ # Traverse the MRO tree and execute all essential (__hook__) methods. for parent in reversed(obj.__class__.__mro__): if hasattr(parent, _dunder(hook)): getattr(parent, _dunder(hook))(obj, *args, **kwargs) # Execute the final regular hook method. if hasattr(obj, hook): getattr(obj, hook)(*args, **kwargs)
[docs] def on(hook, cls, essential=False, before=False): """ Register a class hook. :param hook: Name of the method to hook. :type hook: str :param cls: Class to hook. :type cls: type :param essential: If the hook is essential, it will always be executed even in child classes that override the hook. Essential hooks are only lost if the method on ``cls`` is replaced. :type essential: bool :param before: If ``before`` the hook is executed before the method, otherwise afterwards. :type before: bool """ if essential: hook = _dunder(hook) return _hook(cls, hook, before)
[docs] def after(hook, cls, essential=False): """ Register a class hook to run after the target method. :param hook: Name of the method to hook. :type hook: str :param cls: Class to hook. :type cls: type :param essential: If the hook is essential, it will always be executed even in child classes that override the hook. Essential hooks are only lost if the method on ``cls`` is replaced. :type essential: bool """ return on(hook, cls, essential=essential, before=False)
[docs] def before(hook, cls, essential=False): """ Register a class hook to run before the target method. :param hook: Name of the method to hook. :type hook: str :param cls: Class to hook. :type cls: type :param essential: If the hook is essential, it will always be executed even in child classes that override the hook. Essential hooks are only lost if the method on ``cls`` is replaced. :type essential: bool """ return on(hook, cls, essential=essential, before=True)
def _super(cls, attr): for c in list(cls.__mro__)[1:]: try: return getattr(c, attr) except: pass
[docs] def has_hook(instance, hook): """ Checks the existence of a method or essential method on the ``instance``. :param instance: Object to inspect. :type instance: object :param hook: Name of the hook to look for. :type hook: str """ return hasattr(instance, hook) or hasattr(instance, _dunder(hook))
def overrides(cls, hook, mro=False): """ Returns ``True`` if a class has implemented a method or ``False`` if it has inherited it. :param cls: Class to inspect. :type cls: class :param hook: Name of the hook to look for. :type hook: str """ if not mro: return hook in vars(cls) else: class NotDefined: pass return getattr(cls, hook, NotDefined) is not getattr(object, hook, NotDefined) def _hook(cls, hook, before): # Returns a decorator that will wrap the existing implementation if it exists, # otherwise returns a decorator that will simply add the decorated function to the # class. if overrides(cls, hook): return _make_wrapper(cls, hook, before) else: return _make_injector(cls, hook) def _make_injector(cls, hook): def decorator(func): # Set the decorated function as the hooked function. _set_hooked_func(cls, hook, func) return decorator def _get_func_to_hook(cls, hook): return getattr(cls, hook) def _set_hooked_func(cls, hook, hooked): return setattr(cls, hook, hooked) def _make_wrapper(cls, hook, before): func_to_hook = _get_func_to_hook(cls, hook) def decorator(func): if before: # Wrapper that executes the decorated function before the class method def hooked(*args, **kwargs): func(*args, **kwargs) r = func_to_hook(*args, **kwargs) return r else: # Wrapper that executes the decorated function after the class method def hooked(*args, **kwargs): r = func_to_hook(*args, **kwargs) func(*args, **kwargs) return r # Set the wrapper function as the hooked function _set_hooked_func(cls, hook, hooked) return decorator