Coverage for merco/hooks/registry.py: 89%
46 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
1"""钩子注册与调度"""
3from __future__ import annotations
5import asyncio
6import inspect
7import logging
8from dataclasses import dataclass
9from typing import Callable
11logger = logging.getLogger("merco.hooks.registry")
14@dataclass
15class HookResult:
16 """Hook handler 的结构化返回值。
18 data 合并进当前事件 kwargs,后续 handler 会看到更新后的 kwargs。
19 stop=True 停止后续 hook handler;业务流程是否短路由调用方决定。
20 """
22 data: dict | None = None
23 stop: bool = False
26class HookRegistry:
27 """事件钩子注册表"""
29 def __init__(self):
30 self._hooks: dict[str, list[Callable]] = {}
32 def on(self, event: str, handler: Callable):
33 """注册钩子处理器"""
34 if event not in self._hooks:
35 self._hooks[event] = []
36 self._hooks[event].append(handler)
38 def off(self, event: str, handler: Callable):
39 """移除钩子处理器"""
40 if event in self._hooks:
41 self._hooks[event].remove(handler)
43 async def emit(self, event: str, **kwargs) -> HookResult | None:
44 """触发事件。
46 默认 fire-and-forget:handler 返回 None 时不影响流程。
47 handler 可返回 HookResult(data=...) 修改后续 handler 看到的 kwargs。
48 handler 可返回 HookResult(stop=True) 停止后续 handler 链。
49 """
50 handlers = self._hooks.get(event, [])
51 current = dict(kwargs)
52 changed = False
54 for handler in handlers:
55 try:
56 result = handler(**current)
57 if inspect.isawaitable(result):
58 result = await result
59 except Exception:
60 logger.debug("hook %s handler error", event, exc_info=True)
61 continue
63 if isinstance(result, HookResult):
64 if result.data:
65 current.update(result.data)
66 changed = True
67 if result.stop:
68 return HookResult(data=current, stop=True)
70 if changed:
71 return HookResult(data=current, stop=False)
72 return None
74 def clear(self, event: str = None):
75 """清除钩子"""
76 if event:
77 self._hooks.pop(event, None)
78 else:
79 self._hooks.clear()