# -*- coding: utf-8 -*- """ EventManager für das zentrale Pub/Sub-System. Verwaltet Event-Handler und die Verteilung von Events. """ import asyncio import inspect from typing import Any, Callable, Type, TYPE_CHECKING from trixy_core.events.enums import EventPriority from trixy_core.events.event_data.base import EventData from trixy_core.events.decorators import get_event_handlers from trixy_core.utils.debug import pdebug, perror from trixy_core.utils.logging.file_router import log_as if TYPE_CHECKING: from trixy_core.application import IApplication # Events die nicht geloggt werden sollen (zu häufig/spammend) _EVENT_LOG_BLACKLIST: set[str] = { "music_output_received", "audio_output_received", } class EventHandler: """Repräsentiert einen registrierten Event-Handler.""" def __init__( self, callback: Callable, priority: EventPriority, event_class: Type[EventData] | None = None, instance: Any = None, component_name: str = "core", ) -> None: self.callback = callback self.priority = priority self.event_class = event_class self.instance = instance self.component_name = component_name self.is_async = asyncio.iscoroutinefunction(callback) async def invoke(self, event_name: str, event_data: EventData) -> None: """ Ruft den Handler auf. Args: event_name: Name des Events event_data: Event-Daten """ try: if self.is_async: if self.instance: await self.callback(self.instance, event_name, event_data) else: await self.callback(event_name, event_data) else: if self.instance: self.callback(self.instance, event_name, event_data) else: self.callback(event_name, event_data) except Exception as e: perror(f"Fehler in Event-Handler für '{event_name}': {e}") raise class EventManager: """ Zentraler Event-Manager für Trixy. Verwaltet: - Registrierung von Event-Handlern - Prioritätsbasierte Handler-Ausführung - Async Handler-Unterstützung - Event-Abbruch """ def __init__(self, application: "IApplication") -> None: """ Initialisiert den EventManager. Args: application: Referenz zur Hauptanwendung """ self._application = application self._handlers: dict[str, list[EventHandler]] = {} self._global_handlers: list[EventHandler] = [] def on( self, event_name: str, priority: EventPriority = EventPriority.NORMAL, ) -> Callable: """ Decorator zum Registrieren eines Event-Handlers. Beispiel: @events.on("user_login") async def handle_login(event_name, data): print(f"User logged in: {data}") Args: event_name: Name des Events priority: Handler-Priorität Returns: Decorator-Funktion """ def decorator(func: Callable) -> Callable: self.register(event_name, func, priority) return func return decorator def register( self, event_name: str, callback: Callable, priority: EventPriority = EventPriority.NORMAL, event_class: Type[EventData] | None = None ) -> None: """ Registriert einen Event-Handler. Args: event_name: Name des Events callback: Handler-Funktion priority: Handler-Priorität event_class: Optionale Event-Datenklasse für Typisierung """ handler = EventHandler(callback, priority, event_class) if event_name not in self._handlers: self._handlers[event_name] = [] self._handlers[event_name].append(handler) self._sort_handlers(event_name) pdebug(f"Event-Handler registriert: {event_name} (Priorität: {priority.name})") def register_global( self, callback: Callable, priority: EventPriority = EventPriority.NORMAL ) -> None: """ Registriert einen globalen Handler für alle Events. Args: callback: Handler-Funktion priority: Handler-Priorität """ handler = EventHandler(callback, priority) self._global_handlers.append(handler) self._global_handlers.sort(key=lambda h: h.priority) pdebug(f"Globaler Event-Handler registriert (Priorität: {priority.name})") def unregister(self, event_name: str, callback: Callable) -> bool: """ Entfernt einen Event-Handler. Args: event_name: Name des Events callback: Handler-Funktion Returns: True wenn der Handler entfernt wurde """ if event_name not in self._handlers: return False handlers = self._handlers[event_name] for i, handler in enumerate(handlers): if handler.callback == callback: handlers.pop(i) pdebug(f"Event-Handler entfernt: {event_name}") return True return False def register_object(self, obj: Any) -> None: """ Registriert alle Event-Handler eines Objekts. Durchsucht das Objekt nach Methoden mit @TrixyEvent-Dekorator. Bei TrixyPlugin-Instanzen wird component_name automatisch gesetzt. Args: obj: Das zu registrierende Objekt """ # Komponenten-Name bestimmen (Plugin oder Core) from trixy_core.plugins.trixy_plugin import TrixyPlugin if isinstance(obj, TrixyPlugin): component_name = f"plugin.{obj.name}" else: component_name = "core" handlers = get_event_handlers(obj) for handler_info in handlers: event_name = handler_info["event_name"] priority = handler_info["priority"] event_class = handler_info.get("event_class") method = handler_info["method"] handler = EventHandler( callback=method, priority=priority, event_class=event_class, instance=None, # Method ist bereits gebunden component_name=component_name, ) if event_name not in self._handlers: self._handlers[event_name] = [] self._handlers[event_name].append(handler) self._sort_handlers(event_name) if handlers: pdebug(f"{len(handlers)} Event-Handler von {type(obj).__name__} registriert") def unregister_object(self, obj: Any) -> int: """ Entfernt alle Event-Handler eines Objekts. Args: obj: Das Objekt, dessen Handler entfernt werden sollen Returns: Anzahl der entfernten Handler """ removed = 0 for event_name in list(self._handlers.keys()): handlers = self._handlers[event_name] original_count = len(handlers) self._handlers[event_name] = [ h for h in handlers if h.instance != obj and not ( hasattr(h.callback, "__self__") and h.callback.__self__ == obj ) ] removed += original_count - len(self._handlers[event_name]) if removed: pdebug(f"{removed} Event-Handler von {type(obj).__name__} entfernt") return removed def _sort_handlers(self, event_name: str) -> None: """Sortiert Handler nach Priorität.""" if event_name in self._handlers: self._handlers[event_name].sort(key=lambda h: h.priority) async def trigger( self, event_name: str, event_data: EventData | None = None ) -> EventData: """ Löst ein Event aus. Args: event_name: Name des Events event_data: Event-Daten (oder None für Standard-EventData) Returns: Die Event-Daten (möglicherweise modifiziert oder abgebrochen) """ if event_data is None: event_data = EventData() # Kombiniere spezifische und globale Handler all_handlers: list[EventHandler] = [] if event_name in self._handlers: all_handlers.extend(self._handlers[event_name]) all_handlers.extend(self._global_handlers) all_handlers.sort(key=lambda h: h.priority) for handler in all_handlers: if event_data.is_cancelled(): # MONITOR-Handler werden trotzdem ausgeführt if handler.priority != EventPriority.MONITOR: continue try: with log_as(handler.component_name): await handler.invoke(event_name, event_data) except Exception as e: perror(f"Handler-Fehler bei '{event_name}': {e}") if handler.priority == EventPriority.SYSTEM: raise if event_name not in _EVENT_LOG_BLACKLIST: pdebug(f"Event ausgelöst: {event_name}") return event_data async def emit( self, event_name: str, data: dict | None = None, source: str = "", ) -> EventData: """ Einfache Methode zum Auslösen eines Events mit Dict-Daten. Dies ist eine vereinfachte Alternative zu trigger(), die ein Dict akzeptiert und automatisch in EventData konvertiert. Beispiel: await events.emit("user_login", {"user_id": 123, "name": "Max"}) Args: event_name: Name des Events data: Event-Daten als Dictionary source: Optionale Event-Quelle Returns: Die Event-Daten """ event_data = EventData(source=source) if data: event_data.metadata.update(data) return await self.trigger(event_name, event_data) def trigger_sync( self, event_name: str, event_data: EventData | None = None ) -> EventData: """ Löst ein Event synchron aus (für Nicht-Async-Kontexte). Args: event_name: Name des Events event_data: Event-Daten Returns: Die Event-Daten """ loop = asyncio.get_event_loop() if loop.is_running(): # Erstelle Future für async Ausführung future = asyncio.ensure_future(self.trigger(event_name, event_data)) return event_data # Kann nicht warten, gib sofort zurück else: return loop.run_until_complete(self.trigger(event_name, event_data)) def has_handlers(self, event_name: str) -> bool: """ Prüft, ob Handler für ein Event registriert sind. Args: event_name: Name des Events Returns: True wenn Handler existieren """ return event_name in self._handlers and len(self._handlers[event_name]) > 0 def get_registered_events(self) -> list[str]: """ Gibt alle Event-Namen zurück, für die Handler registriert sind. Returns: Liste der Event-Namen """ return list(self._handlers.keys()) def clear_all(self) -> None: """Entfernt alle registrierten Handler.""" self._handlers.clear() self._global_handlers.clear() pdebug("Alle Event-Handler entfernt")