# -*- coding: utf-8 -*- """ Load-Balancing-Strategie Basisklassen. Definiert das Interface für alle Load-Balancing-Strategien. """ from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from trixy_core.satellite.satellite import Satellite @dataclass class SelectionResult: """ Ergebnis einer Satellite-Auswahl. Attributes: satellite: Der ausgewählte Satellite reason: Grund für die Auswahl score: Optionaler Score (für Weighted-Strategien) metadata: Zusätzliche Informationen """ satellite: "Satellite" reason: str = "" score: float = 0.0 metadata: dict[str, Any] | None = None class LoadBalancingStrategy(ABC): """ Abstrakte Basisklasse für Load-Balancing-Strategien. Definiert das Interface für alle Strategien zur Auswahl eines Satellites aus einer Gruppe. Example: class MyStrategy(LoadBalancingStrategy): def select(self, satellites): # Eigene Logik return SelectionResult(satellites[0]) """ @property @abstractmethod def name(self) -> str: """Name der Strategie.""" ... @abstractmethod def select( self, satellites: list["Satellite"], context: dict[str, Any] | None = None, ) -> SelectionResult | None: """ Wählt einen Satellite aus der Liste aus. Args: satellites: Liste verfügbarer Satellites context: Optionaler Kontext für die Auswahl Returns: SelectionResult oder None wenn keine Auswahl möglich """ ... def on_request_start(self, satellite: "Satellite") -> None: """ Wird aufgerufen, wenn eine Anfrage an einen Satellite beginnt. Kann von Strategien überschrieben werden, die den Zustand verfolgen müssen (z.B. Least-Connections). Args: satellite: Der verwendete Satellite """ pass def on_request_end(self, satellite: "Satellite", success: bool = True) -> None: """ Wird aufgerufen, wenn eine Anfrage an einen Satellite endet. Args: satellite: Der verwendete Satellite success: Ob die Anfrage erfolgreich war """ pass def reset(self) -> None: """Setzt den internen Zustand zurück.""" pass class LoadBalancer: """ Load-Balancer mit konfigurierbarer Strategie. Wählt Satellites basierend auf der konfigurierten Strategie aus. Unterstützt Fallback-Strategie und Filter. Example: balancer = LoadBalancer(RoundRobinStrategy()) # Satellite auswählen result = balancer.select(satellites) if result: satellite = result.satellite await satellite.speak("Hallo") balancer.end_request(satellite) # Mit Filter result = balancer.select( satellites, filter_func=lambda s: s.room_id == "wohnzimmer" ) """ def __init__( self, strategy: LoadBalancingStrategy, fallback_strategy: LoadBalancingStrategy | None = None, ) -> None: """ Initialisiert den Load-Balancer. Args: strategy: Primäre Strategie fallback_strategy: Optionale Fallback-Strategie """ self._strategy = strategy self._fallback = fallback_strategy self._total_requests: int = 0 self._failed_requests: int = 0 @property def strategy(self) -> LoadBalancingStrategy: """Die aktive Strategie.""" return self._strategy @strategy.setter def strategy(self, value: LoadBalancingStrategy) -> None: self._strategy = value @property def fallback_strategy(self) -> LoadBalancingStrategy | None: """Die Fallback-Strategie.""" return self._fallback def select( self, satellites: list["Satellite"], filter_func: Any | None = None, context: dict[str, Any] | None = None, ) -> SelectionResult | None: """ Wählt einen Satellite aus. Args: satellites: Liste verfügbarer Satellites filter_func: Optionale Filter-Funktion context: Optionaler Kontext Returns: SelectionResult oder None """ # Nur verbundene Satellites available = [s for s in satellites if s.is_connected] # Optionalen Filter anwenden if filter_func: available = [s for s in available if filter_func(s)] if not available: return None self._total_requests += 1 # Primäre Strategie versuchen result = self._strategy.select(available, context) # Fallback wenn nötig if result is None and self._fallback: result = self._fallback.select(available, context) if result: self._strategy.on_request_start(result.satellite) return result def start_request(self, satellite: "Satellite") -> None: """Markiert den Start einer Anfrage.""" self._strategy.on_request_start(satellite) def end_request(self, satellite: "Satellite", success: bool = True) -> None: """Markiert das Ende einer Anfrage.""" if not success: self._failed_requests += 1 self._strategy.on_request_end(satellite, success) def reset(self) -> None: """Setzt den Zustand zurück.""" self._strategy.reset() if self._fallback: self._fallback.reset() self._total_requests = 0 self._failed_requests = 0 def get_statistics(self) -> dict[str, Any]: """Gibt Statistiken zurück.""" return { "strategy": self._strategy.name, "fallback_strategy": self._fallback.name if self._fallback else None, "total_requests": self._total_requests, "failed_requests": self._failed_requests, "success_rate": ( (self._total_requests - self._failed_requests) / self._total_requests if self._total_requests > 0 else 1.0 ), }