# -*- coding: utf-8 -*- """ Base Action - Abstrakte Basis für alle Actions. """ from abc import ABC, abstractmethod from dataclasses import dataclass, field from datetime import datetime from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from trixy_core.scheduler.form_fields import FormField, FormValidationError @dataclass class ActionResult: """Ergebnis einer Action-Ausführung.""" success: bool action_name: str start_time: datetime end_time: datetime result: Any = None error: str | None = None metadata: dict[str, Any] = field(default_factory=dict) @property def duration_ms(self) -> float: """Ausführungsdauer in Millisekunden.""" return (self.end_time - self.start_time).total_seconds() * 1000 def to_dict(self) -> dict: """Konvertiert zu Dictionary.""" return { "success": self.success, "action_name": self.action_name, "start_time": self.start_time.isoformat(), "end_time": self.end_time.isoformat(), "duration_ms": self.duration_ms, "error": self.error, "metadata": self.metadata, } class Action(ABC): """ Abstrakte Basis für Actions. Eine Action wird ausgeführt wenn ein Job triggert. """ def __init__( self, name: str | None = None, timeout_seconds: float = 60.0, retry_on_failure: bool = False, max_retries: int = 3, ): """ Initialisiert die Action. Args: name: Action-Name timeout_seconds: Timeout für Ausführung retry_on_failure: Bei Fehler wiederholen max_retries: Maximale Wiederholungen """ self._name = name or self.__class__.__name__ self._timeout_seconds = timeout_seconds self._retry_on_failure = retry_on_failure self._max_retries = max_retries self._enabled = True self._execution_count = 0 self._last_result: ActionResult | None = None @property def name(self) -> str: """Action-Name.""" return self._name @property def timeout_seconds(self) -> float: """Timeout in Sekunden.""" return self._timeout_seconds @property def is_enabled(self) -> bool: """Prüft ob Action aktiv ist.""" return self._enabled @property def execution_count(self) -> int: """Anzahl Ausführungen.""" return self._execution_count @property def last_result(self) -> ActionResult | None: """Letztes Ergebnis.""" return self._last_result def enable(self) -> None: """Aktiviert die Action.""" self._enabled = True def disable(self) -> None: """Deaktiviert die Action.""" self._enabled = False @abstractmethod async def _execute(self, context: dict) -> Any: """ Interne Ausführung. Args: context: Ausführungs-Kontext Returns: Ergebnis der Ausführung """ pass async def execute(self, context: dict | None = None) -> ActionResult: """ Führt die Action aus. Args: context: Ausführungs-Kontext Returns: ActionResult mit Ergebnis """ start_time = datetime.now() ctx = context or {} retries = 0 while True: try: result = await self._execute(ctx) end_time = datetime.now() action_result = ActionResult( success=True, action_name=self._name, start_time=start_time, end_time=end_time, result=result, ) self._execution_count += 1 self._last_result = action_result return action_result except Exception as e: retries += 1 if self._retry_on_failure and retries < self._max_retries: # Retry import asyncio await asyncio.sleep(1.0 * retries) # Exponential backoff continue end_time = datetime.now() action_result = ActionResult( success=False, action_name=self._name, start_time=start_time, end_time=end_time, error=str(e), metadata={"retries": retries}, ) self._execution_count += 1 self._last_result = action_result return action_result @abstractmethod def to_dict(self) -> dict: """Konvertiert zu Dictionary.""" pass def _base_dict(self) -> dict: """Basis-Dictionary für Serialisierung.""" return { "type": self.__class__.__name__, "name": self._name, "timeout_seconds": self._timeout_seconds, "retry_on_failure": self._retry_on_failure, "max_retries": self._max_retries, "enabled": self._enabled, } @classmethod def form_fields(cls) -> list["FormField"]: """Gibt die Formular-Felder fuer die TUI zurueck.""" return [] @classmethod def validate_form(cls, data: dict) -> list["FormValidationError"]: """Validiert Formular-Daten. Gibt Liste von Fehlern zurueck.""" return [] @classmethod def display_name(cls) -> str: """Anzeigename fuer die TUI.""" return cls.__name__ def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self._name})"