python-injection 0.26.1__tar.gz → 0.26.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {python_injection-0.26.1 → python_injection-0.26.3}/PKG-INFO +7 -2
  2. {python_injection-0.26.1 → python_injection-0.26.3}/docs/index.md +4 -0
  3. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/asynchronous.py +4 -9
  4. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/event.py +3 -3
  5. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/lazy.py +1 -1
  6. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/threading.py +4 -4
  7. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/descriptors.py +2 -2
  8. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/injectables.py +4 -6
  9. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/locator.py +2 -3
  10. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/module.py +30 -31
  11. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/scope.py +23 -16
  12. {python_injection-0.26.1 → python_injection-0.26.3}/injection/entrypoint.py +10 -10
  13. {python_injection-0.26.1 → python_injection-0.26.3}/pyproject.toml +3 -2
  14. {python_injection-0.26.1 → python_injection-0.26.3}/.gitignore +0 -0
  15. {python_injection-0.26.1 → python_injection-0.26.3}/LICENSE +0 -0
  16. {python_injection-0.26.1 → python_injection-0.26.3}/injection/__init__.py +0 -0
  17. {python_injection-0.26.1 → python_injection-0.26.3}/injection/__init__.pyi +0 -0
  18. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/__init__.py +0 -0
  19. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/asfunction.py +0 -0
  20. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/__init__.py +0 -0
  21. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/invertible.py +0 -0
  22. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/common/type.py +0 -0
  23. {python_injection-0.26.1 → python_injection-0.26.3}/injection/_core/slots.py +0 -0
  24. {python_injection-0.26.1 → python_injection-0.26.3}/injection/exceptions.py +0 -0
  25. {python_injection-0.26.1 → python_injection-0.26.3}/injection/ext/__init__.py +0 -0
  26. {python_injection-0.26.1 → python_injection-0.26.3}/injection/ext/fastapi.py +0 -0
  27. {python_injection-0.26.1 → python_injection-0.26.3}/injection/ext/fastapi.pyi +0 -0
  28. {python_injection-0.26.1 → python_injection-0.26.3}/injection/loaders.py +0 -0
  29. {python_injection-0.26.1 → python_injection-0.26.3}/injection/py.typed +0 -0
  30. {python_injection-0.26.1 → python_injection-0.26.3}/injection/testing/__init__.py +0 -0
  31. {python_injection-0.26.1 → python_injection-0.26.3}/injection/testing/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.26.1
3
+ Version: 0.26.3
4
4
  Summary: Dead-simple dependency injection framework for Python.
5
5
  Project-URL: Documentation, https://python-injection.remimd.dev
6
6
  Project-URL: Repository, https://github.com/100nm/python-injection
@@ -18,12 +18,13 @@ Classifier: Programming Language :: Python :: 3 :: Only
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
20
  Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3.15
21
22
  Classifier: Topic :: Software Development :: Libraries
22
23
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
25
  Classifier: Topic :: Software Development :: Testing
25
26
  Classifier: Typing :: Typed
26
- Requires-Python: <3.15,>=3.12
27
+ Requires-Python: <3.16,>=3.12
27
28
  Requires-Dist: type-analyzer
28
29
  Provides-Extra: async
29
30
  Requires-Dist: anyio; extra == 'async'
@@ -68,6 +69,7 @@ Simply apply the decorators and the package takes care of the rest.
68
69
  ```python
69
70
  from injection import injectable, inject, singleton
70
71
 
72
+
71
73
  @singleton
72
74
  class Printer:
73
75
  def __init__(self):
@@ -77,6 +79,7 @@ class Printer:
77
79
  self.history.append(message)
78
80
  print(message)
79
81
 
82
+
80
83
  @injectable
81
84
  class Service:
82
85
  def __init__(self, printer: Printer):
@@ -85,10 +88,12 @@ class Service:
85
88
  def hello(self):
86
89
  self.printer.print("Hello world!")
87
90
 
91
+
88
92
  @inject
89
93
  def main(service: Service):
90
94
  service.hello()
91
95
 
96
+
92
97
  if __name__ == "__main__":
93
98
  main()
94
99
  ```
