infinity_bus 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- event_bus/__init__.py +45 -0
- event_bus/bus.py +314 -0
- event_bus/event.py +52 -0
- event_bus/handler.py +90 -0
- event_bus/middleware.py +209 -0
- event_bus/py.typed +0 -0
- event_bus/templates/__init__.py +55 -0
- event_bus/templates/bridge.py +0 -0
- event_bus/templates/expect.py +130 -0
- event_bus/templates/middlewares.py +744 -0
- event_bus/templates/pipe.py +259 -0
- event_bus/templates/register.py +144 -0
- event_bus/templates/request.py +85 -0
- infinity_bus-1.3.1.dist-info/METADATA +160 -0
- infinity_bus-1.3.1.dist-info/RECORD +18 -0
- infinity_bus-1.3.1.dist-info/WHEEL +5 -0
- infinity_bus-1.3.1.dist-info/licenses/LICENSE.md +9 -0
- infinity_bus-1.3.1.dist-info/top_level.txt +1 -0
event_bus/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""event_bus - 基于 asyncio 的轻量级事件总线"""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.3.0"
|
|
4
|
+
|
|
5
|
+
from .event import (
|
|
6
|
+
Event,
|
|
7
|
+
EventDeclaration,
|
|
8
|
+
EventRegistry,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .handler import (
|
|
12
|
+
EventHandler,
|
|
13
|
+
EventHandlerRegistry,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .middleware import (
|
|
17
|
+
Middleware,
|
|
18
|
+
MiddlewareChain,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .bus import (
|
|
22
|
+
EventBus,
|
|
23
|
+
BusShuttingDown,
|
|
24
|
+
ShutdownEvent,
|
|
25
|
+
TaskErrorPayload,
|
|
26
|
+
TaskErrorEvent,
|
|
27
|
+
ShutdownConfig,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__: list[str] = [
|
|
31
|
+
"__version__",
|
|
32
|
+
"Event",
|
|
33
|
+
"EventDeclaration",
|
|
34
|
+
"EventRegistry",
|
|
35
|
+
"EventHandler",
|
|
36
|
+
"EventHandlerRegistry",
|
|
37
|
+
"Middleware",
|
|
38
|
+
"MiddlewareChain",
|
|
39
|
+
"EventBus",
|
|
40
|
+
"BusShuttingDown",
|
|
41
|
+
"ShutdownEvent",
|
|
42
|
+
"TaskErrorPayload",
|
|
43
|
+
"TaskErrorEvent",
|
|
44
|
+
"ShutdownConfig",
|
|
45
|
+
]
|
event_bus/bus.py
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import types
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Dict, Optional, Set, Type, Union
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from .event import Event, EventDeclaration, EventRegistry
|
|
9
|
+
from .handler import EventHandler, EventHandlerRegistry
|
|
10
|
+
from .middleware import MiddlewareChain, OnPublishNext
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class BusShuttingDown(Exception):
|
|
15
|
+
"""总线正在停止,拒绝新发布,请求处理器执行清理并退出"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class ShutdownEvent(EventDeclaration):
|
|
19
|
+
name = "event_bus.__shutdown__"
|
|
20
|
+
|
|
21
|
+
class TaskErrorPayload(BaseModel):
|
|
22
|
+
error_event: Event = Field(description="发生异常的事件")
|
|
23
|
+
handler_id: Optional[str] = Field(default=None, description="发生异常的处理器内部ID")
|
|
24
|
+
handler_name: str = Field(description="发生异常的处理器类名")
|
|
25
|
+
error_type: str = Field(description="异常类型")
|
|
26
|
+
error_message: str = Field(description="异常消息")
|
|
27
|
+
|
|
28
|
+
class TaskErrorEvent(EventDeclaration):
|
|
29
|
+
name = "event_bus.__task_error__"
|
|
30
|
+
payload_type = TaskErrorPayload
|
|
31
|
+
|
|
32
|
+
class ShutdownConfig(BaseModel):
|
|
33
|
+
"""总线停机配置"""
|
|
34
|
+
queue_timeout_min: float = Field(default=1.0, description="队列排空最小等待时间(秒)")
|
|
35
|
+
queue_timeout_max: float = Field(default=15.0, description="队列排空最大等待时间(秒)")
|
|
36
|
+
tasks_timeout: float = Field(default=15.0, description="活跃任务完成等待时间(秒)")
|
|
37
|
+
avg_wait_time: float = Field(default=0.05, description="每个事件平均处理时间估算(秒)")
|
|
38
|
+
|
|
39
|
+
class EventBus:
|
|
40
|
+
"""
|
|
41
|
+
异步事件总线,支持订阅/发布模式
|
|
42
|
+
|
|
43
|
+
系统内置事件:
|
|
44
|
+
event_bus.__task_error__ 任务执行失败时发送,载荷为 TaskErrorPayload,发布者为 EventBusErrorReporter
|
|
45
|
+
event_bus.__shutdown__ 总线将要关闭时发送, 无载荷, 发布者为 EventBus
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
class Proxy:
|
|
49
|
+
"""事件总线代理,提供给处理器调用以访问总线功能"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, bus: 'EventBus', source: str, raw_event: Optional[Event] = None) -> None:
|
|
52
|
+
self._bus: EventBus = bus
|
|
53
|
+
self._source: str = source
|
|
54
|
+
self._raw_event: Optional[Event] = raw_event
|
|
55
|
+
|
|
56
|
+
async def publish(self, name: str, data: Optional[Union[Dict[str, Any], BaseModel]] = None) -> None:
|
|
57
|
+
await self._bus._publish(name, self._source, data, self._raw_event)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def handlers_registry(self) -> EventHandlerRegistry:
|
|
61
|
+
return self._bus._handlers
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def events_registry(self) -> EventRegistry:
|
|
65
|
+
return self._bus._events
|
|
66
|
+
|
|
67
|
+
def __init__(self, event_registry: EventRegistry, handler_registry: EventHandlerRegistry, max_queue_size: int = 1024, max_handler_semaphore: int = 256, shutdown: ShutdownConfig = ShutdownConfig(), middleware_chain: Optional[MiddlewareChain] = None) -> None:
|
|
68
|
+
self._events: EventRegistry = event_registry
|
|
69
|
+
self._handlers: EventHandlerRegistry = handler_registry
|
|
70
|
+
self._mw_chain: MiddlewareChain = middleware_chain or MiddlewareChain()
|
|
71
|
+
|
|
72
|
+
if self._events.get(ShutdownEvent.name) is None:
|
|
73
|
+
self._events.register(ShutdownEvent)
|
|
74
|
+
if self._events.get(TaskErrorEvent.name) is None:
|
|
75
|
+
self._events.register(TaskErrorEvent)
|
|
76
|
+
|
|
77
|
+
self._state_lock: asyncio.Lock = asyncio.Lock()
|
|
78
|
+
self._enable_publish: asyncio.Event = asyncio.Event()
|
|
79
|
+
self._running: asyncio.Event = asyncio.Event()
|
|
80
|
+
self._queue: asyncio.Queue[Event] = asyncio.Queue(maxsize=max_queue_size)
|
|
81
|
+
self._dispatch_task: Optional[asyncio.Task[None]] = None
|
|
82
|
+
|
|
83
|
+
self._handler_semaphore = asyncio.Semaphore(max_handler_semaphore)
|
|
84
|
+
self._active_tasks: Set[asyncio.Task[Any]] = set()
|
|
85
|
+
|
|
86
|
+
self._shutdown: ShutdownConfig = shutdown
|
|
87
|
+
|
|
88
|
+
# 预构建中间件责任链(之后不再变更)
|
|
89
|
+
self._before_publish_chain = self._mw_chain.build_before_publish(self._core_publish)
|
|
90
|
+
self._on_publish_chain: OnPublishNext = self._mw_chain.build_on_publish(self._noop_on_publish)
|
|
91
|
+
|
|
92
|
+
async def _publish(self, name: str, source: str, data: Optional[Union[Dict[str, Any], BaseModel]] = None, old_event: Optional[Event] = None) -> None:
|
|
93
|
+
"""发布事件到总线(经过中间件链)"""
|
|
94
|
+
if not self._enable_publish.is_set():
|
|
95
|
+
if self._running.is_set():
|
|
96
|
+
logger.warning("EventBus is stopping, cannot publish new events")
|
|
97
|
+
raise BusShuttingDown("EventBus is stopping, cannot publish new events")
|
|
98
|
+
else:
|
|
99
|
+
logger.warning("EventBus is not running, cannot publish events")
|
|
100
|
+
raise RuntimeError("EventBus is not running, cannot publish events")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
await self._before_publish_chain(self._events, name, source, data, old_event)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
await self._mw_chain.on_publish_error(e, name, source, data)
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
async def _core_publish(
|
|
109
|
+
self,
|
|
110
|
+
event_registry: EventRegistry,
|
|
111
|
+
name: str,
|
|
112
|
+
source: str,
|
|
113
|
+
data: Optional[Union[Dict[str, Any], BaseModel]],
|
|
114
|
+
old_event: Optional[Event],
|
|
115
|
+
) -> None:
|
|
116
|
+
"""before_publish 链的末端处理器:校验 → 构造 Event → 入队 → 触发 on_publish 链"""
|
|
117
|
+
event_declaration: Optional[Type[EventDeclaration]] = event_registry.get(name)
|
|
118
|
+
if not event_declaration:
|
|
119
|
+
logger.error(f"Unknown event type: {name}")
|
|
120
|
+
raise ValueError(f"Unknown event type: {name}")
|
|
121
|
+
|
|
122
|
+
payload: Optional[BaseModel] = None
|
|
123
|
+
if event_declaration.payload_type:
|
|
124
|
+
if data is None:
|
|
125
|
+
raise ValueError(f"Event {name} requires payload data, but none provided")
|
|
126
|
+
|
|
127
|
+
elif isinstance(data, BaseModel):
|
|
128
|
+
if not isinstance(data, event_declaration.payload_type):
|
|
129
|
+
raise TypeError(
|
|
130
|
+
f"Payload type mismatch for event '{name}': "
|
|
131
|
+
f"expected {event_declaration.payload_type.__name__}, got {type(data).__name__}"
|
|
132
|
+
)
|
|
133
|
+
payload = data.model_copy()
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
payload = event_declaration.payload_type(**data)
|
|
137
|
+
else:
|
|
138
|
+
if data is not None:
|
|
139
|
+
raise ValueError(f"Event {name} does not accept payload data")
|
|
140
|
+
|
|
141
|
+
event = Event(
|
|
142
|
+
name=name,
|
|
143
|
+
data=payload,
|
|
144
|
+
sources=old_event.sources.copy() if old_event else [],
|
|
145
|
+
timestamps=old_event.timestamps.copy() if old_event else [],
|
|
146
|
+
event_ids=old_event.event_ids.copy() if old_event else [],
|
|
147
|
+
)
|
|
148
|
+
event.sources.append(source)
|
|
149
|
+
event.timestamps.append(datetime.now(timezone.utc))
|
|
150
|
+
event.event_ids.append(event.id)
|
|
151
|
+
await self._queue.put(event)
|
|
152
|
+
logger.debug(f"Event published: {event.name} (id={event.id})")
|
|
153
|
+
|
|
154
|
+
# 发布成功后,运行 on_publish 中间件链
|
|
155
|
+
await self._on_publish_chain(event)
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
async def _noop_on_publish(
|
|
159
|
+
event: Event,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""on_publish 链的末端处理器(空操作)"""
|
|
162
|
+
|
|
163
|
+
async def start(self) -> None:
|
|
164
|
+
"""启动事件分发循环"""
|
|
165
|
+
async with self._state_lock:
|
|
166
|
+
if self._running.is_set():
|
|
167
|
+
return
|
|
168
|
+
try:
|
|
169
|
+
self._dispatch_task = asyncio.create_task(self._dispatch_loop())
|
|
170
|
+
except Exception:
|
|
171
|
+
logger.exception("Error occurred while starting event bus")
|
|
172
|
+
raise
|
|
173
|
+
self._running.set()
|
|
174
|
+
self._enable_publish.set()
|
|
175
|
+
await self._mw_chain.setup(self)
|
|
176
|
+
logger.info("EventBus started")
|
|
177
|
+
|
|
178
|
+
async def stop(self) -> None:
|
|
179
|
+
"""停止事件总线"""
|
|
180
|
+
async with self._state_lock:
|
|
181
|
+
if not self._running.is_set():
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
await self._publish(ShutdownEvent.name, source="EventBus", data=None)
|
|
185
|
+
|
|
186
|
+
self._enable_publish.clear() # 阻止新消息入队
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
timeout: float = max(self._shutdown.queue_timeout_min,min(self._shutdown.queue_timeout_max,self._queue.qsize() * self._shutdown.avg_wait_time))
|
|
190
|
+
await asyncio.wait_for(self._queue.join(), timeout=timeout) # 等待队列处理完毕,避免丢失事件
|
|
191
|
+
except asyncio.TimeoutError:
|
|
192
|
+
logger.warning("Timeout while waiting for event queue to drain during shutdown")
|
|
193
|
+
|
|
194
|
+
if self._dispatch_task:
|
|
195
|
+
self._dispatch_task.cancel()
|
|
196
|
+
try:
|
|
197
|
+
await self._dispatch_task
|
|
198
|
+
except asyncio.CancelledError:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
await self._wait_all_tasks_done() # 等待所有处理器任务完成
|
|
202
|
+
|
|
203
|
+
self._running.clear()
|
|
204
|
+
await self._mw_chain.teardown(self)
|
|
205
|
+
logger.info("EventBus stopped")
|
|
206
|
+
|
|
207
|
+
async def __aenter__(self) -> "EventBus":
|
|
208
|
+
"""异步上下文管理器入口"""
|
|
209
|
+
await self.start()
|
|
210
|
+
return self
|
|
211
|
+
|
|
212
|
+
async def __aexit__(self, exc_type: Optional[type], exc_val: Optional[BaseException], exc_tb: Optional[types.TracebackType]) -> Optional[bool]:
|
|
213
|
+
"""异步上下文管理器出口,确保总线资源被释放且不吞没异常"""
|
|
214
|
+
try:
|
|
215
|
+
await self.stop()
|
|
216
|
+
except Exception as stop_err:
|
|
217
|
+
if exc_val is not None:
|
|
218
|
+
logger.error(f"Error during EventBus shutdown (original exception will propagate): {stop_err}")
|
|
219
|
+
else:
|
|
220
|
+
raise
|
|
221
|
+
return None # 不抑制上下文体中的异常
|
|
222
|
+
|
|
223
|
+
def proxy(self, source: str, raw_event: Optional[Event] = None) -> Proxy:
|
|
224
|
+
"""创建一个事件总线代理实例,供事件处理器调用"""
|
|
225
|
+
return EventBus.Proxy(self, source, raw_event)
|
|
226
|
+
|
|
227
|
+
async def _handler_wrapper(self, handler: EventHandler, handler_id: str, bus_proxy: 'EventBus.Proxy', event: Event) -> None:
|
|
228
|
+
"""事件处理器包装器"""
|
|
229
|
+
try:
|
|
230
|
+
async with self._handler_semaphore: # 控制并发处理器数量,避免过载
|
|
231
|
+
async with asyncio.timeout(handler.handle_timeout):
|
|
232
|
+
await handler(bus_proxy, event)
|
|
233
|
+
except Exception as e:
|
|
234
|
+
if "EventBusErrorReporter" not in event.sources:
|
|
235
|
+
try:
|
|
236
|
+
await self._publish(
|
|
237
|
+
name=TaskErrorEvent.name,
|
|
238
|
+
source="EventBusErrorReporter",
|
|
239
|
+
data=TaskErrorPayload(
|
|
240
|
+
error_event=event,
|
|
241
|
+
handler_id=handler_id,
|
|
242
|
+
handler_name=handler.__class__.__name__,
|
|
243
|
+
error_type=type(e).__name__,
|
|
244
|
+
error_message=str(e)
|
|
245
|
+
),
|
|
246
|
+
old_event = event
|
|
247
|
+
)
|
|
248
|
+
except BusShuttingDown as err:
|
|
249
|
+
logger.warning(f"Skipping task_error publish during shutdown: {err}")
|
|
250
|
+
except Exception:
|
|
251
|
+
logger.exception("Failed to publish task_error event")
|
|
252
|
+
raise e
|
|
253
|
+
|
|
254
|
+
async def _dispatch_loop(self) -> None:
|
|
255
|
+
"""事件分发主循环"""
|
|
256
|
+
await self._running.wait() # 等待事件总线启动
|
|
257
|
+
while self._running.is_set():
|
|
258
|
+
event: Optional[Event] = None
|
|
259
|
+
try:
|
|
260
|
+
event = await self._queue.get()
|
|
261
|
+
for handler_id, handler in self._handlers.get_handlers(event.name):
|
|
262
|
+
self._register_task(asyncio.create_task(self._handler_wrapper(handler, handler_id, self.proxy(handler.__class__.__name__, event), event)))
|
|
263
|
+
except Exception:
|
|
264
|
+
logger.exception("Unexpected error in dispatch loop")
|
|
265
|
+
finally:
|
|
266
|
+
if event:
|
|
267
|
+
self._queue.task_done()
|
|
268
|
+
|
|
269
|
+
def _register_task(self, task: asyncio.Task[Any]) -> None:
|
|
270
|
+
self._active_tasks.add(task)
|
|
271
|
+
task.add_done_callback(self._on_task_done)
|
|
272
|
+
|
|
273
|
+
def _on_task_done(self, task: asyncio.Task[Any]) -> None:
|
|
274
|
+
"""任务完成时的回调(在任务完成后立即触发)"""
|
|
275
|
+
self._active_tasks.discard(task)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
if exc := task.exception():
|
|
279
|
+
raise exc
|
|
280
|
+
except asyncio.CancelledError:
|
|
281
|
+
logger.debug(f"Handler task cancelled: {task.get_name()}")
|
|
282
|
+
except asyncio.InvalidStateError:
|
|
283
|
+
logger.warning(f"Task {task.get_name()} callback triggered in invalid state")
|
|
284
|
+
except Exception:
|
|
285
|
+
logger.exception(f"Handler task failed: {task.get_name()}")
|
|
286
|
+
|
|
287
|
+
async def _wait_all_tasks_done(self) -> None:
|
|
288
|
+
"""等待所有未完成的任务完成,适用于事件总线停止时调用"""
|
|
289
|
+
if self._active_tasks:
|
|
290
|
+
try:
|
|
291
|
+
logger.info(f"Waiting for {len(self._active_tasks)} active handler tasks to complete...")
|
|
292
|
+
done, pending = await asyncio.wait(self._active_tasks.copy(), return_when=asyncio.ALL_COMPLETED, timeout=self._shutdown.tasks_timeout)
|
|
293
|
+
logger.info(f"All handler tasks completed. Total: {len(done)}")
|
|
294
|
+
if pending:
|
|
295
|
+
logger.warning(f"Timeout: {len(pending)} tasks pending, cancelling...")
|
|
296
|
+
for task in pending:
|
|
297
|
+
if not task.done():
|
|
298
|
+
task.cancel()
|
|
299
|
+
try:
|
|
300
|
+
await task
|
|
301
|
+
except asyncio.CancelledError:
|
|
302
|
+
pass
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.exception("Unexpected error in wait all task done")
|
|
305
|
+
raise e
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def is_running(self) -> bool: return self._running.is_set()
|
|
309
|
+
@property
|
|
310
|
+
def is_publishing_enabled(self) -> bool: return self._enable_publish.is_set()
|
|
311
|
+
@property
|
|
312
|
+
def active_task_count(self) -> int: return len(self._active_tasks)
|
|
313
|
+
@property
|
|
314
|
+
def queue_size(self) -> int: return self._queue.qsize()
|
event_bus/event.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, ClassVar, Dict, List, Optional, Type
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from abc import ABC
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class Event(BaseModel):
|
|
11
|
+
"""事件数据类"""
|
|
12
|
+
name: str = Field(description="事件类型")
|
|
13
|
+
data: Optional[BaseModel] = Field(default=None, description="事件附加数据")
|
|
14
|
+
|
|
15
|
+
# metadata
|
|
16
|
+
id: str = Field(default_factory=lambda: uuid.uuid4().hex, description="事件UUID")
|
|
17
|
+
sources: List[str] = Field(default_factory=list, description="事件处理链")
|
|
18
|
+
timestamps: List[datetime] = Field(default_factory=lambda: [], description="事件时间戳")
|
|
19
|
+
event_ids: List[str] = Field(default_factory=list, description="因果事件ID链,支持精准重建事件流转路径")
|
|
20
|
+
|
|
21
|
+
class EventDeclaration(ABC):
|
|
22
|
+
"""事件声明抽象基类"""
|
|
23
|
+
name: ClassVar[str]
|
|
24
|
+
payload_type: ClassVar[Optional[Type[BaseModel]]] = None
|
|
25
|
+
|
|
26
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
27
|
+
super().__init_subclass__(**kwargs)
|
|
28
|
+
if not cls.name.strip():
|
|
29
|
+
raise TypeError(f"事件声明类 {cls.__name__} 必须定义非空的 `name` 属性")
|
|
30
|
+
|
|
31
|
+
class EventRegistry:
|
|
32
|
+
"""事件注册表"""
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._events: Dict[str, Type[EventDeclaration]] = {}
|
|
36
|
+
|
|
37
|
+
def register(self, event_decl: Type[EventDeclaration]) -> None:
|
|
38
|
+
"""手动注册事件声明"""
|
|
39
|
+
if event_decl.name in self._events:
|
|
40
|
+
raise ValueError(f"重复的事件声明 {event_decl.name}")
|
|
41
|
+
self._events[event_decl.name] = event_decl
|
|
42
|
+
|
|
43
|
+
def unregister(self, event_name: str) -> None:
|
|
44
|
+
"""注销事件声明"""
|
|
45
|
+
if event_name in self._events:
|
|
46
|
+
del self._events[event_name]
|
|
47
|
+
|
|
48
|
+
def get(self, name: str) -> Optional[Type[EventDeclaration]]:
|
|
49
|
+
return self._events.get(name)
|
|
50
|
+
|
|
51
|
+
def list_names(self) -> List[str]:
|
|
52
|
+
return list(self._events.keys())
|
event_bus/handler.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
import uuid
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from typing import Any, Dict, List, Optional, Pattern, TYPE_CHECKING
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
from .event import Event
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from . import EventBus
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class EventHandler(ABC):
|
|
17
|
+
"""事件处理器基类,所有具体事件处理器应继承此类"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, subscriptions: Optional[List[str]] = None, handle_timeout: Optional[float] = 1.0) -> None:
|
|
20
|
+
self.subscriptions: List[str] = subscriptions.copy() if subscriptions is not None else [] # 订阅的事件类型列表,支持正则表达式
|
|
21
|
+
self.handle_timeout: Optional[float] = handle_timeout
|
|
22
|
+
|
|
23
|
+
async def __call__(self, bus_proxy: 'EventBus.Proxy', event: Event) -> None:
|
|
24
|
+
"""事件处理器入口,自动解包事件数据"""
|
|
25
|
+
await self.handle(event.data, bus_proxy, event)
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
async def handle(self, payload: Optional[BaseModel], bus_proxy: 'EventBus.Proxy', raw_event: Event) -> None: pass
|
|
29
|
+
|
|
30
|
+
class EventHandlerRegistry:
|
|
31
|
+
"""事件处理器注册表,负责管理事件类型与处理器的映射关系"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, regex_cache_maxsize: int = 256) -> None:
|
|
34
|
+
self._regex_cache: OrderedDict[str, Pattern[str]] = OrderedDict()
|
|
35
|
+
self._regex_cache_maxsize: int = regex_cache_maxsize
|
|
36
|
+
self._handlers: Dict[str, EventHandler] = {}
|
|
37
|
+
|
|
38
|
+
def register(self, handler: EventHandler) -> str:
|
|
39
|
+
"""注册一个事件处理器实例"""
|
|
40
|
+
id = uuid.uuid4().hex
|
|
41
|
+
self._handlers[id] = handler
|
|
42
|
+
return id
|
|
43
|
+
|
|
44
|
+
def get(self, handler_id: str) -> Optional[EventHandler]:
|
|
45
|
+
"""根据ID获取事件处理器实例"""
|
|
46
|
+
return self._handlers.get(handler_id)
|
|
47
|
+
|
|
48
|
+
def unregister(self, handler_id: str) -> bool:
|
|
49
|
+
"""注销一个事件处理器实例"""
|
|
50
|
+
if handler_id in self._handlers:
|
|
51
|
+
del self._handlers[handler_id]
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def get_handlers(self, event_type: str) -> List[tuple[str, EventHandler]]:
|
|
56
|
+
"""获取匹配事件类型的所有处理器实例及其注册ID"""
|
|
57
|
+
matched: List[tuple[str, EventHandler]] = []
|
|
58
|
+
for handler_id, handler in self._handlers.items():
|
|
59
|
+
for pattern in handler.subscriptions:
|
|
60
|
+
if self._match_pattern(event_type, pattern):
|
|
61
|
+
matched.append((handler_id, handler))
|
|
62
|
+
break
|
|
63
|
+
return matched
|
|
64
|
+
|
|
65
|
+
def _match_pattern(self, event_type: str, pattern: str) -> bool:
|
|
66
|
+
"""使用正则表达式匹配事件类型(LRU 缓存编译结果,防止无限膨胀)"""
|
|
67
|
+
if pattern not in self._regex_cache:
|
|
68
|
+
if len(self._regex_cache) >= self._regex_cache_maxsize:
|
|
69
|
+
self._regex_cache.popitem(last=False) # 淘汰最旧条目
|
|
70
|
+
self._regex_cache[pattern] = re.compile(pattern)
|
|
71
|
+
else:
|
|
72
|
+
self._regex_cache.move_to_end(pattern) # 命中则标记为最近使用
|
|
73
|
+
return re.fullmatch(self._regex_cache[pattern], event_type) is not None
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def handlers_count(self) -> int:
|
|
77
|
+
return len(self._handlers)
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def all_handlers(self) -> Dict[str, EventHandler]:
|
|
81
|
+
"""获取所有注册的事件处理器实例"""
|
|
82
|
+
return self._handlers.copy()
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def regex_cache_info(self) -> Dict[str, Any]:
|
|
86
|
+
"""获取正则表达式缓存的当前状态"""
|
|
87
|
+
return {
|
|
88
|
+
"size": len(self._regex_cache),
|
|
89
|
+
"max_size": self._regex_cache_maxsize,
|
|
90
|
+
}
|