| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- # -*- coding: utf-8 -*-
- """
- Composite Triggers - Kombiniert mehrere Trigger.
- """
- from datetime import datetime
- from typing import Any
- from trixy_core.scheduler.trigger.base import Trigger, TriggerContext, TriggerState
- class AndTrigger(Trigger):
- """
- Trigger der nur auslöst wenn ALLE Sub-Trigger auslösen.
- Beispiel:
- - AndTrigger([CronTrigger("0 9 * * *"), EventTrigger("user_present")])
- → Nur um 9 Uhr UND wenn User anwesend
- """
- def __init__(
- self,
- triggers: list[Trigger],
- name: str | None = None,
- ):
- """
- Initialisiert den AND-Trigger.
- Args:
- triggers: Liste von Sub-Triggern
- name: Trigger-Name
- """
- super().__init__(name)
- if not triggers:
- raise ValueError("AndTrigger benötigt mindestens einen Trigger")
- self._triggers = list(triggers)
- @property
- def triggers(self) -> list[Trigger]:
- """Sub-Trigger."""
- return list(self._triggers)
- def add_trigger(self, trigger: Trigger) -> None:
- """Fügt Trigger hinzu."""
- self._triggers.append(trigger)
- def remove_trigger(self, trigger: Trigger) -> bool:
- """Entfernt Trigger."""
- try:
- self._triggers.remove(trigger)
- return True
- except ValueError:
- return False
- def should_fire(self, context: TriggerContext) -> bool:
- """Prüft ob alle Trigger auslösen."""
- if not self.is_active:
- return False
- if not self._triggers:
- return False
- return all(t.should_fire(context) for t in self._triggers)
- def get_next_fire_time(
- self,
- last_fire_time: datetime | None = None,
- ) -> datetime | None:
- """
- Berechnet nächsten Auslösezeitpunkt.
- Für AND: Spätester Zeitpunkt aller Sub-Trigger.
- """
- if not self.is_active or not self._triggers:
- return None
- next_times = []
- for trigger in self._triggers:
- next_time = trigger.get_next_fire_time(last_fire_time)
- if next_time is None:
- # Wenn ein Trigger keinen Zeitpunkt hat (z.B. EventTrigger),
- # kann der AND-Trigger auch keinen planbaren Zeitpunkt haben
- return None
- next_times.append(next_time)
- # Spätester Zeitpunkt
- return max(next_times) if next_times else None
- def pause(self) -> None:
- """Pausiert alle Sub-Trigger."""
- super().pause()
- for trigger in self._triggers:
- trigger.pause()
- def resume(self) -> None:
- """Setzt alle Sub-Trigger fort."""
- super().resume()
- for trigger in self._triggers:
- trigger.resume()
- def reset(self) -> None:
- """Setzt alle Sub-Trigger zurück."""
- super().reset()
- for trigger in self._triggers:
- trigger.reset()
- def to_dict(self) -> dict:
- """Konvertiert zu Dictionary."""
- return {
- **self._base_dict(),
- "triggers": [t.to_dict() for t in self._triggers],
- }
- @classmethod
- def display_name(cls) -> str:
- """Anzeigename fuer die TUI."""
- return "UND-Trigger"
- def __repr__(self) -> str:
- return f"AndTrigger(count={len(self._triggers)})"
- class OrTrigger(Trigger):
- """
- Trigger der auslöst wenn MINDESTENS EIN Sub-Trigger auslöst.
- Beispiel:
- - OrTrigger([CronTrigger("0 9 * * *"), EventTrigger("urgent_task")])
- → Um 9 Uhr ODER bei dringendem Task
- """
- def __init__(
- self,
- triggers: list[Trigger],
- name: str | None = None,
- ):
- """
- Initialisiert den OR-Trigger.
- Args:
- triggers: Liste von Sub-Triggern
- name: Trigger-Name
- """
- super().__init__(name)
- if not triggers:
- raise ValueError("OrTrigger benötigt mindestens einen Trigger")
- self._triggers = list(triggers)
- self._last_fired_trigger: Trigger | None = None
- @property
- def triggers(self) -> list[Trigger]:
- """Sub-Trigger."""
- return list(self._triggers)
- @property
- def last_fired_trigger(self) -> Trigger | None:
- """Zuletzt ausgelöster Sub-Trigger."""
- return self._last_fired_trigger
- def add_trigger(self, trigger: Trigger) -> None:
- """Fügt Trigger hinzu."""
- self._triggers.append(trigger)
- def remove_trigger(self, trigger: Trigger) -> bool:
- """Entfernt Trigger."""
- try:
- self._triggers.remove(trigger)
- return True
- except ValueError:
- return False
- def should_fire(self, context: TriggerContext) -> bool:
- """Prüft ob mindestens ein Trigger auslöst."""
- if not self.is_active:
- return False
- if not self._triggers:
- return False
- for trigger in self._triggers:
- if trigger.should_fire(context):
- self._last_fired_trigger = trigger
- return True
- return False
- def get_next_fire_time(
- self,
- last_fire_time: datetime | None = None,
- ) -> datetime | None:
- """
- Berechnet nächsten Auslösezeitpunkt.
- Für OR: Frühester Zeitpunkt aller Sub-Trigger.
- """
- if not self.is_active or not self._triggers:
- return None
- next_times = []
- for trigger in self._triggers:
- next_time = trigger.get_next_fire_time(last_fire_time)
- if next_time:
- next_times.append(next_time)
- # Frühester Zeitpunkt
- return min(next_times) if next_times else None
- def pause(self) -> None:
- """Pausiert alle Sub-Trigger."""
- super().pause()
- for trigger in self._triggers:
- trigger.pause()
- def resume(self) -> None:
- """Setzt alle Sub-Trigger fort."""
- super().resume()
- for trigger in self._triggers:
- trigger.resume()
- def reset(self) -> None:
- """Setzt alle Sub-Trigger zurück."""
- super().reset()
- self._last_fired_trigger = None
- for trigger in self._triggers:
- trigger.reset()
- def to_dict(self) -> dict:
- """Konvertiert zu Dictionary."""
- return {
- **self._base_dict(),
- "triggers": [t.to_dict() for t in self._triggers],
- }
- @classmethod
- def display_name(cls) -> str:
- """Anzeigename fuer die TUI."""
- return "ODER-Trigger"
- def __repr__(self) -> str:
- return f"OrTrigger(count={len(self._triggers)})"
|