@@ -37,6 +37,7 @@ Simply apply the decorators and the package takes care of the rest.
37
37
  ```python
38
38
  from injection import injectable, inject, singleton
39
39
 
40
+
40
41
  @singleton
41
42
  class Printer:
42
43
  def __init__(self):
@@ -46,6 +47,7 @@ class Printer:
46
47
  self.history.append(message)
47
48
  print(message)
48
49
 
50
+
49
51
  @injectable
50
52
  class Service:
51
53
  def __init__(self, printer: Printer):
@@ -54,10 +56,12 @@ class Service:
54
56
  def hello(self):
55
57
  self.printer.print("Hello world!")
56
58
 
59
+
57
60
  @inject
58
61
  def main(service: Service):
59
62
  service.hello()
60
63
 
64
+
61
65
  if __name__ == "__main__":
62
66
  main()
63
67
  ```
@@ -1,15 +1,10 @@
1
1
  from abc import abstractmethod
2
2
  from collections.abc import Awaitable, Callable, Generator
3
+ from contextlib import AbstractAsyncContextManager
3
4
  from dataclasses import dataclass
4
- from typing import (
5
- Any,
6
- AsyncContextManager,
7
- NoReturn,
8
- Protocol,
9
- runtime_checkable,
10
- )
11
-
12
- AsyncSemaphore: Callable[[int], AsyncContextManager[Any]]
5
+ from typing import Any, NoReturn, Protocol, runtime_checkable
6
+
7
+ AsyncSemaphore: Callable[[int], AbstractAsyncContextManager[Any]]
13
8
 
14
9
  try:
15
10
  import anyio
@@ -1,8 +1,8 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Iterator
3
- from contextlib import ExitStack, contextmanager
3
+ from contextlib import AbstractContextManager, ExitStack, contextmanager
4
4
  from dataclasses import dataclass, field
5
- from typing import ContextManager, Self
5
+ from typing import Self
6
6
  from weakref import WeakSet
7
7
 
8
8
 
@@ -14,7 +14,7 @@ class EventListener(ABC):
14
14
  __slots__ = ("__weakref__",)
15
15
 
16
16
  @abstractmethod
17
- def on_event(self, event: Event, /) -> ContextManager[None] | None:
17
+ def on_event(self, event: Event, /) -> AbstractContextManager[None] | None:
18
18
  raise NotImplementedError
19
19
 
20
20
 
@@ -19,7 +19,7 @@ class Lazy[T](Invertible[T]):
19
19
  @property
20
20
  def is_set(self) -> bool:
21
21
  try:
22
- self.__value
22
+ self.__value # noqa: B018
23
23
  except AttributeError:
24
24
  return False
25
25
 
@@ -1,13 +1,13 @@
1
- from contextlib import nullcontext
1
+ from contextlib import AbstractContextManager, nullcontext
2
2
  from os import getenv
3
3
  from threading import RLock
4
- from typing import Any, ContextManager, Final
4
+ from typing import Any, Final
5
5
 
6
6
  _PYTHON_INJECTION_THREADSAFE: Final[bool] = bool(
7
- int(getenv("PYTHON_INJECTION_THREADSAFE", 0))
7
+ int(getenv("PYTHON_INJECTION_THREADSAFE", "0"))
8
8
  )
9
9
 
10
10
 
11
- def get_lock(threadsafe: bool | None = None) -> ContextManager[Any]:
11
+ def get_lock(threadsafe: bool | None = None) -> AbstractContextManager[Any]:
12
12
  threadsafe = _PYTHON_INJECTION_THREADSAFE if threadsafe is None else threadsafe
13
13
  return RLock() if threadsafe else nullcontext()
@@ -73,7 +73,7 @@ class BoundMappedScope:
73
73
  async def adefine(
74
74
  self,
75
75
  /,
76
- kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
76
+ kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(), # noqa: B008
77
77
  threadsafe: bool | None = None,
78
78
  ) -> AsyncIterator[None]:
79
79
  async with adefine_scope(self.name, kind, threadsafe) as scope:
@@ -86,7 +86,7 @@ class BoundMappedScope:
86
86
  def define(
87
87
  self,
88
88
  /,
89
- kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
89
+ kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(), # noqa: B008
90
90
  threadsafe: bool | None = None,
91
91
  ) -> Iterator[None]:
92
92
  with define_scope(self.name, kind, threadsafe) as scope:
@@ -1,13 +1,11 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Awaitable, Callable, MutableMapping, Sequence
3
- from contextlib import suppress
3
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager, suppress
4
4
  from dataclasses import dataclass, field
5
5
  from functools import partial
