scoped.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # -*- coding: utf-8 -*-
  2. """
  3. Scoped-Scopes für request-basierte Services.
  4. Erstellt eine Instanz pro Scope (z.B. HTTP-Request).
  5. """
  6. from __future__ import annotations
  7. import threading
  8. from typing import Callable, Generic, TypeVar
  9. from trixy_core.service.scopes.scope import (
  10. ServiceScope,
  11. ScopeContext,
  12. ScopeType,
  13. get_current_scope,
  14. )
  15. T = TypeVar("T")
  16. class ScopedScope(ServiceScope[T], Generic[T]):
  17. """
  18. Scoped-Scope für request-basierte Services.
  19. Eine Instanz wird pro ScopeContext erstellt und geteilt.
  20. Wenn kein Scope aktiv ist, verhält sich dieser wie Transient.
  21. Example:
  22. scope = ScopedScope(lambda: MyService())
  23. with scope_context():
  24. instance1 = scope.get_instance("my_service")
  25. instance2 = scope.get_instance("my_service")
  26. assert instance1 is instance2
  27. with scope_context():
  28. instance3 = scope.get_instance("my_service")
  29. assert instance3 is not instance1
  30. """
  31. scope_type = ScopeType.SCOPED
  32. def __init__(
  33. self,
  34. factory: Callable[[], T],
  35. require_scope: bool = False,
  36. ) -> None:
  37. """
  38. Initialisiert den Scoped-Scope.
  39. Args:
  40. factory: Factory-Funktion zur Instanz-Erstellung.
  41. require_scope: Ob ein aktiver Scope erforderlich ist.
  42. """
  43. super().__init__(factory)
  44. self._require_scope = require_scope
  45. def get_instance(self, key: str) -> T:
  46. """
  47. Holt oder erstellt eine Instanz im aktuellen Scope.
  48. Args:
  49. key: Service-Schlüssel.
  50. Returns:
  51. Service-Instanz.
  52. Raises:
  53. RuntimeError: Wenn require_scope=True und kein Scope aktiv.
  54. """
  55. scope = get_current_scope()
  56. if scope is None:
  57. if self._require_scope:
  58. raise RuntimeError(
  59. f"Kein aktiver Scope für Service '{key}'"
  60. )
  61. # Fallback zu Transient
  62. return self._factory()
  63. # Im Scope nachschauen
  64. if scope.contains(key):
  65. return scope.get(key)
  66. # Neue Instanz erstellen und cachen
  67. instance = self._factory()
  68. scope.set(key, instance)
  69. return instance
  70. class RequestScope(ScopedScope[T], Generic[T]):
  71. """
  72. Alias für ScopedScope, optimiert für HTTP-Requests.
  73. Erfordert immer einen aktiven Scope.
  74. """
  75. def __init__(self, factory: Callable[[], T]) -> None:
  76. """
  77. Initialisiert den Request-Scope.
  78. Args:
  79. factory: Factory-Funktion zur Instanz-Erstellung.
  80. """
  81. super().__init__(factory, require_scope=True)
  82. class ThreadScope(ServiceScope[T], Generic[T]):
  83. """
  84. Thread-lokaler Scope für Services.
  85. Eine Instanz pro Thread.
  86. """
  87. scope_type = ScopeType.THREAD
  88. def __init__(self, factory: Callable[[], T]) -> None:
  89. """
  90. Initialisiert den Thread-Scope.
  91. Args:
  92. factory: Factory-Funktion zur Instanz-Erstellung.
  93. """
  94. super().__init__(factory)
  95. self._local = threading.local()
  96. def get_instance(self, key: str) -> T:
  97. """
  98. Holt oder erstellt die Thread-lokale Instanz.
  99. Args:
  100. key: Service-Schlüssel.
  101. Returns:
  102. Thread-lokale Service-Instanz.
  103. """
  104. if not hasattr(self._local, "instance"):
  105. self._local.instance = self._factory()
  106. return self._local.instance
  107. def dispose(self) -> None:
  108. """Disposed die Instanz des aktuellen Threads."""
  109. if hasattr(self._local, "instance"):
  110. instance = self._local.instance
  111. if hasattr(instance, "dispose"):
  112. try:
  113. instance.dispose()
  114. except Exception:
  115. pass
  116. elif hasattr(instance, "close"):
  117. try:
  118. instance.close()
  119. except Exception:
  120. pass
  121. delattr(self._local, "instance")