registry.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. # -*- coding: utf-8 -*-
  2. """
  3. Hook-Registry für zentrale Hook-Verwaltung.
  4. Ermöglicht das Registrieren, Finden und Verwalten von Hooks
  5. über alle Plugins hinweg.
  6. """
  7. from __future__ import annotations
  8. from dataclasses import dataclass, field
  9. from typing import Any, TypeVar
  10. from trixy_core.plugins.hooks.hook import (
  11. PluginHook,
  12. HookPriority,
  13. HookCallback,
  14. HookHandler,
  15. )
  16. T = TypeVar("T")
  17. @dataclass
  18. class HookInfo:
  19. """
  20. Informationen über einen registrierten Hook.
  21. Attributes:
  22. name: Hook-Name
  23. description: Beschreibung
  24. handler_count: Anzahl Handler
  25. total_calls: Gesamtaufrufe
  26. handlers: Handler-Details
  27. """
  28. name: str
  29. description: str
  30. handler_count: int
  31. total_calls: int
  32. handlers: list[dict[str, Any]] = field(default_factory=list)
  33. def to_dict(self) -> dict[str, Any]:
  34. """Konvertiert zu Dictionary."""
  35. return {
  36. "name": self.name,
  37. "description": self.description,
  38. "handler_count": self.handler_count,
  39. "total_calls": self.total_calls,
  40. "handlers": self.handlers,
  41. }
  42. class HookRegistry:
  43. """
  44. Zentrale Registry für alle Plugin-Hooks.
  45. Verwaltet Hooks und ermöglicht deren dynamische
  46. Erstellung und Abfrage.
  47. Example:
  48. registry = HookRegistry()
  49. # Hook erstellen oder abrufen
  50. process_hook = registry.get_or_create("process_text")
  51. # Handler registrieren
  52. registry.register_handler(
  53. "process_text",
  54. "my_plugin",
  55. lambda text: text.upper()
  56. )
  57. # Hook anwenden
  58. result = registry.apply("process_text", "hello")
  59. print(result.value) # "HELLO"
  60. # Alle Hooks eines Plugins entfernen
  61. registry.remove_plugin("my_plugin")
  62. # Statistiken
  63. for info in registry.get_all_info():
  64. print(f"{info.name}: {info.handler_count} handlers")
  65. """
  66. def __init__(self) -> None:
  67. """Initialisiert die Registry."""
  68. self._hooks: dict[str, PluginHook] = {}
  69. self._plugin_hooks: dict[str, set[str]] = {} # plugin_name -> set(hook_names)
  70. @property
  71. def hook_count(self) -> int:
  72. """Anzahl registrierter Hooks."""
  73. return len(self._hooks)
  74. @property
  75. def total_handlers(self) -> int:
  76. """Gesamtanzahl aller Handler."""
  77. return sum(hook.handler_count for hook in self._hooks.values())
  78. def create(
  79. self,
  80. name: str,
  81. description: str = "",
  82. default_value: Any = None,
  83. ) -> PluginHook:
  84. """
  85. Erstellt einen neuen Hook.
  86. Args:
  87. name: Eindeutiger Hook-Name
  88. description: Beschreibung
  89. default_value: Standardwert
  90. Returns:
  91. Der erstellte Hook
  92. Raises:
  93. ValueError: Wenn Hook bereits existiert
  94. """
  95. if name in self._hooks:
  96. raise ValueError(f"Hook existiert bereits: {name}")
  97. hook: PluginHook = PluginHook(name, description, default_value)
  98. self._hooks[name] = hook
  99. return hook
  100. def get(self, name: str) -> PluginHook | None:
  101. """Gibt einen Hook zurück."""
  102. return self._hooks.get(name)
  103. def get_or_create(
  104. self,
  105. name: str,
  106. description: str = "",
  107. default_value: Any = None,
  108. ) -> PluginHook:
  109. """
  110. Gibt einen Hook zurück oder erstellt ihn.
  111. Args:
  112. name: Hook-Name
  113. description: Beschreibung (nur bei Erstellung)
  114. default_value: Standardwert (nur bei Erstellung)
  115. Returns:
  116. Der Hook
  117. """
  118. if name not in self._hooks:
  119. self._hooks[name] = PluginHook(name, description, default_value)
  120. return self._hooks[name]
  121. def remove(self, name: str) -> PluginHook | None:
  122. """
  123. Entfernt einen Hook.
  124. Args:
  125. name: Hook-Name
  126. Returns:
  127. Der entfernte Hook oder None
  128. """
  129. hook = self._hooks.pop(name, None)
  130. # Aus Plugin-Mapping entfernen
  131. for hooks in self._plugin_hooks.values():
  132. hooks.discard(name)
  133. return hook
  134. def register_handler(
  135. self,
  136. hook_name: str,
  137. plugin_name: str,
  138. callback: HookCallback,
  139. priority: HookPriority = HookPriority.NORMAL,
  140. once: bool = False,
  141. ) -> bool:
  142. """
  143. Registriert einen Handler für einen Hook.
  144. Erstellt den Hook automatisch wenn nicht vorhanden.
  145. Args:
  146. hook_name: Hook-Name
  147. plugin_name: Plugin-Name
  148. callback: Callback-Funktion
  149. priority: Ausführungspriorität
  150. once: Nur einmal ausführen
  151. Returns:
  152. True bei Erfolg
  153. """
  154. hook = self.get_or_create(hook_name)
  155. hook.add_handler(callback, plugin_name, priority, once)
  156. # Plugin-Mapping aktualisieren
  157. if plugin_name not in self._plugin_hooks:
  158. self._plugin_hooks[plugin_name] = set()
  159. self._plugin_hooks[plugin_name].add(hook_name)
  160. return True
  161. def unregister_handler(
  162. self,
  163. hook_name: str,
  164. plugin_name: str,
  165. ) -> int:
  166. """
  167. Entfernt Handler eines Plugins von einem Hook.
  168. Args:
  169. hook_name: Hook-Name
  170. plugin_name: Plugin-Name
  171. Returns:
  172. Anzahl entfernter Handler
  173. """
  174. hook = self._hooks.get(hook_name)
  175. if hook is None:
  176. return 0
  177. removed = hook.remove_handler(plugin_name)
  178. # Plugin-Mapping aktualisieren
  179. if plugin_name in self._plugin_hooks:
  180. self._plugin_hooks[plugin_name].discard(hook_name)
  181. return removed
  182. def remove_plugin(self, plugin_name: str) -> int:
  183. """
  184. Entfernt alle Handler eines Plugins.
  185. Args:
  186. plugin_name: Plugin-Name
  187. Returns:
  188. Gesamtanzahl entfernter Handler
  189. """
  190. total_removed = 0
  191. if plugin_name in self._plugin_hooks:
  192. for hook_name in list(self._plugin_hooks[plugin_name]):
  193. hook = self._hooks.get(hook_name)
  194. if hook:
  195. total_removed += hook.remove_handler(plugin_name)
  196. del self._plugin_hooks[plugin_name]
  197. return total_removed
  198. def apply(
  199. self,
  200. hook_name: str,
  201. value: Any,
  202. *args: Any,
  203. **kwargs: Any,
  204. ) -> Any:
  205. """
  206. Wendet einen Hook an.
  207. Args:
  208. hook_name: Hook-Name
  209. value: Zu verarbeitender Wert
  210. *args: Zusätzliche Argumente
  211. **kwargs: Keyword-Argumente
  212. Returns:
  213. HookResult oder der Originalwert
  214. """
  215. hook = self._hooks.get(hook_name)
  216. if hook is None:
  217. from trixy_core.plugins.hooks.hook import HookResult
  218. return HookResult(value=value)
  219. return hook.apply(value, *args, **kwargs)
  220. async def apply_async(
  221. self,
  222. hook_name: str,
  223. value: Any,
  224. *args: Any,
  225. **kwargs: Any,
  226. ) -> Any:
  227. """Wendet einen Hook asynchron an."""
  228. hook = self._hooks.get(hook_name)
  229. if hook is None:
  230. from trixy_core.plugins.hooks.hook import HookResult
  231. return HookResult(value=value)
  232. return await hook.apply_async(value, *args, **kwargs)
  233. def trigger(
  234. self,
  235. hook_name: str,
  236. *args: Any,
  237. **kwargs: Any,
  238. ) -> list[Any]:
  239. """
  240. Triggert einen Hook als Action.
  241. Args:
  242. hook_name: Hook-Name
  243. *args: Argumente
  244. **kwargs: Keyword-Argumente
  245. Returns:
  246. Liste der Ergebnisse
  247. """
  248. hook = self._hooks.get(hook_name)
  249. if hook is None:
  250. return []
  251. return hook.trigger(*args, **kwargs)
  252. async def trigger_async(
  253. self,
  254. hook_name: str,
  255. *args: Any,
  256. **kwargs: Any,
  257. ) -> list[Any]:
  258. """Triggert einen Hook asynchron."""
  259. hook = self._hooks.get(hook_name)
  260. if hook is None:
  261. return []
  262. return await hook.trigger_async(*args, **kwargs)
  263. def get_hooks_for_plugin(self, plugin_name: str) -> list[str]:
  264. """
  265. Gibt alle Hooks zurück, für die ein Plugin Handler hat.
  266. Args:
  267. plugin_name: Plugin-Name
  268. Returns:
  269. Liste der Hook-Namen
  270. """
  271. return list(self._plugin_hooks.get(plugin_name, set()))
  272. def get_plugins_for_hook(self, hook_name: str) -> list[str]:
  273. """
  274. Gibt alle Plugins zurück, die Handler für einen Hook haben.
  275. Args:
  276. hook_name: Hook-Name
  277. Returns:
  278. Liste der Plugin-Namen
  279. """
  280. hook = self._hooks.get(hook_name)
  281. if hook is None:
  282. return []
  283. return list(set(h.plugin_name for h in hook.get_handlers()))
  284. def get_info(self, hook_name: str) -> HookInfo | None:
  285. """
  286. Gibt Informationen über einen Hook zurück.
  287. Args:
  288. hook_name: Hook-Name
  289. Returns:
  290. HookInfo oder None
  291. """
  292. hook = self._hooks.get(hook_name)
  293. if hook is None:
  294. return None
  295. handlers = []
  296. total_calls = 0
  297. for handler in hook.get_handlers():
  298. total_calls += handler.call_count
  299. handlers.append({
  300. "plugin": handler.plugin_name,
  301. "priority": handler.priority.name,
  302. "is_async": handler.is_async,
  303. "enabled": handler.enabled,
  304. "call_count": handler.call_count,
  305. })
  306. return HookInfo(
  307. name=hook.name,
  308. description=hook.description,
  309. handler_count=hook.handler_count,
  310. total_calls=total_calls,
  311. handlers=handlers,
  312. )
  313. def get_all_info(self) -> list[HookInfo]:
  314. """Gibt Informationen über alle Hooks zurück."""
  315. return [
  316. info for name in self._hooks
  317. if (info := self.get_info(name)) is not None
  318. ]
  319. def list_hooks(self) -> list[str]:
  320. """Gibt alle Hook-Namen zurück."""
  321. return list(self._hooks.keys())
  322. def has_hook(self, name: str) -> bool:
  323. """Prüft, ob ein Hook existiert."""
  324. return name in self._hooks
  325. def clear(self) -> None:
  326. """Entfernt alle Hooks und Handler."""
  327. self._hooks.clear()
  328. self._plugin_hooks.clear()
  329. def __contains__(self, name: str) -> bool:
  330. return name in self._hooks
  331. def __getitem__(self, name: str) -> PluginHook:
  332. if name not in self._hooks:
  333. raise KeyError(f"Hook nicht gefunden: {name}")
  334. return self._hooks[name]
  335. def __len__(self) -> int:
  336. return len(self._hooks)