eventmanager.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. # -*- coding: utf-8 -*-
  2. """
  3. EventManager für das zentrale Pub/Sub-System.
  4. Verwaltet Event-Handler und die Verteilung von Events.
  5. """
  6. import asyncio
  7. import inspect
  8. from typing import Any, Callable, Type, TYPE_CHECKING
  9. from trixy_core.events.enums import EventPriority
  10. from trixy_core.events.event_data.base import EventData
  11. from trixy_core.events.decorators import get_event_handlers
  12. from trixy_core.utils.debug import pdebug, perror
  13. from trixy_core.utils.logging.file_router import log_as
  14. if TYPE_CHECKING:
  15. from trixy_core.application import IApplication
  16. # Events die nicht geloggt werden sollen (zu häufig/spammend)
  17. _EVENT_LOG_BLACKLIST: set[str] = {
  18. "music_output_received",
  19. "audio_output_received",
  20. }
  21. class EventHandler:
  22. """Repräsentiert einen registrierten Event-Handler."""
  23. def __init__(
  24. self,
  25. callback: Callable,
  26. priority: EventPriority,
  27. event_class: Type[EventData] | None = None,
  28. instance: Any = None,
  29. component_name: str = "core",
  30. ) -> None:
  31. self.callback = callback
  32. self.priority = priority
  33. self.event_class = event_class
  34. self.instance = instance
  35. self.component_name = component_name
  36. self.is_async = asyncio.iscoroutinefunction(callback)
  37. async def invoke(self, event_name: str, event_data: EventData) -> None:
  38. """
  39. Ruft den Handler auf.
  40. Args:
  41. event_name: Name des Events
  42. event_data: Event-Daten
  43. """
  44. try:
  45. if self.is_async:
  46. if self.instance:
  47. await self.callback(self.instance, event_name, event_data)
  48. else:
  49. await self.callback(event_name, event_data)
  50. else:
  51. if self.instance:
  52. self.callback(self.instance, event_name, event_data)
  53. else:
  54. self.callback(event_name, event_data)
  55. except Exception as e:
  56. perror(f"Fehler in Event-Handler für '{event_name}': {e}")
  57. raise
  58. class EventManager:
  59. """
  60. Zentraler Event-Manager für Trixy.
  61. Verwaltet:
  62. - Registrierung von Event-Handlern
  63. - Prioritätsbasierte Handler-Ausführung
  64. - Async Handler-Unterstützung
  65. - Event-Abbruch
  66. """
  67. def __init__(self, application: "IApplication") -> None:
  68. """
  69. Initialisiert den EventManager.
  70. Args:
  71. application: Referenz zur Hauptanwendung
  72. """
  73. self._application = application
  74. self._handlers: dict[str, list[EventHandler]] = {}
  75. self._global_handlers: list[EventHandler] = []
  76. def on(
  77. self,
  78. event_name: str,
  79. priority: EventPriority = EventPriority.NORMAL,
  80. ) -> Callable:
  81. """
  82. Decorator zum Registrieren eines Event-Handlers.
  83. Beispiel:
  84. @events.on("user_login")
  85. async def handle_login(event_name, data):
  86. print(f"User logged in: {data}")
  87. Args:
  88. event_name: Name des Events
  89. priority: Handler-Priorität
  90. Returns:
  91. Decorator-Funktion
  92. """
  93. def decorator(func: Callable) -> Callable:
  94. self.register(event_name, func, priority)
  95. return func
  96. return decorator
  97. def register(
  98. self,
  99. event_name: str,
  100. callback: Callable,
  101. priority: EventPriority = EventPriority.NORMAL,
  102. event_class: Type[EventData] | None = None
  103. ) -> None:
  104. """
  105. Registriert einen Event-Handler.
  106. Args:
  107. event_name: Name des Events
  108. callback: Handler-Funktion
  109. priority: Handler-Priorität
  110. event_class: Optionale Event-Datenklasse für Typisierung
  111. """
  112. handler = EventHandler(callback, priority, event_class)
  113. if event_name not in self._handlers:
  114. self._handlers[event_name] = []
  115. self._handlers[event_name].append(handler)
  116. self._sort_handlers(event_name)
  117. pdebug(f"Event-Handler registriert: {event_name} (Priorität: {priority.name})")
  118. def register_global(
  119. self,
  120. callback: Callable,
  121. priority: EventPriority = EventPriority.NORMAL
  122. ) -> None:
  123. """
  124. Registriert einen globalen Handler für alle Events.
  125. Args:
  126. callback: Handler-Funktion
  127. priority: Handler-Priorität
  128. """
  129. handler = EventHandler(callback, priority)
  130. self._global_handlers.append(handler)
  131. self._global_handlers.sort(key=lambda h: h.priority)
  132. pdebug(f"Globaler Event-Handler registriert (Priorität: {priority.name})")
  133. def unregister(self, event_name: str, callback: Callable) -> bool:
  134. """
  135. Entfernt einen Event-Handler.
  136. Args:
  137. event_name: Name des Events
  138. callback: Handler-Funktion
  139. Returns:
  140. True wenn der Handler entfernt wurde
  141. """
  142. if event_name not in self._handlers:
  143. return False
  144. handlers = self._handlers[event_name]
  145. for i, handler in enumerate(handlers):
  146. if handler.callback == callback:
  147. handlers.pop(i)
  148. pdebug(f"Event-Handler entfernt: {event_name}")
  149. return True
  150. return False
  151. def register_object(self, obj: Any) -> None:
  152. """
  153. Registriert alle Event-Handler eines Objekts.
  154. Durchsucht das Objekt nach Methoden mit @TrixyEvent-Dekorator.
  155. Bei TrixyPlugin-Instanzen wird component_name automatisch gesetzt.
  156. Args:
  157. obj: Das zu registrierende Objekt
  158. """
  159. # Komponenten-Name bestimmen (Plugin oder Core)
  160. from trixy_core.plugins.trixy_plugin import TrixyPlugin
  161. if isinstance(obj, TrixyPlugin):
  162. component_name = f"plugin.{obj.name}"
  163. else:
  164. component_name = "core"
  165. handlers = get_event_handlers(obj)
  166. for handler_info in handlers:
  167. event_name = handler_info["event_name"]
  168. priority = handler_info["priority"]
  169. event_class = handler_info.get("event_class")
  170. method = handler_info["method"]
  171. handler = EventHandler(
  172. callback=method,
  173. priority=priority,
  174. event_class=event_class,
  175. instance=None, # Method ist bereits gebunden
  176. component_name=component_name,
  177. )
  178. if event_name not in self._handlers:
  179. self._handlers[event_name] = []
  180. self._handlers[event_name].append(handler)
  181. self._sort_handlers(event_name)
  182. if handlers:
  183. pdebug(f"{len(handlers)} Event-Handler von {type(obj).__name__} registriert")
  184. def unregister_object(self, obj: Any) -> int:
  185. """
  186. Entfernt alle Event-Handler eines Objekts.
  187. Args:
  188. obj: Das Objekt, dessen Handler entfernt werden sollen
  189. Returns:
  190. Anzahl der entfernten Handler
  191. """
  192. removed = 0
  193. for event_name in list(self._handlers.keys()):
  194. handlers = self._handlers[event_name]
  195. original_count = len(handlers)
  196. self._handlers[event_name] = [
  197. h for h in handlers
  198. if h.instance != obj and not (
  199. hasattr(h.callback, "__self__") and h.callback.__self__ == obj
  200. )
  201. ]
  202. removed += original_count - len(self._handlers[event_name])
  203. if removed:
  204. pdebug(f"{removed} Event-Handler von {type(obj).__name__} entfernt")
  205. return removed
  206. def _sort_handlers(self, event_name: str) -> None:
  207. """Sortiert Handler nach Priorität."""
  208. if event_name in self._handlers:
  209. self._handlers[event_name].sort(key=lambda h: h.priority)
  210. async def trigger(
  211. self,
  212. event_name: str,
  213. event_data: EventData | None = None
  214. ) -> EventData:
  215. """
  216. Löst ein Event aus.
  217. Args:
  218. event_name: Name des Events
  219. event_data: Event-Daten (oder None für Standard-EventData)
  220. Returns:
  221. Die Event-Daten (möglicherweise modifiziert oder abgebrochen)
  222. """
  223. if event_data is None:
  224. event_data = EventData()
  225. # Kombiniere spezifische und globale Handler
  226. all_handlers: list[EventHandler] = []
  227. if event_name in self._handlers:
  228. all_handlers.extend(self._handlers[event_name])
  229. all_handlers.extend(self._global_handlers)
  230. all_handlers.sort(key=lambda h: h.priority)
  231. for handler in all_handlers:
  232. if event_data.is_cancelled():
  233. # MONITOR-Handler werden trotzdem ausgeführt
  234. if handler.priority != EventPriority.MONITOR:
  235. continue
  236. try:
  237. with log_as(handler.component_name):
  238. await handler.invoke(event_name, event_data)
  239. except Exception as e:
  240. perror(f"Handler-Fehler bei '{event_name}': {e}")
  241. if handler.priority == EventPriority.SYSTEM:
  242. raise
  243. if event_name not in _EVENT_LOG_BLACKLIST:
  244. pdebug(f"Event ausgelöst: {event_name}")
  245. return event_data
  246. async def emit(
  247. self,
  248. event_name: str,
  249. data: dict | None = None,
  250. source: str = "",
  251. ) -> EventData:
  252. """
  253. Einfache Methode zum Auslösen eines Events mit Dict-Daten.
  254. Dies ist eine vereinfachte Alternative zu trigger(), die ein Dict
  255. akzeptiert und automatisch in EventData konvertiert.
  256. Beispiel:
  257. await events.emit("user_login", {"user_id": 123, "name": "Max"})
  258. Args:
  259. event_name: Name des Events
  260. data: Event-Daten als Dictionary
  261. source: Optionale Event-Quelle
  262. Returns:
  263. Die Event-Daten
  264. """
  265. event_data = EventData(source=source)
  266. if data:
  267. event_data.metadata.update(data)
  268. return await self.trigger(event_name, event_data)
  269. def trigger_sync(
  270. self,
  271. event_name: str,
  272. event_data: EventData | None = None
  273. ) -> EventData:
  274. """
  275. Löst ein Event synchron aus (für Nicht-Async-Kontexte).
  276. Args:
  277. event_name: Name des Events
  278. event_data: Event-Daten
  279. Returns:
  280. Die Event-Daten
  281. """
  282. loop = asyncio.get_event_loop()
  283. if loop.is_running():
  284. # Erstelle Future für async Ausführung
  285. future = asyncio.ensure_future(self.trigger(event_name, event_data))
  286. return event_data # Kann nicht warten, gib sofort zurück
  287. else:
  288. return loop.run_until_complete(self.trigger(event_name, event_data))
  289. def has_handlers(self, event_name: str) -> bool:
  290. """
  291. Prüft, ob Handler für ein Event registriert sind.
  292. Args:
  293. event_name: Name des Events
  294. Returns:
  295. True wenn Handler existieren
  296. """
  297. return event_name in self._handlers and len(self._handlers[event_name]) > 0
  298. def get_registered_events(self) -> list[str]:
  299. """
  300. Gibt alle Event-Namen zurück, für die Handler registriert sind.
  301. Returns:
  302. Liste der Event-Namen
  303. """
  304. return list(self._handlers.keys())
  305. def clear_all(self) -> None:
  306. """Entfernt alle registrierten Handler."""
  307. self._handlers.clear()
  308. self._global_handlers.clear()
  309. pdebug("Alle Event-Handler entfernt")