PMFlow/backend/app/events.py

373 lines
10 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
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: datetime.utcnow().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)