| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- # -*- 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")
|