6
6
  from typing import (
7
7
  Any,
8
- AsyncContextManager,
9
8
  ClassVar,
10
- ContextManager,
11
9
  NoReturn,
12
10
  Protocol,
13
11
  Self,
@@ -55,7 +53,7 @@ class TransientInjectable[T](Injectable[T]):
55
53
  class CacheLogic[T]:
56
54
  __slots__ = ("__semaphore",)
57
55
 
58
- __semaphore: AsyncContextManager[Any]
56
+ __semaphore: AbstractAsyncContextManager[Any]
59
57
 
60
58
  def __init__(self) -> None:
61
59
  self.__semaphore = AsyncSemaphore(1)
@@ -169,7 +167,7 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
169
167
  return partial(cls, scope_names=names)
170
168
 
171
169
 
172
- class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
170
+ class AsyncCMScopedInjectable[T](ScopedInjectable[AbstractAsyncContextManager[T], T]):
173
171
  __slots__ = ()
174
172
 
175
173
  async def abuild(self, scope: Scope) -> T:
@@ -180,7 +178,7 @@ class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
180
178
  raise RuntimeError("Can't use async context manager synchronously.")
181
179
 
182
180
 
183
- class CMScopedInjectable[T](ScopedInjectable[ContextManager[T], T]):
181
+ class CMScopedInjectable[T](ScopedInjectable[AbstractContextManager[T], T]):
184
182
  __slots__ = ()
185
183
 
186
184
  async def abuild(self, scope: Scope) -> T:
@@ -2,13 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from collections.abc import Awaitable, Callable, Collection, Iterable, Iterator
5
- from contextlib import suppress
5
+ from contextlib import AbstractContextManager, suppress
6
6
  from dataclasses import dataclass, field
7
7
  from enum import StrEnum
8
8
  from inspect import iscoroutinefunction
9
9
  from typing import (
10
10
  Any,
11
- ContextManager,
12
11
  Literal,
13
12
  NamedTuple,
14
13
  Protocol,
@@ -226,7 +225,7 @@ class Locator:
226
225
  self.__channel.add_listener(listener)
227
226
  return self
228
227
 
229
- def dispatch(self, event: Event) -> ContextManager[None]:
228
+ def dispatch(self, event: Event) -> AbstractContextManager[None]:
230
229
  return self.__channel.dispatch(event)
231
230
 
232
231
  def __iter_injectables(
@@ -14,7 +14,13 @@ from collections.abc import (
14
14
  Iterator,
15
15
  Mapping,
16
16
  )
17
- from contextlib import asynccontextmanager, contextmanager, suppress
17
+ from contextlib import (
18
+ AbstractAsyncContextManager,
19
+ AbstractContextManager,
20
+ asynccontextmanager,
21
+ contextmanager,
22
+ suppress,
23
+ )
18
24
  from dataclasses import dataclass, field
19
25
  from enum import StrEnum
20
26
  from functools import partialmethod, singledispatchmethod, update_wrapper
@@ -30,17 +36,7 @@ from inspect import (
30
36
  from inspect import signature as inspect_signature
31
37
  from logging import Logger, getLogger
32
38
  from types import MethodType
33
- from typing import (
34
- TYPE_CHECKING,
35
- Any,
36
- AsyncContextManager,
37
- ClassVar,
38
- ContextManager,
39
- Literal,
40
- NamedTuple,
41
- Self,
42
- overload,
43
- )
39
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal, NamedTuple, Self, overload
44
40
 
45
41
  from type_analyzer import MatchingTypesConfig, iter_matching_types, matching_types
46
42
 
@@ -157,7 +153,7 @@ class Priority(StrEnum):
157
153
  type PriorityStr = Literal["low", "high"]
158
154
 
159
155
  type ContextManagerRecipe[**P, T] = (
160
- Callable[P, ContextManager[T]] | Callable[P, AsyncContextManager[T]]
156
+ Callable[P, AbstractContextManager[T]] | Callable[P, AbstractAsyncContextManager[T]]
161
157
  )
162
158
  type GeneratorRecipe[**P, T] = (
163
159
  Callable[P, Generator[T, Any, Any]] | Callable[P, AsyncGenerator[T, Any]]
@@ -231,7 +227,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
231
227
  ignore_type_hint: bool = False,
232
228
  inject: bool = True,
233
229
  on: TypeInfo[T] = (),
234
- mode: Mode | ModeStr = Mode.get_default(),
230
+ mode: Mode | ModeStr = Mode.get_default(), # noqa: B008
235
231
  ) -> Any:
236
232
  def decorator(wp: Recipe[P, T]) -> Recipe[P, T]:
237
233
  hints = on if ignore_type_hint else (wp, on)
@@ -255,7 +251,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
255
251
  ignore_type_hint: bool = False,
256
252
  inject: bool = True,
257
253
  on: TypeInfo[T] = (),
258
- mode: Mode | ModeStr = Mode.get_default(),
254
+ mode: Mode | ModeStr = Mode.get_default(), # noqa: B008
259
255
  ) -> Any:
260
256
  def decorator(
261
257
  wrapped: Recipe[P, T] | GeneratorRecipe[P, T],
@@ -307,7 +303,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
307
303
  on: TypeInfo[T] = (),
308
304
  *,
309
305
  alias: bool = False,
310
- mode: Mode | ModeStr = Mode.get_default(),
306
+ mode: Mode | ModeStr = Mode.get_default(), # noqa: B008
311
307
  ) -> T:
312
308
  if not alias:
313
309
  on = (type(instance), on)
@@ -325,7 +321,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
325
321
  /,
326
322
  scope_name: str,
327
323
  *,
328
- mode: Mode | ModeStr = Mode.get_default(),
324
+ mode: Mode | ModeStr = Mode.get_default(), # noqa: B008
329
325
  ) -> SlotKey[T]:
330
326
  injectable = ScopedSlotInjectable(cls, scope_name)
331
327
  broker = StaticInjectableBroker(injectable)
@@ -341,8 +337,8 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
341
337
  ) -> Any:
342
338
  def decorator(wp: Callable[P, T]) -> Callable[P, T]:
343
339
  if isclass(wp):
344
- wp.__init__ = self.inject(wp.__init__, threadsafe=threadsafe)
345
- return wp
340
+ wp.__init__ = self.inject(wp.__init__, threadsafe=threadsafe) # type: ignore[method-assign]
341
+ return wp # type: ignore[return-value]
346
342
 
347
343
  return self.make_injected_function(wp, threadsafe)
348
344
 
@@ -583,7 +579,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
583
579
  self,
584
580
  module: Module,
585
581
  *,
586
- priority: Priority | PriorityStr = Priority.get_default(),
582
+ priority: Priority | PriorityStr = Priority.get_default(), # noqa: B008
587
583
  ) -> Self:
