| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- # -*- 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})"
|