base.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. # -*- coding: utf-8 -*-
  2. """
  3. Basis-Middleware-Klassen für Events.
  4. Definiert die abstrakte Middleware und die Pipeline-Verwaltung.
  5. """
  6. from __future__ import annotations
  7. import asyncio
  8. from abc import ABC, abstractmethod
  9. from dataclasses import dataclass, field
  10. from datetime import datetime
  11. from enum import Enum, auto
  12. from typing import TYPE_CHECKING, Any, Callable, Coroutine
  13. if TYPE_CHECKING:
  14. from trixy_core.events.event_data.base import EventData
  15. class MiddlewareAction(Enum):
  16. """
  17. Aktionen die eine Middleware ausführen kann.
  18. """
  19. CONTINUE = auto()
  20. """Normale Fortsetzung der Pipeline."""
  21. SKIP_HANDLERS = auto()
  22. """Überspringt die Event-Handler, führt aber After-Hooks aus."""
  23. ABORT = auto()
  24. """Bricht die gesamte Pipeline ab."""
  25. RETRY = auto()
  26. """Wiederholt den aktuellen Schritt."""
  27. @dataclass
  28. class MiddlewareResult:
  29. """
  30. Ergebnis einer Middleware-Ausführung.
  31. """
  32. action: MiddlewareAction = MiddlewareAction.CONTINUE
  33. """Aktion für die Pipeline."""
  34. modified_data: "EventData | None" = None
  35. """Modifizierte Event-Daten (oder None wenn unverändert)."""
  36. error: Exception | None = None
  37. """Fehler, falls aufgetreten."""
  38. metadata: dict[str, Any] = field(default_factory=dict)
  39. """Zusätzliche Metadaten."""
  40. @property
  41. def should_continue(self) -> bool:
  42. """Prüft, ob die Pipeline fortgesetzt werden soll."""
  43. return self.action == MiddlewareAction.CONTINUE
  44. @property
  45. def should_skip(self) -> bool:
  46. """Prüft, ob Handler übersprungen werden sollen."""
  47. return self.action == MiddlewareAction.SKIP_HANDLERS
  48. @property
  49. def should_abort(self) -> bool:
  50. """Prüft, ob die Pipeline abgebrochen werden soll."""
  51. return self.action == MiddlewareAction.ABORT
  52. @dataclass
  53. class MiddlewareContext:
  54. """
  55. Kontext für die Middleware-Ausführung.
  56. Enthält Informationen über das aktuelle Event und
  57. ermöglicht die Kommunikation zwischen Middlewares.
  58. """
  59. event_name: str
  60. """Name des Events."""
  61. event_data: "EventData"
  62. """Die Event-Daten."""
  63. started_at: datetime = field(default_factory=datetime.now)
  64. """Startzeitpunkt der Verarbeitung."""
  65. properties: dict[str, Any] = field(default_factory=dict)
  66. """Geteilte Eigenschaften zwischen Middlewares."""
  67. errors: list[Exception] = field(default_factory=list)
  68. """Gesammelte Fehler."""
  69. skipped: bool = False
  70. """Ob Handler übersprungen wurden."""
  71. aborted: bool = False
  72. """Ob die Pipeline abgebrochen wurde."""
  73. def set_property(self, key: str, value: Any) -> None:
  74. """
  75. Setzt eine Eigenschaft im Kontext.
  76. Args:
  77. key: Schlüssel.
  78. value: Wert.
  79. """
  80. self.properties[key] = value
  81. def get_property(self, key: str, default: Any = None) -> Any:
  82. """
  83. Gibt eine Eigenschaft zurück.
  84. Args:
  85. key: Schlüssel.
  86. default: Standardwert.
  87. Returns:
  88. Der Wert oder der Standardwert.
  89. """
  90. return self.properties.get(key, default)
  91. @property
  92. def elapsed_ms(self) -> float:
  93. """Verstrichene Zeit in Millisekunden."""
  94. return (datetime.now() - self.started_at).total_seconds() * 1000
  95. class EventMiddleware(ABC):
  96. """
  97. Abstrakte Basisklasse für Event-Middleware.
  98. Eine Middleware kann vor (before) und nach (after) der
  99. Event-Verarbeitung Code ausführen.
  100. Example:
  101. class MyMiddleware(EventMiddleware):
  102. async def before(self, context: MiddlewareContext) -> MiddlewareResult:
  103. print(f"Vor Event: {context.event_name}")
  104. return MiddlewareResult()
  105. async def after(self, context: MiddlewareContext) -> None:
  106. print(f"Nach Event: {context.event_name}")
  107. """
  108. def __init__(
  109. self,
  110. name: str | None = None,
  111. enabled: bool = True,
  112. ) -> None:
  113. """
  114. Initialisiert die Middleware.
  115. Args:
  116. name: Name der Middleware (Standard: Klassenname).
  117. enabled: Ob die Middleware aktiv ist.
  118. """
  119. self._name = name or self.__class__.__name__
  120. self._enabled = enabled
  121. @property
  122. def name(self) -> str:
  123. """Name der Middleware."""
  124. return self._name
  125. @property
  126. def enabled(self) -> bool:
  127. """Ob die Middleware aktiv ist."""
  128. return self._enabled
  129. @enabled.setter
  130. def enabled(self, value: bool) -> None:
  131. """Setzt den Aktivierungsstatus."""
  132. self._enabled = value
  133. def should_process(self, event_name: str) -> bool:
  134. """
  135. Prüft, ob das Event verarbeitet werden soll.
  136. Kann überschrieben werden, um Events zu filtern.
  137. Args:
  138. event_name: Name des Events.
  139. Returns:
  140. True wenn das Event verarbeitet werden soll.
  141. """
  142. return True
  143. @abstractmethod
  144. async def before(
  145. self,
  146. context: MiddlewareContext,
  147. ) -> MiddlewareResult:
  148. """
  149. Wird vor der Event-Verarbeitung aufgerufen.
  150. Args:
  151. context: Der Middleware-Kontext.
  152. Returns:
  153. Ergebnis mit der gewünschten Aktion.
  154. """
  155. pass
  156. async def after(
  157. self,
  158. context: MiddlewareContext,
  159. ) -> None:
  160. """
  161. Wird nach der Event-Verarbeitung aufgerufen.
  162. Args:
  163. context: Der Middleware-Kontext.
  164. """
  165. pass
  166. async def on_error(
  167. self,
  168. context: MiddlewareContext,
  169. error: Exception,
  170. ) -> None:
  171. """
  172. Wird bei einem Fehler aufgerufen.
  173. Args:
  174. context: Der Middleware-Kontext.
  175. error: Der aufgetretene Fehler.
  176. """
  177. pass
  178. class PassThroughMiddleware(EventMiddleware):
  179. """
  180. Einfache Middleware die nichts tut.
  181. Nützlich als Platzhalter oder Basis für einfache Implementierungen.
  182. """
  183. async def before(
  184. self,
  185. context: MiddlewareContext,
  186. ) -> MiddlewareResult:
  187. """Gibt einfach CONTINUE zurück."""
  188. return MiddlewareResult()
  189. class MiddlewarePipeline:
  190. """
  191. Pipeline für die Ausführung von Middlewares.
  192. Verwaltet eine geordnete Liste von Middlewares und führt
  193. sie in der richtigen Reihenfolge aus.
  194. Example:
  195. pipeline = MiddlewarePipeline()
  196. pipeline.add(LoggingMiddleware())
  197. pipeline.add(TimingMiddleware())
  198. pipeline.add(ValidationMiddleware())
  199. # Event verarbeiten
  200. async def process_event(event_name, event_data):
  201. context = MiddlewareContext(event_name, event_data)
  202. # Before-Phase
  203. result = await pipeline.run_before(context)
  204. if result.should_abort:
  205. return
  206. # Handler ausführen
  207. if not result.should_skip:
  208. await run_handlers(event_name, event_data)
  209. # After-Phase
  210. await pipeline.run_after(context)
  211. """
  212. def __init__(self) -> None:
  213. """Initialisiert die Pipeline."""
  214. self._middlewares: list[EventMiddleware] = []
  215. @property
  216. def middlewares(self) -> list[EventMiddleware]:
  217. """Gibt die Middleware-Liste zurück."""
  218. return list(self._middlewares)
  219. @property
  220. def count(self) -> int:
  221. """Anzahl der Middlewares."""
  222. return len(self._middlewares)
  223. def add(
  224. self,
  225. middleware: EventMiddleware,
  226. position: int | None = None,
  227. ) -> None:
  228. """
  229. Fügt eine Middleware hinzu.
  230. Args:
  231. middleware: Die Middleware.
  232. position: Position in der Liste (None = Ende).
  233. """
  234. if position is None:
  235. self._middlewares.append(middleware)
  236. else:
  237. self._middlewares.insert(position, middleware)
  238. def remove(self, middleware: EventMiddleware) -> bool:
  239. """
  240. Entfernt eine Middleware.
  241. Args:
  242. middleware: Die Middleware.
  243. Returns:
  244. True wenn entfernt.
  245. """
  246. try:
  247. self._middlewares.remove(middleware)
  248. return True
  249. except ValueError:
  250. return False
  251. def remove_by_name(self, name: str) -> bool:
  252. """
  253. Entfernt eine Middleware nach Name.
  254. Args:
  255. name: Name der Middleware.
  256. Returns:
  257. True wenn entfernt.
  258. """
  259. for i, mw in enumerate(self._middlewares):
  260. if mw.name == name:
  261. self._middlewares.pop(i)
  262. return True
  263. return False
  264. def get(self, name: str) -> EventMiddleware | None:
  265. """
  266. Gibt eine Middleware nach Name zurück.
  267. Args:
  268. name: Name der Middleware.
  269. Returns:
  270. Die Middleware oder None.
  271. """
  272. for mw in self._middlewares:
  273. if mw.name == name:
  274. return mw
  275. return None
  276. def clear(self) -> None:
  277. """Entfernt alle Middlewares."""
  278. self._middlewares.clear()
  279. async def run_before(
  280. self,
  281. context: MiddlewareContext,
  282. ) -> MiddlewareResult:
  283. """
  284. Führt die Before-Phase aller Middlewares aus.
  285. Args:
  286. context: Der Middleware-Kontext.
  287. Returns:
  288. Das aggregierte Ergebnis.
  289. """
  290. final_result = MiddlewareResult()
  291. for middleware in self._middlewares:
  292. if not middleware.enabled:
  293. continue
  294. if not middleware.should_process(context.event_name):
  295. continue
  296. try:
  297. result = await middleware.before(context)
  298. # Aktualisiere Event-Daten wenn modifiziert
  299. if result.modified_data is not None:
  300. context.event_data = result.modified_data
  301. # Prüfe Aktion
  302. if result.should_abort:
  303. context.aborted = True
  304. return result
  305. if result.should_skip:
  306. context.skipped = True
  307. final_result.action = MiddlewareAction.SKIP_HANDLERS
  308. except Exception as e:
  309. context.errors.append(e)
  310. await middleware.on_error(context, e)
  311. return final_result
  312. async def run_after(
  313. self,
  314. context: MiddlewareContext,
  315. ) -> None:
  316. """
  317. Führt die After-Phase aller Middlewares aus.
  318. Middlewares werden in umgekehrter Reihenfolge ausgeführt.
  319. Args:
  320. context: Der Middleware-Kontext.
  321. """
  322. # Umgekehrte Reihenfolge für After-Phase
  323. for middleware in reversed(self._middlewares):
  324. if not middleware.enabled:
  325. continue
  326. if not middleware.should_process(context.event_name):
  327. continue
  328. try:
  329. await middleware.after(context)
  330. except Exception as e:
  331. context.errors.append(e)
  332. await middleware.on_error(context, e)
  333. async def run_on_error(
  334. self,
  335. context: MiddlewareContext,
  336. error: Exception,
  337. ) -> None:
  338. """
  339. Führt die Error-Handler aller Middlewares aus.
  340. Args:
  341. context: Der Middleware-Kontext.
  342. error: Der aufgetretene Fehler.
  343. """
  344. for middleware in self._middlewares:
  345. if not middleware.enabled:
  346. continue
  347. try:
  348. await middleware.on_error(context, error)
  349. except Exception:
  350. # Fehler in Error-Handlern ignorieren
  351. pass
  352. def create_context(
  353. self,
  354. event_name: str,
  355. event_data: "EventData",
  356. ) -> MiddlewareContext:
  357. """
  358. Erstellt einen neuen Middleware-Kontext.
  359. Args:
  360. event_name: Name des Events.
  361. event_data: Die Event-Daten.
  362. Returns:
  363. Neuer Kontext.
  364. """
  365. return MiddlewareContext(
  366. event_name=event_name,
  367. event_data=event_data,
  368. )