588
584
  if module is self:
589
585
  raise ModuleError("Module can't be used by itself.")
@@ -604,10 +600,9 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
604
600
  def stop_using(self, module: Module) -> Self:
605
601
  event = ModuleRemoved(self, module)
606
602
 
607
- with suppress(KeyError):
608
- with self.dispatch(event):
609
- self.__modules.pop(module)
610
- module.remove_listener(self)
603
+ with suppress(KeyError), self.dispatch(event):
604
+ self.__modules.pop(module)
605
+ module.remove_listener(self)
611
606
 
612
607
  return self
613
608
 
@@ -616,7 +611,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
616
611
  self,
617
612
  module: Module,
618
613
  *,
619
- priority: Priority | PriorityStr = Priority.get_default(),
614
+ priority: Priority | PriorityStr = Priority.get_default(), # noqa: B008
620
615
  unlock: bool = False,
621
616
  ) -> Iterator[Self]:
622
617
  self.use(module, priority=priority)
@@ -660,7 +655,7 @@ class Module(EventListener, InjectionProvider): # type: ignore[misc]
660
655
  self.__channel.remove_listener(listener)
661
656
  return self
662
657
 
663
- def on_event(self, event: Event, /) -> ContextManager[None]:
658
+ def on_event(self, event: Event, /) -> AbstractContextManager[None]:
664
659
  self_event = ModuleEventProxy(self, event)
665
660
  return self.dispatch(self_event)
666
661
 
@@ -829,7 +824,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
829
824
  )
830
825
 
831
826
  __dependencies: Dependencies
832
- __lock: ContextManager[Any]
827
+ __lock: AbstractContextManager[Any]
833
828
  __owner: type | None
834
829
  __signature: Signature
835
830
  __tasks: deque[Callable[..., Any]]
@@ -904,8 +899,12 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
904
899
  self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
905
900
  return self
906
901
 
907
- def task[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
908
- def decorator(wp: Callable[_P, _T]) -> Callable[_P, _T]:
902
+ def task[**TaskP, TaskR](
903
+ self,
904
+ wrapped: Callable[TaskP, TaskR] | None = None,
905
+ /,
906
+ ) -> Any:
907
+ def decorator(wp: Callable[TaskP, TaskR]) -> Callable[TaskP, TaskR]:
909
908
  self.__tasks.append(wp)
910
909
  return wp
911
910
 
@@ -920,7 +919,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
920
919
  return self
921
920
 
922
921
  @singledispatchmethod
923
- def on_event(self, event: Event, /) -> ContextManager[None] | None:
922
+ def on_event(self, event: Event, /) -> AbstractContextManager[None] | None:
924
923
  return None
925
924
 
926
925
  @on_event.register
@@ -941,7 +940,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
941
940
  arguments: dict[str, Any],
942
941
  additional_arguments: dict[str, Any],
943
942
  ) -> Arguments:
