extension.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # -*- coding: utf-8 -*-
  2. """
  3. Extension-Basisklasse.
  4. Definiert die Schnittstelle für alle Extensions.
  5. """
  6. from abc import ABC, abstractmethod
  7. from dataclasses import dataclass, field
  8. from datetime import datetime
  9. from enum import Enum
  10. from typing import Any, TypeVar, Generic
  11. class ExtensionState(Enum):
  12. """Zustand einer Extension."""
  13. REGISTERED = "registered" # Registriert, aber nicht aktiv
  14. INITIALIZING = "initializing" # Wird initialisiert
  15. ACTIVE = "active" # Aktiv und nutzbar
  16. ERROR = "error" # Fehler bei Initialisierung
  17. DISABLED = "disabled" # Deaktiviert
  18. @dataclass
  19. class ExtensionInfo:
  20. """Metadaten einer Extension."""
  21. # Identifikation
  22. id: str # Eindeutige ID (z.B. "spotify_source")
  23. name: str # Anzeigename
  24. description: str = ""
  25. version: str = "1.0.0"
  26. # Herkunft
  27. plugin_name: str = "" # Plugin das diese Extension bereitstellt
  28. author: str = ""
  29. # Extension Point
  30. extension_point: str = "" # z.B. "music.source"
  31. # Priorität (höher = wird bevorzugt)
  32. priority: int = 0
  33. # Abhängigkeiten
  34. dependencies: list[str] = field(default_factory=list)
  35. # Zustand
  36. state: ExtensionState = ExtensionState.REGISTERED
  37. error_message: str = ""
  38. # Zeitstempel
  39. registered_at: datetime = field(default_factory=datetime.now)
  40. activated_at: datetime | None = None
  41. # Zusätzliche Daten
  42. metadata: dict[str, Any] = field(default_factory=dict)
  43. def to_dict(self) -> dict[str, Any]:
  44. """Konvertiert zu Dictionary."""
  45. return {
  46. "id": self.id,
  47. "name": self.name,
  48. "description": self.description,
  49. "version": self.version,
  50. "plugin_name": self.plugin_name,
  51. "extension_point": self.extension_point,
  52. "priority": self.priority,
  53. "state": self.state.value,
  54. "error_message": self.error_message if self.state == ExtensionState.ERROR else None,
  55. }
  56. T = TypeVar("T")
  57. class Extension(ABC, Generic[T]):
  58. """
  59. Abstrakte Basisklasse für Extensions.
  60. Extensions erweitern Core-Komponenten um neue Funktionalität.
  61. Sie werden von Plugins registriert und von Core-Komponenten genutzt.
  62. Type Parameter T: Der Typ der Instanz die diese Extension bereitstellt.
  63. z.B. Extension[MusicSource] für Musikquellen.
  64. """
  65. def __init__(
  66. self,
  67. extension_id: str,
  68. name: str,
  69. plugin_name: str = "",
  70. ) -> None:
  71. """
  72. Initialisiert die Extension.
  73. Args:
  74. extension_id: Eindeutige ID
  75. name: Anzeigename
  76. plugin_name: Name des bereitstellenden Plugins
  77. """
  78. self._info = ExtensionInfo(
  79. id=extension_id,
  80. name=name,
  81. plugin_name=plugin_name,
  82. )
  83. self._instance: T | None = None
  84. @property
  85. def info(self) -> ExtensionInfo:
  86. """Extension-Informationen."""
  87. return self._info
  88. @property
  89. def id(self) -> str:
  90. """Extension-ID."""
  91. return self._info.id
  92. @property
  93. def name(self) -> str:
  94. """Anzeigename."""
  95. return self._info.name
  96. @property
  97. def state(self) -> ExtensionState:
  98. """Aktueller Zustand."""
  99. return self._info.state
  100. @property
  101. def is_active(self) -> bool:
  102. """Prüft ob Extension aktiv ist."""
  103. return self._info.state == ExtensionState.ACTIVE
  104. @property
  105. def instance(self) -> T | None:
  106. """Die bereitgestellte Instanz (wenn aktiv)."""
  107. return self._instance
  108. # ==========================================================================
  109. # Lifecycle
  110. # ==========================================================================
  111. @abstractmethod
  112. async def create_instance(self, config: dict[str, Any]) -> T:
  113. """
  114. Erstellt die Instanz die diese Extension bereitstellt.
  115. Args:
  116. config: Konfiguration für die Instanz
  117. Returns:
  118. Die erstellte Instanz
  119. Raises:
  120. Exception: Bei Fehlern
  121. """
  122. pass
  123. async def initialize(self, config: dict[str, Any]) -> bool:
  124. """
  125. Initialisiert die Extension.
  126. Args:
  127. config: Konfiguration
  128. Returns:
  129. True wenn erfolgreich
  130. """
  131. self._info.state = ExtensionState.INITIALIZING
  132. try:
  133. self._instance = await self.create_instance(config)
  134. self._info.state = ExtensionState.ACTIVE
  135. self._info.activated_at = datetime.now()
  136. return True
  137. except Exception as e:
  138. self._info.state = ExtensionState.ERROR
  139. self._info.error_message = str(e)
  140. return False
  141. async def shutdown(self) -> None:
  142. """Fährt die Extension herunter."""
  143. if self._instance is not None:
  144. await self.destroy_instance(self._instance)
  145. self._instance = None
  146. self._info.state = ExtensionState.DISABLED
  147. async def destroy_instance(self, instance: T) -> None:
  148. """
  149. Zerstört die Instanz (Cleanup).
  150. Override für spezifische Cleanup-Logik.
  151. Args:
  152. instance: Die zu zerstörende Instanz
  153. """
  154. # Standard: Nichts tun
  155. pass
  156. # ==========================================================================
  157. # Validierung
  158. # ==========================================================================
  159. def validate_config(self, config: dict[str, Any]) -> list[str]:
  160. """
  161. Validiert die Konfiguration.
  162. Args:
  163. config: Zu validierende Konfiguration
  164. Returns:
  165. Liste von Fehlermeldungen (leer = valide)
  166. """
  167. return []
  168. def get_default_config(self) -> dict[str, Any]:
  169. """
  170. Liefert Standard-Konfiguration.
  171. Returns:
  172. Standard-Konfiguration
  173. """
  174. return {}
  175. def get_config_schema(self) -> dict[str, Any]:
  176. """
  177. Liefert JSON-Schema für Konfiguration.
  178. Returns:
  179. JSON-Schema
  180. """
  181. return {}
  182. # ==========================================================================
  183. # Info
  184. # ==========================================================================
  185. def __repr__(self) -> str:
  186. return (
  187. f"{self.__class__.__name__}("
  188. f"id={self.id!r}, "
  189. f"state={self.state.value!r})"
  190. )