from __future__ import annotations import logging from collections import defaultdict from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor logger = logging.getLogger("iti.events") class EventBus: def __init__(self, *, max_workers: int = 10) -> None: self._handlers: dict[str, list[Callable]] = defaultdict(list) self._executor = ThreadPoolExecutor(max_workers=max_workers) def on(self, event_name: str): def decorator(func: Callable) -> Callable: self.register_handler(event_name, func) return func return decorator def register_handler(self, event_name: str, handler: Callable) -> None: self._handlers[event_name].append(handler) def emit(self, event_name: str, *args, async_mode: bool = False, **kwargs) -> None: for handler in list(self._handlers.get(event_name, [])): if async_mode: self._executor.submit(self._run_handler, handler, *args, **kwargs) else: self._run_handler(handler, *args, **kwargs) def _run_handler(self, handler: Callable, *args, **kwargs) -> None: try: handler(*args, **kwargs) except Exception: logger.exception("event handler failed: %s", handler) eventbus = EventBus()