base.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # -*- coding: utf-8 -*-
  2. """
  3. Base Trigger - Abstrakte Basis für alle Trigger.
  4. """
  5. from abc import ABC, abstractmethod
  6. from dataclasses import dataclass
  7. from datetime import datetime
  8. from enum import Enum
  9. from typing import Any, TYPE_CHECKING
  10. if TYPE_CHECKING:
  11. from trixy_core.scheduler.form_fields import FormField, FormValidationError
  12. class TriggerState(Enum):
  13. """Zustand eines Triggers."""
  14. ACTIVE = "active" # Trigger ist aktiv
  15. PAUSED = "paused" # Trigger ist pausiert
  16. FINISHED = "finished" # Trigger ist beendet (z.B. einmaliger Trigger)
  17. @dataclass
  18. class TriggerContext:
  19. """Kontext für Trigger-Auswertung."""
  20. current_time: datetime
  21. last_fire_time: datetime | None = None
  22. event_name: str | None = None
  23. event_data: dict | None = None
  24. @classmethod
  25. def now(cls) -> "TriggerContext":
  26. """Erstellt Kontext mit aktueller Zeit."""
  27. return cls(current_time=datetime.now())
  28. class Trigger(ABC):
  29. """
  30. Abstrakte Basis für Trigger.
  31. Ein Trigger bestimmt, wann ein Job ausgelöst wird.
  32. """
  33. def __init__(self, name: str | None = None):
  34. """
  35. Initialisiert den Trigger.
  36. Args:
  37. name: Trigger-Name (optional)
  38. """
  39. self._name = name or self.__class__.__name__
  40. self._state = TriggerState.ACTIVE
  41. self._last_fire_time: datetime | None = None
  42. self._fire_count = 0
  43. @property
  44. def name(self) -> str:
  45. """Trigger-Name."""
  46. return self._name
  47. @property
  48. def state(self) -> TriggerState:
  49. """Trigger-Zustand."""
  50. return self._state
  51. @property
  52. def is_active(self) -> bool:
  53. """Prüft ob Trigger aktiv ist."""
  54. return self._state == TriggerState.ACTIVE
  55. @property
  56. def last_fire_time(self) -> datetime | None:
  57. """Letzte Auslösung."""
  58. return self._last_fire_time
  59. @property
  60. def fire_count(self) -> int:
  61. """Anzahl Auslösungen."""
  62. return self._fire_count
  63. @abstractmethod
  64. def should_fire(self, context: TriggerContext) -> bool:
  65. """
  66. Prüft ob Trigger auslösen soll.
  67. Args:
  68. context: Trigger-Kontext
  69. Returns:
  70. True wenn Trigger auslösen soll
  71. """
  72. pass
  73. @abstractmethod
  74. def get_next_fire_time(
  75. self,
  76. last_fire_time: datetime | None = None,
  77. ) -> datetime | None:
  78. """
  79. Berechnet nächsten Auslösezeitpunkt.
  80. Args:
  81. last_fire_time: Letzte Auslösung
  82. Returns:
  83. Nächster Zeitpunkt oder None
  84. """
  85. pass
  86. def fire(self) -> None:
  87. """Markiert Trigger als ausgelöst."""
  88. self._last_fire_time = datetime.now()
  89. self._fire_count += 1
  90. def pause(self) -> None:
  91. """Pausiert den Trigger."""
  92. self._state = TriggerState.PAUSED
  93. def resume(self) -> None:
  94. """Setzt Trigger fort."""
  95. if self._state == TriggerState.PAUSED:
  96. self._state = TriggerState.ACTIVE
  97. def reset(self) -> None:
  98. """Setzt Trigger zurück."""
  99. self._last_fire_time = None
  100. self._fire_count = 0
  101. self._state = TriggerState.ACTIVE
  102. @abstractmethod
  103. def to_dict(self) -> dict:
  104. """Konvertiert zu Dictionary."""
  105. pass
  106. def _base_dict(self) -> dict:
  107. """Basis-Dictionary für Serialisierung."""
  108. return {
  109. "type": self.__class__.__name__,
  110. "name": self._name,
  111. "state": self._state.value,
  112. "fire_count": self._fire_count,
  113. "last_fire_time": (
  114. self._last_fire_time.isoformat()
  115. if self._last_fire_time else None
  116. ),
  117. }
  118. @classmethod
  119. def form_fields(cls) -> list["FormField"]:
  120. """Gibt die Formular-Felder fuer die TUI zurueck."""
  121. return []
  122. @classmethod
  123. def validate_form(cls, data: dict) -> list["FormValidationError"]:
  124. """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck."""
  125. return []
  126. @classmethod
  127. def display_name(cls) -> str:
  128. """Anzeigename fuer die TUI."""
  129. return cls.__name__
  130. def __repr__(self) -> str:
  131. return f"{self.__class__.__name__}(name={self._name})"