# -*- coding: utf-8 -*- """ Extension-Basisklasse. Definiert die Schnittstelle für alle Extensions. """ from abc import ABC, abstractmethod from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import Any, TypeVar, Generic class ExtensionState(Enum): """Zustand einer Extension.""" REGISTERED = "registered" # Registriert, aber nicht aktiv INITIALIZING = "initializing" # Wird initialisiert ACTIVE = "active" # Aktiv und nutzbar ERROR = "error" # Fehler bei Initialisierung DISABLED = "disabled" # Deaktiviert @dataclass class ExtensionInfo: """Metadaten einer Extension.""" # Identifikation id: str # Eindeutige ID (z.B. "spotify_source") name: str # Anzeigename description: str = "" version: str = "1.0.0" # Herkunft plugin_name: str = "" # Plugin das diese Extension bereitstellt author: str = "" # Extension Point extension_point: str = "" # z.B. "music.source" # Priorität (höher = wird bevorzugt) priority: int = 0 # Abhängigkeiten dependencies: list[str] = field(default_factory=list) # Zustand state: ExtensionState = ExtensionState.REGISTERED error_message: str = "" # Zeitstempel registered_at: datetime = field(default_factory=datetime.now) activated_at: datetime | None = None # Zusätzliche Daten metadata: dict[str, Any] = field(default_factory=dict) def to_dict(self) -> dict[str, Any]: """Konvertiert zu Dictionary.""" return { "id": self.id, "name": self.name, "description": self.description, "version": self.version, "plugin_name": self.plugin_name, "extension_point": self.extension_point, "priority": self.priority, "state": self.state.value, "error_message": self.error_message if self.state == ExtensionState.ERROR else None, } T = TypeVar("T") class Extension(ABC, Generic[T]): """ Abstrakte Basisklasse für Extensions. Extensions erweitern Core-Komponenten um neue Funktionalität. Sie werden von Plugins registriert und von Core-Komponenten genutzt. Type Parameter T: Der Typ der Instanz die diese Extension bereitstellt. z.B. Extension[MusicSource] für Musikquellen. """ def __init__( self, extension_id: str, name: str, plugin_name: str = "", ) -> None: """ Initialisiert die Extension. Args: extension_id: Eindeutige ID name: Anzeigename plugin_name: Name des bereitstellenden Plugins """ self._info = ExtensionInfo( id=extension_id, name=name, plugin_name=plugin_name, ) self._instance: T | None = None @property def info(self) -> ExtensionInfo: """Extension-Informationen.""" return self._info @property def id(self) -> str: """Extension-ID.""" return self._info.id @property def name(self) -> str: """Anzeigename.""" return self._info.name @property def state(self) -> ExtensionState: """Aktueller Zustand.""" return self._info.state @property def is_active(self) -> bool: """Prüft ob Extension aktiv ist.""" return self._info.state == ExtensionState.ACTIVE @property def instance(self) -> T | None: """Die bereitgestellte Instanz (wenn aktiv).""" return self._instance # ========================================================================== # Lifecycle # ========================================================================== @abstractmethod async def create_instance(self, config: dict[str, Any]) -> T: """ Erstellt die Instanz die diese Extension bereitstellt. Args: config: Konfiguration für die Instanz Returns: Die erstellte Instanz Raises: Exception: Bei Fehlern """ pass async def initialize(self, config: dict[str, Any]) -> bool: """ Initialisiert die Extension. Args: config: Konfiguration Returns: True wenn erfolgreich """ self._info.state = ExtensionState.INITIALIZING try: self._instance = await self.create_instance(config) self._info.state = ExtensionState.ACTIVE self._info.activated_at = datetime.now() return True except Exception as e: self._info.state = ExtensionState.ERROR self._info.error_message = str(e) return False async def shutdown(self) -> None: """Fährt die Extension herunter.""" if self._instance is not None: await self.destroy_instance(self._instance) self._instance = None self._info.state = ExtensionState.DISABLED async def destroy_instance(self, instance: T) -> None: """ Zerstört die Instanz (Cleanup). Override für spezifische Cleanup-Logik. Args: instance: Die zu zerstörende Instanz """ # Standard: Nichts tun pass # ========================================================================== # Validierung # ========================================================================== def validate_config(self, config: dict[str, Any]) -> list[str]: """ Validiert die Konfiguration. Args: config: Zu validierende Konfiguration Returns: Liste von Fehlermeldungen (leer = valide) """ return [] def get_default_config(self) -> dict[str, Any]: """ Liefert Standard-Konfiguration. Returns: Standard-Konfiguration """ return {} def get_config_schema(self) -> dict[str, Any]: """ Liefert JSON-Schema für Konfiguration. Returns: JSON-Schema """ return {} # ========================================================================== # Info # ========================================================================== def __repr__(self) -> str: return ( f"{self.__class__.__name__}(" f"id={self.id!r}, " f"state={self.state.value!r})" )