Files
tp-ems/backend/app/hooks/loader.py
Du Wenbo 2822486270 Squashed 'core/' changes from 92ec910..2b9797d
2b9797d feat: add customer hooks plugin system (v1.1.0)
26d2731 chore: add VERSION file (1.0.0)

git-subtree-dir: core
git-subtree-split: 2b9797d61b501ecbaa73253f6f4001769917a24f
2026-04-04 18:32:56 +08:00

93 lines
3.0 KiB
Python

"""客户钩子加载器 — 从 customers/{CUSTOMER}/hooks/ 动态加载客户自定义钩子
加载逻辑:
1. 读取 CUSTOMER 环境变量
2. 查找 customers/{CUSTOMER}/hooks/__init__.py
3. 导入并返回 hooks 实例
4. 找不到则返回默认空钩子(所有方法为空操作)
"""
import importlib
import importlib.util
import logging
import os
import sys
from functools import lru_cache
from typing import Optional
from app.hooks.base import CustomerHooks
from app.core.config import get_settings
logger = logging.getLogger("hooks.loader")
_hooks_instance: Optional[CustomerHooks] = None
def _find_hooks_dir() -> Optional[str]:
"""Find the customer hooks directory, searching multiple locations."""
settings = get_settings()
customer = settings.CUSTOMER
config_path = settings.customer_config_path
hooks_dir = os.path.join(config_path, "hooks")
if os.path.isdir(hooks_dir) and os.path.exists(os.path.join(hooks_dir, "__init__.py")):
return hooks_dir
return None
def _load_hooks_from_dir(hooks_dir: str) -> Optional[CustomerHooks]:
"""Load hooks module from a directory path."""
try:
init_path = os.path.join(hooks_dir, "__init__.py")
spec = importlib.util.spec_from_file_location("customer_hooks", init_path)
if spec and spec.loader:
# Add parent dir to sys.path so relative imports work
parent_dir = os.path.dirname(os.path.dirname(hooks_dir))
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
hooks = getattr(module, "hooks", None)
if isinstance(hooks, CustomerHooks):
return hooks
else:
logger.warning(
f"Customer hooks module loaded but 'hooks' attribute is not a CustomerHooks instance"
)
except Exception as e:
logger.error(f"Failed to load customer hooks: {e}", exc_info=True)
return None
def get_hooks() -> CustomerHooks:
"""获取当前客户的钩子实例。线程安全,全局单例。
Returns:
CustomerHooks: 客户钩子实例,如果客户没有自定义钩子则返回默认空钩子
"""
global _hooks_instance
if _hooks_instance is not None:
return _hooks_instance
settings = get_settings()
hooks_dir = _find_hooks_dir()
if hooks_dir:
loaded = _load_hooks_from_dir(hooks_dir)
if loaded:
logger.info(f"Loaded customer hooks for '{settings.CUSTOMER}' from {hooks_dir}")
_hooks_instance = loaded
return _hooks_instance
logger.info(f"No custom hooks for '{settings.CUSTOMER}', using defaults")
_hooks_instance = CustomerHooks()
return _hooks_instance
def reload_hooks() -> CustomerHooks:
"""重新加载客户钩子(开发时热重载用)"""
global _hooks_instance
_hooks_instance = None
return get_hooks()