scope.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # -*- coding: utf-8 -*-
  2. """
  3. Basis-Klassen für Service-Scopes.
  4. Definiert die abstrakte Schnittstelle für Scope-Implementierungen
  5. und den ScopeContext für Request-Scoped Services.
  6. """
  7. from __future__ import annotations
  8. import contextvars
  9. import uuid
  10. from abc import ABC, abstractmethod
  11. from contextlib import contextmanager
  12. from dataclasses import dataclass, field
  13. from datetime import datetime
  14. from enum import Enum, auto
  15. from typing import Any, Callable, Generator, Generic, Type, TypeVar
  16. T = TypeVar("T")
  17. class ScopeType(Enum):
  18. """
  19. Verfügbare Scope-Typen.
  20. """
  21. SINGLETON = auto()
  22. """Eine Instanz für die gesamte Anwendung."""
  23. TRANSIENT = auto()
  24. """Neue Instanz bei jedem Abruf."""
  25. SCOPED = auto()
  26. """Eine Instanz pro Scope (z.B. Request)."""
  27. THREAD = auto()
  28. """Eine Instanz pro Thread."""
  29. @dataclass
  30. class ScopeContext:
  31. """
  32. Kontext für scoped Services.
  33. Verwaltet den Lebenszyklus von Instanzen innerhalb
  34. eines Scopes (z.B. eines HTTP-Requests).
  35. """
  36. scope_id: str = field(default_factory=lambda: str(uuid.uuid4()))
  37. """Eindeutige ID des Scopes."""
  38. created_at: datetime = field(default_factory=datetime.now)
  39. """Erstellungszeitpunkt des Scopes."""
  40. instances: dict[str, Any] = field(default_factory=dict)
  41. """Gecachte Instanzen in diesem Scope."""
  42. metadata: dict[str, Any] = field(default_factory=dict)
  43. """Zusätzliche Metadaten."""
  44. _disposed: bool = field(default=False, repr=False)
  45. """Ob der Scope bereits disposed wurde."""
  46. def get(self, key: str) -> Any | None:
  47. """
  48. Holt eine gecachte Instanz.
  49. Args:
  50. key: Service-Schlüssel.
  51. Returns:
  52. Instanz oder None.
  53. """
  54. if self._disposed:
  55. raise RuntimeError("Scope wurde bereits disposed")
  56. return self.instances.get(key)
  57. def set(self, key: str, instance: Any) -> None:
  58. """
  59. Speichert eine Instanz im Scope.
  60. Args:
  61. key: Service-Schlüssel.
  62. instance: Die zu speichernde Instanz.
  63. """
  64. if self._disposed:
  65. raise RuntimeError("Scope wurde bereits disposed")
  66. self.instances[key] = instance
  67. def contains(self, key: str) -> bool:
  68. """Prüft, ob eine Instanz im Scope existiert."""
  69. return key in self.instances
  70. def dispose(self) -> None:
  71. """
  72. Disposed den Scope und alle enthaltenen Instanzen.
  73. Ruft dispose() auf allen Instanzen auf, die diese
  74. Methode haben.
  75. """
  76. if self._disposed:
  77. return
  78. for instance in self.instances.values():
  79. if hasattr(instance, "dispose"):
  80. try:
  81. instance.dispose()
  82. except Exception:
  83. pass
  84. elif hasattr(instance, "close"):
  85. try:
  86. instance.close()
  87. except Exception:
  88. pass
  89. self.instances.clear()
  90. self._disposed = True
  91. @property
  92. def is_disposed(self) -> bool:
  93. """Prüft, ob der Scope disposed wurde."""
  94. return self._disposed
  95. def __enter__(self) -> "ScopeContext":
  96. """Context-Manager-Eintritt."""
  97. return self
  98. def __exit__(self, exc_type, exc_val, exc_tb) -> None:
  99. """Context-Manager-Austritt mit automatischem Dispose."""
  100. self.dispose()
  101. # Globale ContextVar für den aktuellen Scope
  102. _current_scope: contextvars.ContextVar[ScopeContext | None] = contextvars.ContextVar(
  103. "current_scope", default=None
  104. )
  105. def get_current_scope() -> ScopeContext | None:
  106. """
  107. Gibt den aktuellen Scope zurück.
  108. Returns:
  109. ScopeContext oder None wenn kein Scope aktiv.
  110. """
  111. return _current_scope.get()
  112. def set_current_scope(scope: ScopeContext | None) -> ScopeContext | None:
  113. """
  114. Setzt den aktuellen Scope.
  115. Args:
  116. scope: Der zu setzende Scope oder None.
  117. Returns:
  118. Der vorherige Scope.
  119. """
  120. previous = _current_scope.get()
  121. _current_scope.set(scope)
  122. return previous
  123. @contextmanager
  124. def scope_context(
  125. metadata: dict[str, Any] | None = None,
  126. ) -> Generator[ScopeContext, None, None]:
  127. """
  128. Context-Manager für einen neuen Scope.
  129. Args:
  130. metadata: Optionale Metadaten für den Scope.
  131. Yields:
  132. Der erstellte ScopeContext.
  133. Example:
  134. with scope_context() as scope:
  135. service = container.get(MyService)
  136. # Service wird am Ende des Blocks disposed
  137. """
  138. scope = ScopeContext(metadata=metadata or {})
  139. previous = set_current_scope(scope)
  140. try:
  141. yield scope
  142. finally:
  143. set_current_scope(previous)
  144. scope.dispose()
  145. class ServiceScope(ABC, Generic[T]):
  146. """
  147. Abstrakte Basisklasse für Service-Scopes.
  148. Definiert die Schnittstelle für die Instanz-Erstellung
  149. und -Verwaltung.
  150. """
  151. scope_type: ScopeType = ScopeType.SINGLETON
  152. """Typ des Scopes."""
  153. def __init__(self, factory: Callable[[], T]) -> None:
  154. """
  155. Initialisiert den Scope.
  156. Args:
  157. factory: Factory-Funktion zur Instanz-Erstellung.
  158. """
  159. self._factory = factory
  160. @property
  161. def factory(self) -> Callable[[], T]:
  162. """Gibt die Factory-Funktion zurück."""
  163. return self._factory
  164. @abstractmethod
  165. def get_instance(self, key: str) -> T:
  166. """
  167. Holt oder erstellt eine Instanz.
  168. Args:
  169. key: Service-Schlüssel.
  170. Returns:
  171. Die Service-Instanz.
  172. """
  173. pass
  174. def dispose(self) -> None:
  175. """
  176. Disposed den Scope und alle Instanzen.
  177. Override für spezifisches Aufräumverhalten.
  178. """
  179. pass
  180. def __call__(self, key: str) -> T:
  181. """Ermöglicht Aufruf als Funktion."""
  182. return self.get_instance(key)