| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- # -*- 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)
|