application.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # -*- coding: utf-8 -*-
  2. """
  3. Abstrakte Basis-Anwendungsklasse für Trixy.
  4. Definiert die gemeinsame Struktur für Server, Client, Standalone und Trainer.
  5. """
  6. from __future__ import annotations
  7. import asyncio
  8. import os
  9. import signal
  10. from abc import ABC, abstractmethod
  11. from pathlib import Path
  12. from typing import Any, TYPE_CHECKING
  13. if TYPE_CHECKING:
  14. from trixy_core.sync.store import SyncStore
  15. from trixy_core.service.service_container import ServiceContainer
  16. from trixy_core.events.eventmanager import EventManager
  17. from trixy_core.config.config_manager import ConfigManager
  18. from trixy_core.utils.debug import pinfo, pdebug, perror, set_debug_mode, _setup_file_routing
  19. from trixy_core.utils.version import VERSION_STRING
  20. class IApplication(ABC):
  21. """
  22. Abstrakte Basisklasse für alle Trixy-Anwendungsmodi.
  23. Stellt gemeinsame Infrastruktur bereit:
  24. - ServiceContainer für Service-Management
  25. - EventManager für Pub/Sub
  26. - ConfigManager für Konfiguration
  27. """
  28. def __init__(self, debug: bool = False) -> None:
  29. """
  30. Initialisiert die Anwendung.
  31. Args:
  32. debug: Debug-Modus aktivieren
  33. """
  34. self._debug = debug
  35. set_debug_mode(debug)
  36. self._running = False
  37. self._shutdown_event = asyncio.Event()
  38. # Core-Komponenten initialisieren
  39. self._config_manager = ConfigManager()
  40. self._event_manager = EventManager(self)
  41. self._service_container = ServiceContainer(self)
  42. # Sync-Store (optional, wird von Standalone/Server gesetzt)
  43. self._sync_store: "SyncStore | None" = None
  44. # Basis-Pfade
  45. self._base_path = Path.cwd()
  46. @property
  47. def debug(self) -> bool:
  48. """Debug-Modus Status."""
  49. return self._debug
  50. @property
  51. def running(self) -> bool:
  52. """Läuft die Anwendung?"""
  53. return self._running
  54. @property
  55. def config_manager(self) -> ConfigManager:
  56. """ConfigManager-Instanz."""
  57. return self._config_manager
  58. @property
  59. def config(self) -> ConfigManager:
  60. """Alias für config_manager."""
  61. return self._config_manager
  62. @property
  63. def events(self) -> EventManager:
  64. """EventManager-Instanz."""
  65. return self._event_manager
  66. @property
  67. def services(self) -> ServiceContainer:
  68. """ServiceContainer-Instanz."""
  69. return self._service_container
  70. @property
  71. def sync_store(self) -> "SyncStore | None":
  72. """SyncStore-Instanz (falls verfuegbar)."""
  73. return self._sync_store
  74. @property
  75. def base_path(self) -> Path:
  76. """Basis-Pfad der Anwendung."""
  77. return self._base_path
  78. @abstractmethod
  79. async def initialize(self) -> None:
  80. """
  81. Initialisiert die Anwendung.
  82. Wird vor dem Start aufgerufen, um Konfiguration zu laden
  83. und Services zu registrieren.
  84. """
  85. pass
  86. @abstractmethod
  87. async def start(self) -> None:
  88. """
  89. Startet die Anwendung.
  90. Startet alle Services und beginnt die Hauptschleife.
  91. """
  92. pass
  93. @abstractmethod
  94. async def stop(self) -> None:
  95. """
  96. Stoppt die Anwendung.
  97. Stoppt alle Services und gibt Ressourcen frei.
  98. """
  99. pass
  100. async def run(self) -> None:
  101. """
  102. Hauptmethode zum Ausführen der Anwendung.
  103. Initialisiert, startet und wartet auf Shutdown-Signal.
  104. """
  105. pinfo(f"Trixy v{VERSION_STRING} wird gestartet...")
  106. # Signal-Handler einrichten
  107. loop = asyncio.get_running_loop()
  108. for sig in (signal.SIGINT, signal.SIGTERM):
  109. loop.add_signal_handler(
  110. sig,
  111. lambda: asyncio.create_task(self._signal_handler())
  112. )
  113. try:
  114. await self.initialize()
  115. await self.start()
  116. self._running = True
  117. pinfo("Anwendung gestartet")
  118. # Warte auf Shutdown-Signal
  119. await self._shutdown_event.wait()
  120. except Exception as e:
  121. perror(f"Fehler beim Starten: {e}")
  122. raise
  123. finally:
  124. await self.stop()
  125. pinfo("Anwendung beendet")
  126. # Force-Exit: Blockierende Executor-Threads (stdin input/read_keys)
  127. # verhindern sauberen asyncio-Shutdown (300s Timeout).
  128. # Da stop() bereits alle Services und Ressourcen aufgeräumt hat,
  129. # ist os._exit() hier sicher.
  130. os._exit(0)
  131. async def _signal_handler(self) -> None:
  132. """Behandelt Shutdown-Signale."""
  133. if not self._running:
  134. return
  135. pinfo("Shutdown-Signal empfangen...")
  136. self._running = False
  137. self._shutdown_event.set()
  138. def shutdown(self) -> None:
  139. """Löst einen Shutdown aus."""
  140. self._shutdown_event.set()
  141. def _init_file_logging(self, logging_config) -> None:
  142. """
  143. Initialisiert das File-Logging-System.
  144. Args:
  145. logging_config: LoggingConfig-Instanz mit Logging-Parametern
  146. """
  147. from trixy_core.utils.logging.file_router import init_file_router
  148. router = init_file_router(
  149. log_directory=logging_config.log_directory,
  150. file_log_level=logging_config.file_log_level,
  151. max_file_size_mb=logging_config.max_file_size_mb,
  152. backup_count=logging_config.backup_count,
  153. )
  154. _setup_file_routing(router)
  155. pdebug(f"File-Logging initialisiert: {logging_config.log_directory}/")
  156. def _shutdown_file_logging(self) -> None:
  157. """Faehrt das File-Logging-System herunter."""
  158. from trixy_core.utils.logging.file_router import shutdown_file_router
  159. _setup_file_routing(None)
  160. shutdown_file_router()
  161. def get_path(self, *parts: str) -> Path:
  162. """
  163. Gibt einen Pfad relativ zum Basis-Pfad zurück.
  164. Args:
  165. *parts: Pfad-Komponenten
  166. Returns:
  167. Vollständiger Pfad
  168. """
  169. return self._base_path.joinpath(*parts)