# backend/app/automation/event_bus.py # event_bus 정본(단일 고정) — post-mvp-overview.md §5. # `from app.automation.event_bus import bus` (bus.publish('x', {...})) # `from app.automation.event_bus import publish` (publish('x', {...}), emit 동일) # 둘 다 정상. app/events.py·app/event_bus.py 같은 다른 경로는 정본이 아니다. from __future__ import annotations from collections import defaultdict from collections.abc import Callable from dataclasses import dataclass, field from datetime import UTC, datetime @dataclass class Event: type: str payload: dict = field(default_factory=dict) at: datetime = field(default_factory=lambda: datetime.now(UTC)) class EventBus: """in-process 동기 이벤트 버스. 핸들러는 (Event) -> None. 프로토타입은 publish 즉시 동기 디스패치. phase-14 worker 가 비동기/스케줄로 확장.""" def __init__(self) -> None: self._subs: dict[str, list[Callable[[Event], None]]] = defaultdict(list) self.history: list[Event] = [] # 테스트/디버그용 발행 기록 def subscribe(self, event_type: str, handler: Callable[[Event], None]) -> None: self._subs[event_type].append(handler) def publish(self, event_type: str, payload: dict | None = None) -> Event: ev = Event(type=event_type, payload=payload or {}) self.history.append(ev) for h in list(self._subs.get(event_type, [])): try: h(ev) # 한 구독자의 예외가 다른 구독자를 막지 않도록 격리 except Exception: pass return ev # 전역 단일 버스(데모). 테스트는 새 인스턴스를 주입해 격리한다. bus = EventBus() # 모듈 레벨 편의 export — post-mvp-overview.md event_bus 정본. publish = bus.publish # publish('automation.matched', {...}) subscribe = bus.subscribe # subscribe('automation.matched', handler) emit = bus.publish # emit 은 publish 의 별칭(동일 동작)