944
- bound = BoundArguments(self.signature, additional_arguments | arguments) # type: ignore[arg-type]
943
+ bound = BoundArguments(self.signature, additional_arguments | arguments)
945
944
  return Arguments(bound.args, bound.kwargs)
946
945
 
947
946
  def __run_tasks(self) -> None:
@@ -4,7 +4,14 @@ import itertools
4
4
  from abc import ABC, abstractmethod
5
5
  from collections import defaultdict
6
6
  from collections.abc import AsyncIterator, Collection, Iterator, Mapping, MutableMapping
7
- from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
7
+ from contextlib import (
8
+ AbstractAsyncContextManager,
9
+ AbstractContextManager,
10
+ AsyncExitStack,
11
+ ExitStack,
12
+ asynccontextmanager,
13
+ contextmanager,
14
+ )
8
15
  from contextvars import ContextVar
9
16
  from dataclasses import dataclass, field
10
17
  from enum import StrEnum
@@ -13,8 +20,6 @@ from types import EllipsisType, TracebackType
13
20
  from typing import (
14
21
  TYPE_CHECKING,
15
22
  Any,
16
- AsyncContextManager,
17
- ContextManager,
18
23
  Final,
19
24
  Literal,
20
25
  NoReturn,
@@ -56,7 +61,7 @@ class ScopeResolver(Protocol):
56
61
  raise NotImplementedError
57
62
 
58
63
  @abstractmethod
59
- def bind(self, scope: Scope) -> ContextManager[None]:
64
+ def bind(self, scope: Scope) -> AbstractContextManager[None]:
60
65
  raise NotImplementedError
61
66
 
62
67
  @abstractmethod
@@ -128,7 +133,7 @@ __scope_resolvers: Final[Mapping[str, Mapping[str, ScopeResolver]]] = {
128
133
  async def adefine_scope(
129
134
  name: str,
130
135
  /,
131
- kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
136
+ kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(), # noqa: B008
132
137
  threadsafe: bool | None = None,
133
138
  ) -> AsyncIterator[ScopeFacade]:
134
139
  async with AsyncScope() as scope:
@@ -140,12 +145,11 @@ async def adefine_scope(
140
145
  def define_scope(
141
146
  name: str,
142
147
  /,
143
- kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
148
+ kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(), # noqa: B008
144
149
  threadsafe: bool | None = None,
145
150
  ) -> Iterator[ScopeFacade]:
146
- with SyncScope() as scope:
147
- with _bind_scope(name, scope, kind, threadsafe) as facade:
148
- yield facade
151
+ with SyncScope() as scope, _bind_scope(name, scope, kind, threadsafe) as facade:
152
+ yield facade
149
153
 
150
154
 
151
155
  if TYPE_CHECKING: # pragma: no cover
@@ -234,11 +238,11 @@ class Scope(Protocol):
234
238
  cache: MutableMapping[SlotKey[Any], Any]
235
239
 
236
240
  @abstractmethod
237
- async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
241
+ async def aenter[T](self, context_manager: AbstractAsyncContextManager[T]) -> T:
238
242
  raise NotImplementedError
239
243
 
240
244
  @abstractmethod
241
- def enter[T](self, context_manager: ContextManager[T]) -> T:
245
+ def enter[T](self, context_manager: AbstractContextManager[T]) -> T:
242
246
  raise NotImplementedError
243
247
 
244
248
 
@@ -270,10 +274,10 @@ class AsyncScope(BaseScope[AsyncExitStack]):
270
274
  self.close()
271
275
  return await self.delegate.__aexit__(exc_type, exc_value, traceback)
272
276
 
273
- async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
277
+ async def aenter[T](self, context_manager: AbstractAsyncContextManager[T]) -> T:
274
278
  return await self.delegate.enter_async_context(context_manager)
275
279
 
276
- def enter[T](self, context_manager: ContextManager[T]) -> T:
280
+ def enter[T](self, context_manager: AbstractContextManager[T]) -> T:
277
281
  return self.delegate.enter_context(context_manager)
278
282
 
279
283
 
@@ -296,10 +300,13 @@ class SyncScope(BaseScope[ExitStack]):
296
300
  self.close()
297
301
  return self.delegate.__exit__(exc_type, exc_value, traceback)
298
302
 
299
- async def aenter[T](self, context_manager: AsyncContextManager[T]) -> NoReturn:
303
+ async def aenter[T](
304
+ self,
305
+ context_manager: AbstractAsyncContextManager[T],
306
+ ) -> NoReturn:
300
307
  raise ScopeError("Synchronous scope doesn't support async context manager.")
301
308
 
302
- def enter[T](self, context_manager: ContextManager[T]) -> T:
309
+ def enter[T](self, context_manager: AbstractContextManager[T]) -> T:
303
310
  return self.delegate.enter_context(context_manager)
304
311
 
305
312
 
@@ -319,7 +326,7 @@ class ScopeFacade(Protocol):
319
326
  @dataclass(repr=False, frozen=True, slots=True)
320
327
  class _UserScope(ScopeFacade):
321
328
  scope: Scope
322
- lock: ContextManager[Any]
329
+ lock: AbstractContextManager[Any]
323
330
 
324
331
  def set_slot[T](self, key: SlotKey[T], value: T) -> Self:
325
332
  return self.slot_map({key: value})
@@ -194,18 +194,18 @@ class EntrypointBuilder[**P, T1, T2](_EntrypointDecorator[P, T1, T2]):
194
194
 
195
195
  return decorator(wrapped) if wrapped else decorator
196
196
 
197
- def async_to_sync[_T](
198
- self: EntrypointBuilder[P, T1, Awaitable[_T]],
199
- run: Callable[[Coroutine[Any, Any, _T]], _T] = asyncio.run,
197
+ def async_to_sync[T](
198
+ self: EntrypointBuilder[P, T1, Awaitable[T]],
199
+ run: Callable[[Coroutine[Any, Any, T]], T] = asyncio.run,
200
200
  /,
201
- ) -> EntrypointBuilder[P, T1, _T]:
201
+ ) -> EntrypointBuilder[P, T1, T]:
202
202
  return self._add_rule(_AsyncToSyncRule(run)) # type: ignore[arg-type]
203
203
 
204
- def decorate[_T](
204
+ def decorate[T](
205
205
  self,
206
- decorator: Callable[[Callable[P, T2]], Callable[P, _T]],
206
+ decorator: Callable[[Callable[P, T2]], Callable[P, T]],
207
207
  /,
208
- ) -> EntrypointBuilder[P, T1, _T]:
208
+ ) -> EntrypointBuilder[P, T1, T]:
209
209
  return self._add_rule(_DecorateRule(decorator))
210
210
 
211
211
  def inject(self) -> Self:
@@ -224,10 +224,10 @@ class EntrypointBuilder[**P, T1, T2](_EntrypointDecorator[P, T1, T2]):
224
224
  self._add_rule(_LoadProfileRule(self.profile_loader, name))
225
225
  return self
226
226
 
227
- def _add_rule[_T](
227
+ def _add_rule[T](
228
228
  self,
229
- rule: Rule[P, T2, _T],
230
- ) -> EntrypointBuilder[P, T1, _T]:
229
+ rule: Rule[P, T2, T],
230
+ ) -> EntrypointBuilder[P, T1, T]:
231
231
  self.__rules.append(rule)
232
232
  return self # type: ignore[return-value]
233
233
 
@@ -24,12 +24,12 @@ test = [
24
24
 
25
25
  [project]
26
26
  name = "python-injection"
27
- version = "0.26.1"
27
+ version = "0.26.3"
28
28
  description = "Dead-simple dependency injection framework for Python."
29
29
  license = "MIT"
30
30
  license-files = ["LICENSE"]
31
31
  readme = "docs/index.md"
32
- requires-python = ">=3.12, <3.15"
32
+ requires-python = ">=3.12, <3.16"
33
33
  authors = [{ name = "remimd" }]
34
34
  keywords = ["dependencies", "dependency", "inject", "injection"]
35
35
  classifiers = [
@@ -44,6 +44,7 @@ classifiers = [
44
44
  "Programming Language :: Python :: 3.12",
45
45
  "Programming Language :: Python :: 3.13",
46
46
  "Programming Language :: Python :: 3.14",
47
+ "Programming Language :: Python :: 3.15",
47
48
  "Operating System :: OS Independent",
48
49
  "Intended Audience :: Developers",
49
50
  "Natural Language :: English",