registry.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. # -*- coding: utf-8 -*-
  2. """
  3. Trainer-Registry: Erkennung und Verwaltung von Trainern.
  4. Sucht Core-Trainer in trixy_core/trainer/core/*.py und
  5. Plugin-Trainer in plugins/*/trainer.py.
  6. """
  7. from __future__ import annotations
  8. import importlib
  9. import importlib.util
  10. import inspect
  11. from pathlib import Path
  12. from trixy_core.trainer.itrainer import ITrainer
  13. from trixy_core.trainer.dataclasses import TrainerInfo
  14. from trixy_core.trainer.enums import TrainerType
  15. from trixy_core.utils.debug import pdebug, perror
  16. class TrainerRegistry:
  17. """Verwaltet alle verfuegbaren Trainer."""
  18. def __init__(self) -> None:
  19. self._trainers: dict[str, ITrainer] = {}
  20. @property
  21. def trainers(self) -> dict[str, ITrainer]:
  22. """Alle registrierten Trainer."""
  23. return dict(self._trainers)
  24. def register(self, trainer: ITrainer) -> None:
  25. """Registriert einen Trainer manuell."""
  26. info = trainer.get_info()
  27. self._trainers[info.trainer_id] = trainer
  28. pdebug(f"Trainer registriert: {info.name} ({info.trainer_id})")
  29. def get(self, trainer_id: str) -> ITrainer | None:
  30. """Liefert einen Trainer nach ID."""
  31. return self._trainers.get(trainer_id)
  32. def discover_core(self) -> int:
  33. """
  34. Sucht Core-Trainer in trixy_core/trainer/core/.
  35. Returns:
  36. Anzahl gefundener Trainer
  37. """
  38. core_dir = Path(__file__).parent / "core"
  39. if not core_dir.exists():
  40. return 0
  41. count = 0
  42. # Flache .py Dateien
  43. for py_file in core_dir.glob("*.py"):
  44. if py_file.name.startswith("_"):
  45. continue
  46. try:
  47. module_name = f"trixy_core.trainer.core.{py_file.stem}"
  48. module = importlib.import_module(module_name)
  49. count += self._scan_module(module, TrainerType.CORE)
  50. except Exception as e:
  51. pdebug(f"Core-Trainer Fehler: {py_file.stem}: {e}")
  52. # Subdirectories mit trainer.py oder __init__.py
  53. for sub_dir in core_dir.iterdir():
  54. if not sub_dir.is_dir() or sub_dir.name.startswith("_"):
  55. continue
  56. # trainer.py im Subdirectory suchen
  57. trainer_file = sub_dir / "trainer.py"
  58. if trainer_file.is_file():
  59. try:
  60. module_name = f"trixy_core.trainer.core.{sub_dir.name}.trainer"
  61. module = importlib.import_module(module_name)
  62. found = self._scan_module(module, TrainerType.CORE)
  63. count += found
  64. if found:
  65. pdebug(f"Core-Trainer entdeckt: {sub_dir.name}/trainer.py")
  66. except Exception as e:
  67. pdebug(f"Core-Trainer Fehler: {sub_dir.name}: {e}")
  68. return count
  69. def discover_plugins(self, plugins_dir: str = "plugins") -> int:
  70. """
  71. Sucht Plugin-Trainer in plugins/*/trainer.py.
  72. Returns:
  73. Anzahl gefundener Trainer
  74. """
  75. base = Path(plugins_dir)
  76. if not base.exists():
  77. return 0
  78. count = 0
  79. for trainer_file in base.glob("*/trainer.py"):
  80. try:
  81. spec = importlib.util.spec_from_file_location(
  82. f"plugin_trainer_{trainer_file.parent.name}",
  83. trainer_file,
  84. )
  85. if spec and spec.loader:
  86. module = importlib.util.module_from_spec(spec)
  87. spec.loader.exec_module(module)
  88. count += self._scan_module(module, TrainerType.PLUGIN)
  89. except Exception as e:
  90. perror(f"Fehler beim Laden von Plugin-Trainer {trainer_file}: {e}")
  91. return count
  92. def _scan_module(self, module: object, trainer_type: TrainerType) -> int:
  93. """Scannt ein Modul nach ITrainer-Klassen und instanziiert sie."""
  94. count = 0
  95. for _, cls in inspect.getmembers(module, inspect.isclass):
  96. if (
  97. issubclass(cls, ITrainer)
  98. and cls is not ITrainer
  99. and not inspect.isabstract(cls)
  100. ):
  101. try:
  102. instance = cls()
  103. info = instance.get_info()
  104. info.trainer_type = trainer_type
  105. self._trainers[info.trainer_id] = instance
  106. count += 1
  107. pdebug(f"Trainer entdeckt: {info.name} ({trainer_type.value})")
  108. except Exception as e:
  109. perror(f"Fehler beim Instanziieren von {cls.__name__}: {e}")
  110. return count
  111. def list_info(self) -> list[TrainerInfo]:
  112. """Liefert Info-Objekte aller registrierten Trainer."""
  113. result = []
  114. for trainer in self._trainers.values():
  115. result.append(trainer.get_info())
  116. return result