# -*- coding: utf-8 -*- """ Basis-Klassen für Service-Scopes. Definiert die abstrakte Schnittstelle für Scope-Implementierungen und den ScopeContext für Request-Scoped Services. """ from __future__ import annotations import contextvars import uuid from abc import ABC, abstractmethod from contextlib import contextmanager from dataclasses import dataclass, field from datetime import datetime from enum import Enum, auto from typing import Any, Callable, Generator, Generic, Type, TypeVar T = TypeVar("T") class ScopeType(Enum): """ Verfügbare Scope-Typen. """ SINGLETON = auto() """Eine Instanz für die gesamte Anwendung.""" TRANSIENT = auto() """Neue Instanz bei jedem Abruf.""" SCOPED = auto() """Eine Instanz pro Scope (z.B. Request).""" THREAD = auto() """Eine Instanz pro Thread.""" @dataclass class ScopeContext: """ Kontext für scoped Services. Verwaltet den Lebenszyklus von Instanzen innerhalb eines Scopes (z.B. eines HTTP-Requests). """ scope_id: str = field(default_factory=lambda: str(uuid.uuid4())) """Eindeutige ID des Scopes.""" created_at: datetime = field(default_factory=datetime.now) """Erstellungszeitpunkt des Scopes.""" instances: dict[str, Any] = field(default_factory=dict) """Gecachte Instanzen in diesem Scope.""" metadata: dict[str, Any] = field(default_factory=dict) """Zusätzliche Metadaten.""" _disposed: bool = field(default=False, repr=False) """Ob der Scope bereits disposed wurde.""" def get(self, key: str) -> Any | None: """ Holt eine gecachte Instanz. Args: key: Service-Schlüssel. Returns: Instanz oder None. """ if self._disposed: raise RuntimeError("Scope wurde bereits disposed") return self.instances.get(key) def set(self, key: str, instance: Any) -> None: """ Speichert eine Instanz im Scope. Args: key: Service-Schlüssel. instance: Die zu speichernde Instanz. """ if self._disposed: raise RuntimeError("Scope wurde bereits disposed") self.instances[key] = instance def contains(self, key: str) -> bool: """Prüft, ob eine Instanz im Scope existiert.""" return key in self.instances def dispose(self) -> None: """ Disposed den Scope und alle enthaltenen Instanzen. Ruft dispose() auf allen Instanzen auf, die diese Methode haben. """ if self._disposed: return for instance in self.instances.values(): if hasattr(instance, "dispose"): try: instance.dispose() except Exception: pass elif hasattr(instance, "close"): try: instance.close() except Exception: pass self.instances.clear() self._disposed = True @property def is_disposed(self) -> bool: """Prüft, ob der Scope disposed wurde.""" return self._disposed def __enter__(self) -> "ScopeContext": """Context-Manager-Eintritt.""" return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: """Context-Manager-Austritt mit automatischem Dispose.""" self.dispose() # Globale ContextVar für den aktuellen Scope _current_scope: contextvars.ContextVar[ScopeContext | None] = contextvars.ContextVar( "current_scope", default=None ) def get_current_scope() -> ScopeContext | None: """ Gibt den aktuellen Scope zurück. Returns: ScopeContext oder None wenn kein Scope aktiv. """ return _current_scope.get() def set_current_scope(scope: ScopeContext | None) -> ScopeContext | None: """ Setzt den aktuellen Scope. Args: scope: Der zu setzende Scope oder None. Returns: Der vorherige Scope. """ previous = _current_scope.get() _current_scope.set(scope) return previous @contextmanager def scope_context( metadata: dict[str, Any] | None = None, ) -> Generator[ScopeContext, None, None]: """ Context-Manager für einen neuen Scope. Args: metadata: Optionale Metadaten für den Scope. Yields: Der erstellte ScopeContext. Example: with scope_context() as scope: service = container.get(MyService) # Service wird am Ende des Blocks disposed """ scope = ScopeContext(metadata=metadata or {}) previous = set_current_scope(scope) try: yield scope finally: set_current_scope(previous) scope.dispose() class ServiceScope(ABC, Generic[T]): """ Abstrakte Basisklasse für Service-Scopes. Definiert die Schnittstelle für die Instanz-Erstellung und -Verwaltung. """ scope_type: ScopeType = ScopeType.SINGLETON """Typ des Scopes.""" def __init__(self, factory: Callable[[], T]) -> None: """ Initialisiert den Scope. Args: factory: Factory-Funktion zur Instanz-Erstellung. """ self._factory = factory @property def factory(self) -> Callable[[], T]: """Gibt die Factory-Funktion zurück.""" return self._factory @abstractmethod def get_instance(self, key: str) -> T: """ Holt oder erstellt eine Instanz. Args: key: Service-Schlüssel. Returns: Die Service-Instanz. """ pass def dispose(self) -> None: """ Disposed den Scope und alle Instanzen. Override für spezifisches Aufräumverhalten. """ pass def __call__(self, key: str) -> T: """Ermöglicht Aufruf als Funktion.""" return self.get_instance(key)