Source code for fetchez.hooks.registry

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
fetchez.hooks.registry
~~~~~~~~~~~~~~~~~~~~~~

This holds the hook registry.

:copyright: (c) 2010-2026 Regents of the University of Colorado
:license: MIT, see LICENSE for more details.
"""

import importlib
import os
import sys
import logging
from typing import Dict, Any
from . import FetchHook

logger = logging.getLogger(__name__)


[docs] class HookRegistry: _hooks: Dict[Any, Any] = {}
[docs] @classmethod def load_builtins(cls): """Recursively scan and load all built-in hooks from the 'builtins' directory.""" # Determine the absolute path to the 'hooks' directory current_dir = os.path.dirname(os.path.abspath(__file__)) builtins_dir = os.path.join(current_dir, "builtins") if not os.path.exists(builtins_dir): logger.debug(f"No builtins directory found at {builtins_dir}") return # Walk the directory tree recursively for root, dirs, files in os.walk(builtins_dir): dirs[:] = [d for d in dirs if not d.startswith("_")] for f in files: if f.endswith(".py") and not f.startswith("_"): rel_dir = os.path.relpath(root, current_dir) mod_path = rel_dir.replace(os.sep, ".") mod_name = f[:-3] full_mod_name = f"fetchez.hooks.{mod_path}.{mod_name}" try: mod = importlib.import_module(full_mod_name) cls._register_from_module(mod) except Exception as e: logger.warning( f"Failed to load built-in hook {full_mod_name}: {e}" )
[docs] @classmethod def load_user_plugins(cls): """Scan ~/.fetchez/hooks/ and .fetchez/hooks for python files.""" home = os.path.expanduser("~") home_hook_dir = os.path.join(home, ".fetchez", "hooks") cwd_hook_dir = os.path.join(home, ".fetchez", "hooks") for p_dir in [home_hook_dir, cwd_hook_dir]: if not os.path.exists(p_dir): continue sys.path.insert(0, p_dir) for f in os.listdir(p_dir): if f.endswith(".py") and not f.startswith("_"): try: mod_name = f[:-3] mod = importlib.import_module(mod_name) cls._register_from_module(mod) except Exception as e: logger.warning(f"Failed to load user hook {f}: {e}") sys.path.pop(0)
[docs] @classmethod def register_hook(cls, hook_cls): """Register a hook class. The hook must have a 'name' attribute (e.g. name='unzip'). """ import inspect if not hasattr(hook_cls, "name"): logger.warning( f"Cannot register hook {hook_cls}: Missing 'name' attribute." ) return key = hook_cls.name if ( inspect.isclass(hook_cls) and issubclass(hook_cls, FetchHook) and hook_cls is not FetchHook ): cls._hooks[key] = hook_cls logger.debug(f"Registered external hook: {key}")
@classmethod def _register_from_module(cls, module): """Inspect a module for classes inheriting from FetchHook.""" import inspect for name, obj in inspect.getmembers(module): if ( inspect.isclass(obj) and issubclass(obj, FetchHook) and obj is not FetchHook ): key = getattr(obj, "name", name.lower()) cls._hooks[key] = obj logger.debug(f"Registered hook from module: {key}")
[docs] @classmethod def get_hook(cls, name): """Retrieve a hook class by name.""" return cls._hooks.get(name)
[docs] @classmethod def list_hooks(cls): """Return a dict of all registered hooks.""" return cls._hooks