PMFlow/backend/app/events.py

374 lines
11 KiB
Python

"""
Neural Sync - Event Bus System
实现事件驱动架构的核心组件,支持异步事件发布/订阅模式。
"""
from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime
from typing import Callable, Dict, List, Any, Optional
import asyncio
import logging
from contextlib import asynccontextmanager
from .utils.timezone import now_cn_aware
logger = logging.getLogger(__name__)
class EventType(Enum):
"""系统事件类型定义"""
# 信号事件
SIGNAL_INGESTED = "signal.ingested"
SIGNAL_ANALYZED = "signal.analyzed"
SIGNAL_FEEDBACK = "signal.feedback"
SIGNAL_PRIORITY_CHANGED = "signal.priority_changed"
# 任务事件
TASK_ASSIGNED = "task.assigned"
TASK_STARTED = "task.started"
TASK_COMPLETED = "task.completed"
TASK_FAILED = "task.failed"
# 代码事件
CODE_COMMIT = "code.commit"
CODE_REVIEW_REQUESTED = "code.review_requested"
CODE_DRAFT_GENERATED = "code.draft_generated"
# 审计事件
AUDIT_STARTED = "audit.started"
AUDIT_COMPLETED = "audit.completed"
AUDIT_FAILED = "audit.failed"
# 合并事件
AUTO_MERGE_REQUESTED = "auto_merge.requested"
AUTO_MERGE_SUCCESS = "auto_merge.success"
AUTO_MERGE_FAILED = "auto_merge.failed"
AUTO_MERGE_COOLDOWN = "auto_merge.cooldown"
# 集成事件
PLANE_SYNC_SUCCESS = "integration.plane.success"
PLANE_SYNC_FAILED = "integration.plane.failed"
LARK_NOTIFY_SUCCESS = "integration.lark.success"
LARK_NOTIFY_FAILED = "integration.lark.failed"
# 项目域事件
DOMAIN_CREATED = "domain.created"
DOMAIN_UPDATED = "domain.updated"
DOMAIN_DELETED = "domain.deleted"
DOMAIN_SWITCHED = "domain.switched"
DOMAIN_SYNCING = "domain.syncing"
DOMAIN_SYNCED = "domain.synced"
# 项目事件
PROJECT_CREATED = "project.created"
PROJECT_UPDATED = "project.updated"
PROJECT_DELETED = "project.deleted"
SIGNAL_LINKED = "signal.linked"
SIGNAL_UNLINKED = "signal.unlinked"
@dataclass
class Event:
"""事件数据结构"""
type: EventType
data: Dict[str, Any]
timestamp: float = field(default_factory=lambda: now_cn_aware().timestamp())
source: Optional[str] = None
correlation_id: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式"""
return {
"type": self.type.value,
"data": self.data,
"timestamp": self.timestamp,
"source": self.source,
"correlation_id": self.correlation_id,
"metadata": self.metadata,
}
class EventBus:
"""
事件总线 - 核心事件分发系统
特性:
- 异步事件发布
- 多订阅者支持
- 事件过滤
- 错误处理
- 性能监控
"""
def __init__(self):
self._subscribers: Dict[EventType, List[Callable]] = {}
self._wildcard_subscribers: List[Callable] = []
self._event_history: List[Event] = []
self._max_history = 1000
self._metrics = {
"published": 0,
"delivered": 0,
"failed": 0,
}
self._lock = asyncio.Lock()
self._running = False
@property
def metrics(self) -> Dict[str, int]:
"""获取事件指标"""
return self._metrics.copy()
@property
def history(self) -> List[Dict[str, Any]]:
"""获取事件历史"""
return [e.to_dict() for e in self._event_history]
async def start(self):
"""启动事件总线"""
self._running = True
logger.info("EventBus started")
async def stop(self):
"""停止事件总线"""
self._running = False
logger.info("EventBus stopped")
def subscribe(
self,
event_type: EventType,
handler: Callable,
filter_func: Optional[Callable[[Event], bool]] = None,
) -> Callable:
"""
订阅事件
Args:
event_type: 事件类型
handler: 事件处理函数 (async def handler(event: Event) -> None)
filter_func: 事件过滤函数 (def filter(event: Event) -> bool)
Returns:
取消订阅的函数
"""
async def wrapped_handler(event: Event):
if filter_func and not filter_func(event):
return
try:
await handler(event)
self._metrics["delivered"] += 1
except Exception as e:
logger.error(
f"Error in event handler for {event_type.value}: {e}",
exc_info=True,
)
self._metrics["failed"] += 1
# 包装为原始 handler 用于取消订阅
wrapped_handler.original = handler # type: ignore
async def _subscribe():
async with self._lock:
if event_type not in self._subscribers:
self._subscribers[event_type] = []
self._subscribers[event_type].append(wrapped_handler)
asyncio.create_task(_subscribe())
def unsubscribe():
asyncio.create_task(self._unsubscribe(event_type, wrapped_handler))
return unsubscribe
def subscribe_wildcard(
self,
handler: Callable,
filter_func: Optional[Callable[[Event], bool]] = None,
) -> Callable:
"""订阅所有事件(通配符订阅)"""
async def wrapped_handler(event: Event):
if filter_func and not filter_func(event):
return
try:
await handler(event)
self._metrics["delivered"] += 1
except Exception as e:
logger.error(
f"Error in wildcard event handler: {e}",
exc_info=True,
)
self._metrics["failed"] += 1
wrapped_handler.original = handler # type: ignore
async def _subscribe():
async with self._lock:
self._wildcard_subscribers.append(wrapped_handler)
asyncio.create_task(_subscribe())
def unsubscribe():
asyncio.create_task(self._unsubscribe_wildcard(wrapped_handler))
return unsubscribe
async def _unsubscribe(self, event_type: EventType, handler: Callable):
"""取消订阅"""
async with self._lock:
if event_type in self._subscribers:
self._subscribers[event_type] = [
h for h in self._subscribers[event_type] if h != handler
]
async def _unsubscribe_wildcard(self, handler: Callable):
"""取消通配符订阅"""
async with self._lock:
self._wildcard_subscribers = [
h for h in self._wildcard_subscribers if h != handler
]
async def publish(self, event: Event) -> bool:
"""
发布事件
Args:
event: 事件对象
Returns:
是否成功发布
"""
if not self._running:
logger.warning(f"EventBus not running, discarding event: {event.type.value}")
return False
self._metrics["published"] += 1
# 记录历史
self._event_history.append(event)
if len(self._event_history) > self._max_history:
self._event_history.pop(0)
# 通知订阅者
tasks = []
# 特定事件订阅者
if event.type in self._subscribers:
for handler in self._subscribers[event.type]:
tasks.append(handler(event))
# 通配符订阅者
for handler in self._wildcard_subscribers:
tasks.append(handler(event))
if tasks:
results = await asyncio.gather(*tasks, return_exceptions=True)
failed = sum(1 for r in results if isinstance(r, Exception))
if failed > 0:
self._metrics["failed"] += failed
logger.debug(
f"Published event {event.type.value} to {len(tasks)} subscribers"
)
return True
async def publish_and_wait(
self, event: Event, timeout: float = 5.0
) -> List[Any]:
"""
发布事件并等待所有处理完成
Args:
event: 事件对象
timeout: 超时时间(秒)
Returns:
所有处理器的返回值列表
"""
if not self._running:
raise RuntimeError("EventBus not running")
self._metrics["published"] += 1
self._event_history.append(event)
tasks = []
if event.type in self._subscribers:
tasks.extend(self._subscribers[event.type])
for handler in self._wildcard_subscribers:
tasks.append(handler(event))
if not tasks:
return []
try:
results = await asyncio.wait_for(
asyncio.gather(*tasks, return_exceptions=True),
timeout=timeout,
)
return results
except asyncio.TimeoutError:
logger.warning(
f"Event {event.type.value} processing timed out after {timeout}s"
)
raise
def clear_history(self):
"""清空事件历史"""
self._event_history.clear()
def get_subscriber_count(self, event_type: Optional[EventType] = None) -> int:
"""获取订阅者数量"""
if event_type:
return len(self._subscribers.get(event_type, []))
return sum(len(subs) for subs in self._subscribers.values()) + len(
self._wildcard_subscribers
)
# 全局事件总线实例
_global_event_bus: Optional[EventBus] = None
def get_event_bus() -> EventBus:
"""获取全局事件总线实例"""
global _global_event_bus
if _global_event_bus is None:
_global_event_bus = EventBus()
return _global_event_bus
@asynccontextmanager
async def event_bus_context():
"""事件总线上下文管理器"""
bus = get_event_bus()
await bus.start()
try:
yield bus
finally:
await bus.stop()
# 便捷函数
async def publish_event(event_type: EventType, data: Dict[str, Any], **kwargs) -> bool:
"""发布事件的便捷函数"""
event = Event(type=event_type, data=data, **kwargs)
return await get_event_bus().publish(event)
def subscribe_event(
event_type: EventType, handler: Callable
) -> Callable:
"""订阅事件的便捷函数"""
return get_event_bus().subscribe(event_type, handler)
def subscribe_all_events(handler: Callable) -> Callable:
"""订阅所有事件的便捷函数"""
return get_event_bus().subscribe_wildcard(handler)