callback.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. # -*- coding: utf-8 -*-
  2. """
  3. Callback Action - Führt Python-Funktionen aus.
  4. """
  5. import asyncio
  6. import inspect
  7. from typing import Any, Callable, Awaitable
  8. from trixy_core.scheduler.action.base import Action
  9. class CallbackAction(Action):
  10. """
  11. Action die eine Python-Funktion aufruft.
  12. Beispiel:
  13. - CallbackAction(my_function)
  14. - CallbackAction(async_function, pass_context=True)
  15. - CallbackAction(lambda: print("Hello"))
  16. """
  17. def __init__(
  18. self,
  19. callback: Callable[..., Any] | Callable[..., Awaitable[Any]],
  20. args: tuple = (),
  21. kwargs: dict | None = None,
  22. pass_context: bool = False,
  23. name: str | None = None,
  24. timeout_seconds: float = 60.0,
  25. retry_on_failure: bool = False,
  26. max_retries: int = 3,
  27. ):
  28. """
  29. Initialisiert die Action.
  30. Args:
  31. callback: Funktion (sync oder async)
  32. args: Positionale Argumente
  33. kwargs: Keyword-Argumente
  34. pass_context: Kontext als erstes Argument übergeben
  35. name: Action-Name
  36. timeout_seconds: Timeout
  37. retry_on_failure: Bei Fehler wiederholen
  38. max_retries: Maximale Wiederholungen
  39. """
  40. func_name = getattr(callback, "__name__", "callback")
  41. super().__init__(
  42. name or f"callback_{func_name}",
  43. timeout_seconds,
  44. retry_on_failure,
  45. max_retries,
  46. )
  47. self._callback = callback
  48. self._args = args
  49. self._kwargs = kwargs or {}
  50. self._pass_context = pass_context
  51. self._is_async = asyncio.iscoroutinefunction(callback)
  52. @property
  53. def is_async(self) -> bool:
  54. """Prüft ob Callback async ist."""
  55. return self._is_async
  56. @property
  57. def pass_context(self) -> bool:
  58. """Prüft ob Kontext übergeben wird."""
  59. return self._pass_context
  60. async def _execute(self, context: dict) -> Any:
  61. """Führt den Callback aus."""
  62. # Argumente vorbereiten
  63. args = self._args
  64. kwargs = dict(self._kwargs)
  65. if self._pass_context:
  66. args = (context,) + args
  67. # Callback ausführen
  68. if self._is_async:
  69. result = await asyncio.wait_for(
  70. self._callback(*args, **kwargs),
  71. timeout=self._timeout_seconds,
  72. )
  73. else:
  74. # Sync-Funktion in Thread ausführen
  75. loop = asyncio.get_event_loop()
  76. result = await asyncio.wait_for(
  77. loop.run_in_executor(
  78. None,
  79. lambda: self._callback(*args, **kwargs),
  80. ),
  81. timeout=self._timeout_seconds,
  82. )
  83. return result
  84. def to_dict(self) -> dict:
  85. """Konvertiert zu Dictionary."""
  86. return {
  87. **self._base_dict(),
  88. "callback": getattr(self._callback, "__name__", "<lambda>"),
  89. "pass_context": self._pass_context,
  90. # args/kwargs können nicht immer serialisiert werden
  91. }
  92. @classmethod
  93. def display_name(cls) -> str:
  94. """Anzeigename fuer die TUI."""
  95. return "Callback-Funktion"
  96. def __repr__(self) -> str:
  97. func_name = getattr(self._callback, "__name__", "<lambda>")
  98. return f"CallbackAction(callback={func_name})"
  99. class MethodAction(Action):
  100. """
  101. Action die eine Methode auf einem Objekt aufruft.
  102. Beispiel:
  103. - MethodAction(service, "refresh")
  104. - MethodAction(manager, "cleanup", kwargs={"force": True})
  105. """
  106. def __init__(
  107. self,
  108. obj: Any,
  109. method_name: str,
  110. args: tuple = (),
  111. kwargs: dict | None = None,
  112. name: str | None = None,
  113. timeout_seconds: float = 60.0,
  114. ):
  115. """
  116. Initialisiert die Action.
  117. Args:
  118. obj: Objekt auf dem Methode aufgerufen wird
  119. method_name: Name der Methode
  120. args: Positionale Argumente
  121. kwargs: Keyword-Argumente
  122. name: Action-Name
  123. timeout_seconds: Timeout
  124. """
  125. super().__init__(name or f"method_{method_name}", timeout_seconds)
  126. self._obj = obj
  127. self._method_name = method_name
  128. self._args = args
  129. self._kwargs = kwargs or {}
  130. @property
  131. def method_name(self) -> str:
  132. """Methoden-Name."""
  133. return self._method_name
  134. async def _execute(self, context: dict) -> Any:
  135. """Führt die Methode aus."""
  136. # Methode holen
  137. method = getattr(self._obj, self._method_name)
  138. if not callable(method):
  139. raise ValueError(f"{self._method_name} ist nicht aufrufbar")
  140. # Methode ausführen
  141. if asyncio.iscoroutinefunction(method):
  142. result = await asyncio.wait_for(
  143. method(*self._args, **self._kwargs),
  144. timeout=self._timeout_seconds,
  145. )
  146. else:
  147. loop = asyncio.get_event_loop()
  148. result = await asyncio.wait_for(
  149. loop.run_in_executor(
  150. None,
  151. lambda: method(*self._args, **self._kwargs),
  152. ),
  153. timeout=self._timeout_seconds,
  154. )
  155. return result
  156. @classmethod
  157. def display_name(cls) -> str:
  158. """Anzeigename fuer die TUI."""
  159. return "Methoden-Aufruf"
  160. def to_dict(self) -> dict:
  161. """Konvertiert zu Dictionary."""
  162. return {
  163. **self._base_dict(),
  164. "object_type": type(self._obj).__name__,
  165. "method_name": self._method_name,
  166. }