python-injection 0.18.13__tar.gz → 0.19.0__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.18.13 → python_injection-0.19.0}/PKG-INFO +20 -7
  2. {python_injection-0.18.13 → python_injection-0.19.0}/README.md +17 -6
  3. {python_injection-0.18.13 → python_injection-0.19.0}/injection/__init__.py +2 -0
  4. {python_injection-0.18.13 → python_injection-0.19.0}/injection/__init__.pyi +44 -23
  5. python_injection-0.19.0/injection/_core/asfunction.py +47 -0
  6. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/asynchronous.py +20 -14
  7. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/threading.py +1 -1
  8. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/descriptors.py +1 -1
  9. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/injectables.py +2 -5
  10. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/module.py +34 -24
  11. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/scope.py +3 -3
  12. {python_injection-0.18.13 → python_injection-0.19.0}/injection/entrypoint.py +16 -16
  13. {python_injection-0.18.13 → python_injection-0.19.0}/injection/ext/fastapi.py +5 -5
  14. {python_injection-0.18.13 → python_injection-0.19.0}/injection/loaders.py +8 -2
  15. {python_injection-0.18.13 → python_injection-0.19.0}/pyproject.toml +4 -1
  16. {python_injection-0.18.13 → python_injection-0.19.0}/.gitignore +0 -0
  17. {python_injection-0.18.13 → python_injection-0.19.0}/LICENSE +0 -0
  18. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/__init__.py +0 -0
  19. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/__init__.py +0 -0
  20. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/event.py +0 -0
  21. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/invertible.py +0 -0
  22. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/key.py +0 -0
  23. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/lazy.py +0 -0
  24. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/common/type.py +0 -0
  25. {python_injection-0.18.13 → python_injection-0.19.0}/injection/_core/slots.py +0 -0
  26. {python_injection-0.18.13 → python_injection-0.19.0}/injection/exceptions.py +0 -0
  27. {python_injection-0.18.13 → python_injection-0.19.0}/injection/ext/__init__.py +0 -0
  28. {python_injection-0.18.13 → python_injection-0.19.0}/injection/ext/fastapi.pyi +0 -0
  29. {python_injection-0.18.13 → python_injection-0.19.0}/injection/py.typed +0 -0
  30. {python_injection-0.18.13 → python_injection-0.19.0}/injection/testing/__init__.py +0 -0
  31. {python_injection-0.18.13 → python_injection-0.19.0}/injection/testing/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.18.13
3
+ Version: 0.19.0
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -14,6 +14,8 @@ Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
17
19
  Classifier: Topic :: Software Development :: Libraries
18
20
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
19
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -27,11 +29,10 @@ Description-Content-Type: text/markdown
27
29
  # python-injection
28
30
 
29
31
  [![CI](https://github.com/100nm/python-injection/actions/workflows/ci.yml/badge.svg)](https://github.com/100nm/python-injection)
30
- [![PyPI](https://img.shields.io/pypi/v/python-injection.svg?color=blue)](https://pypi.org/project/python-injection)
32
+ [![PyPI - Version](https://img.shields.io/pypi/v/python-injection.svg?color=blue)](https://pypi.org/project/python-injection)
33
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/python-injection.svg?color=blue)](https://pypistats.org/packages/python-injection)
31
34
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
32
35
 
33
- Fast and easy dependency injection framework.
34
-
35
36
  ## Installation
36
37
 
37
38
  ⚠️ _Requires Python 3.12 or higher_
@@ -40,12 +41,22 @@ Fast and easy dependency injection framework.
40
41
  pip install python-injection
41
42
  ```
42
43
 
44
+ ## Features
45
+
46
+ * Automatic dependency resolution based on type hints.
47
+ * Support for multiple dependency lifetimes: `transient`, `singleton`, `constant`, and `scoped`.
48
+ * Works seamlessly in both `async` and `sync` environments.
49
+ * Separation of dependency sets using modules.
50
+ * Runtime switching between different sets of dependencies.
51
+ * Centralized setup logic using entrypoints.
52
+ * Built-in type annotation for easy integration with [`FastAPI`](https://github.com/fastapi/fastapi).
53
+ * Lazy dependency resolution for optimized performance.
54
+
43
55
  ## Motivations
44
56
 
45
57
  1. Easy to use
46
58
  2. No impact on class and function definitions
47
- 3. Easily interchangeable dependencies _(depending on the runtime environment, for example)_
48
- 4. No prerequisites
59
+ 3. No tedious configuration
49
60
 
50
61
  ## Quick start
51
62
 
@@ -90,5 +101,7 @@ if __name__ == "__main__":
90
101
  * [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md)
91
102
  * [**Loaders**](https://github.com/100nm/python-injection/tree/prod/documentation/loaders.md)
92
103
  * [**Entrypoint**](https://github.com/100nm/python-injection/tree/prod/documentation/entrypoint.md)
93
- * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md)
104
+ * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations)
105
+ * [**FastAPI**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations/fastapi.md)
106
+ * [**What if my framework isn't listed?**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations/unlisted-framework.md)
94
107
  * [**Concrete example**](https://github.com/100nm/python-injection-example)
@@ -1,11 +1,10 @@
1
1
  # python-injection
2
2
 
3
3
  [![CI](https://github.com/100nm/python-injection/actions/workflows/ci.yml/badge.svg)](https://github.com/100nm/python-injection)
4
- [![PyPI](https://img.shields.io/pypi/v/python-injection.svg?color=blue)](https://pypi.org/project/python-injection)
4
+ [![PyPI - Version](https://img.shields.io/pypi/v/python-injection.svg?color=blue)](https://pypi.org/project/python-injection)
5
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/python-injection.svg?color=blue)](https://pypistats.org/packages/python-injection)
5
6
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
6
7
 
7
- Fast and easy dependency injection framework.
8
-
9
8
  ## Installation
10
9
 
11
10
  ⚠️ _Requires Python 3.12 or higher_
@@ -14,12 +13,22 @@ Fast and easy dependency injection framework.
14
13
  pip install python-injection
15
14
  ```
16
15
 
16
+ ## Features
17
+
18
+ * Automatic dependency resolution based on type hints.
19
+ * Support for multiple dependency lifetimes: `transient`, `singleton`, `constant`, and `scoped`.
20
+ * Works seamlessly in both `async` and `sync` environments.
21
+ * Separation of dependency sets using modules.
22
+ * Runtime switching between different sets of dependencies.
23
+ * Centralized setup logic using entrypoints.
24
+ * Built-in type annotation for easy integration with [`FastAPI`](https://github.com/fastapi/fastapi).
25
+ * Lazy dependency resolution for optimized performance.
26
+
17
27
  ## Motivations
18
28
 
19
29
  1. Easy to use
20
30
  2. No impact on class and function definitions
21
- 3. Easily interchangeable dependencies _(depending on the runtime environment, for example)_
22
- 4. No prerequisites
31
+ 3. No tedious configuration
23
32
 
24
33
  ## Quick start
25
34
 
@@ -64,5 +73,7 @@ if __name__ == "__main__":
64
73
  * [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md)
65
74
  * [**Loaders**](https://github.com/100nm/python-injection/tree/prod/documentation/loaders.md)
66
75
  * [**Entrypoint**](https://github.com/100nm/python-injection/tree/prod/documentation/entrypoint.md)
67
- * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md)
76
+ * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations)
77
+ * [**FastAPI**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations/fastapi.md)
78
+ * [**What if my framework isn't listed?**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations/unlisted-framework.md)
68
79
  * [**Concrete example**](https://github.com/100nm/python-injection-example)
@@ -1,3 +1,4 @@
1
+ from ._core.asfunction import asfunction
1
2
  from ._core.descriptors import LazyInstance
2
3
  from ._core.injectables import Injectable
3
4
  from ._core.module import Mode, Module, Priority, mod
@@ -18,6 +19,7 @@ __all__ = (
18
19
  "afind_instance",
19
20
  "aget_instance",
20
21
  "aget_lazy_instance",
22
+ "asfunction",
21
23
  "constant",
22
24
  "define_scope",
23
25
  "find_instance",
@@ -30,19 +30,35 @@ set_constant = __MODULE.set_constant
30
30
  should_be_injectable = __MODULE.should_be_injectable
31
31
  singleton = __MODULE.singleton
32
32
 
33
+ @overload
34
+ def asfunction[**P, T](
35
+ wrapped: type[Callable[P, T]],
36
+ /,
37
+ *,
38
+ module: Module = ...,
39
+ threadsafe: bool | None = ...,
40
+ ) -> Callable[P, T]: ...
41
+ @overload
42
+ def asfunction[**P, T](
43
+ wrapped: None = ...,
44
+ /,
45
+ *,
46
+ module: Module = ...,
47
+ threadsafe: bool | None = ...,
48
+ ) -> Callable[[type[Callable[P, T]]], Callable[P, T]]: ...
33
49
  @asynccontextmanager
34
50
  def adefine_scope(
35
51
  name: str,
36
52
  /,
37
53
  kind: ScopeKind | ScopeKindStr = ...,
38
- threadsafe: bool = ...,
54
+ threadsafe: bool | None = ...,
39
55
  ) -> AsyncIterator[Scope]: ...
40
56
  @contextmanager
41
57
  def define_scope(
42
58
  name: str,
43
59
  /,
44
60
  kind: ScopeKind | ScopeKindStr = ...,
45
- threadsafe: bool = ...,
61
+ threadsafe: bool | None = ...,
46
62
  ) -> Iterator[Scope]: ...
47
63
  def mod(name: str = ..., /) -> Module:
48
64
  """
@@ -80,7 +96,7 @@ class LazyInstance[T]:
80
96
  default: T = ...,
81
97
  *,
82
98
  module: Module = ...,
83
- threadsafe: bool = ...,
99
+ threadsafe: bool | None = ...,
84
100
  ) -> None: ...
85
101
  @overload
86
102
  def __get__(self, instance: object, owner: type | None = ...) -> T: ...
@@ -108,14 +124,14 @@ class Module:
108
124
  wrapped: Callable[P, T] = ...,
109
125
  /,
110
126
  *,
111
- threadsafe: bool = ...,
127
+ threadsafe: bool | None = ...,
112
128
  ) -> Any:
113
129
  """
114
130
  Decorator applicable to a class or function. Inject function dependencies using
115
131
  parameter type annotations. If applied to a class, the dependencies resolved
116
132
  will be those of the `__init__` method.
117
133
 
118
- With `threadsafe=True`, the injection logic is wrapped in a `threading.Lock`.
134
+ With `threadsafe=True`, the injection logic is wrapped in a `threading.RLock`.
119
135
  """
120
136
 
121
137
  def injectable[**P, T](
@@ -149,7 +165,7 @@ class Module:
149
165
  always be the same.
150
166
  """
151
167
 
152
- def scoped[**P, T](
168
+ def scoped[T](
153
169
  self,
154
170
  scope_name: str,
155
171
  /,
@@ -171,18 +187,18 @@ class Module:
171
187
  registered.
172
188
  """
173
189
 
174
- def constant[T](
190
+ def constant[**P, T](
175
191
  self,
176
- wrapped: type[T] = ...,
192
+ wrapped: _Recipe[P, T] = ...,
177
193
  /,
178
194
  *,
179
195
  on: _TypeInfo[T] = ...,
180
196
  mode: Mode | ModeStr = ...,
181
197
  ) -> Any:
182
198
  """
183
- Decorator applicable to a class. It is used to indicate how the constant is
184
- constructed. At injection time, the injected instance will always be the same.
185
- Unlike `@singleton`, dependencies will not be resolved.
199
+ Decorator applicable to a class or function. It is used to indicate how the
200
+ constant is constructed. At injection time, the injected instance will always
201
+ be the same. Unlike `@singleton`, dependencies will not be resolved.
186
202
  """
187
203
 
188
204
  def set_constant[T](
@@ -211,21 +227,26 @@ class Module:
211
227
  self,
212
228
  wrapped: Callable[P, T],
213
229
  /,
214
- threadsafe: bool = ...,
230
+ threadsafe: bool | None = ...,
215
231
  ) -> Callable[P, T]: ...
216
232
  def make_async_factory[T](
217
233
  self,
218
234
  wrapped: type[T],
219
235
  /,
220
- threadsafe: bool = ...,
236
+ threadsafe: bool | None = ...,
221
237
  ) -> Callable[..., Awaitable[T]]: ...
222
238
  async def afind_instance[T](
223
239
  self,
224
240
  cls: _InputType[T],
225
241
  *,
226
- threadsafe: bool = ...,
242
+ threadsafe: bool | None = ...,
227
243
  ) -> T: ...
228
- def find_instance[T](self, cls: _InputType[T], *, threadsafe: bool = ...) -> T:
244
+ def find_instance[T](
245
+ self,
246
+ cls: _InputType[T],
247
+ *,
248
+ threadsafe: bool | None = ...,
249
+ ) -> T:
229
250
  """
230
251
  Function used to retrieve an instance associated with the type passed in
231
252
  parameter or an exception will be raised.
@@ -237,7 +258,7 @@ class Module:
237
258
  cls: _InputType[T],
238
259
  default: Default,
239
260
  *,
240
- threadsafe: bool = ...,
261
+ threadsafe: bool | None = ...,
241
262
  ) -> T | Default: ...
242
263
  @overload
243
264
  async def aget_instance[T](
@@ -245,7 +266,7 @@ class Module:
245
266
  cls: _InputType[T],
246
267
  default: T = ...,
247
268
  *,
248
- threadsafe: bool = ...,
269
+ threadsafe: bool | None = ...,
249
270
  ) -> T: ...
250
271
  @overload
251
272
  def get_instance[T, Default](
@@ -253,7 +274,7 @@ class Module:
253
274
  cls: _InputType[T],
254
275
  default: Default,
255
276
  *,
256
- threadsafe: bool = ...,
277
+ threadsafe: bool | None = ...,
257
278
  ) -> T | Default:
258
279
  """
259
280
  Function used to retrieve an instance associated with the type passed in
@@ -266,7 +287,7 @@ class Module:
266
287
  cls: _InputType[T],
267
288
  default: T = ...,
268
289
  *,
269
- threadsafe: bool = ...,
290
+ threadsafe: bool | None = ...,
270
291
  ) -> T: ...
271
292
  @overload
272
293
  def aget_lazy_instance[T, Default](
@@ -274,7 +295,7 @@ class Module:
274
295
  cls: _InputType[T],
275
296
  default: Default,
276
297
  *,
277
- threadsafe: bool = ...,
298
+ threadsafe: bool | None = ...,
278
299
  ) -> Awaitable[T | Default]: ...
279
300
  @overload
280
301
  def aget_lazy_instance[T](
@@ -282,7 +303,7 @@ class Module:
282
303
  cls: _InputType[T],
283
304
  default: T = ...,
284
305
  *,
285
- threadsafe: bool = ...,
306
+ threadsafe: bool | None = ...,
286
307
  ) -> Awaitable[T]: ...
287
308
  @overload
288
309
  def get_lazy_instance[T, Default](
@@ -290,7 +311,7 @@ class Module:
290
311
  cls: _InputType[T],
291
312
  default: Default,
292
313
  *,
293
- threadsafe: bool = ...,
314
+ threadsafe: bool | None = ...,
294
315
  ) -> _Invertible[T | Default]:
295
316
  """
296
317
  Function used to retrieve an instance associated with the type passed in
@@ -306,7 +327,7 @@ class Module:
306
327
  cls: _InputType[T],
307
328
  default: T = ...,
308
329
  *,
309
- threadsafe: bool = ...,
330
+ threadsafe: bool | None = ...,
310
331
  ) -> _Invertible[T]: ...
311
332
  def init_modules(self, *modules: Module) -> Self:
312
333
  """
@@ -0,0 +1,47 @@
1
+ from collections.abc import Callable
2
+ from functools import wraps
3
+ from inspect import iscoroutinefunction
4
+ from typing import Any
5
+
6
+ from injection._core.common.asynchronous import Caller
7
+ from injection._core.module import Module, mod
8
+
9
+
10
+ def asfunction[**P, T](
11
+ wrapped: type[Callable[P, T]] | None = None,
12
+ /,
13
+ *,
14
+ module: Module | None = None,
15
+ threadsafe: bool | None = None,
16
+ ) -> Any:
17
+ module = module or mod()
18
+
19
+ def decorator(wp: type[Callable[P, T]]) -> Callable[P, T]:
20
+ get_method = wp.__call__.__get__
21
+ method = get_method(NotImplemented)
22
+ factory: Caller[..., Callable[P, T]] = module.make_injected_function(
23
+ wp,
24
+ threadsafe=threadsafe,
25
+ ).__inject_metadata__
26
+
27
+ wrapper: Callable[P, T]
28
+
29
+ if iscoroutinefunction(method):
30
+
31
+ @wraps(method)
32
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> Any:
33
+ self = await factory.acall()
34
+ return await get_method(self)(*args, **kwargs)
35
+
36
+ else:
37
+
38
+ @wraps(method)
39
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
40
+ self = factory.call()
41
+ return get_method(self)(*args, **kwargs)
42
+
43
+ wrapper.__name__ = wp.__name__
44
+ wrapper.__qualname__ = wp.__qualname__
45
+ return wrapper
46
+
47
+ return decorator(wrapped) if wrapped else decorator
@@ -1,7 +1,26 @@
1
1
  from abc import abstractmethod
2
2
  from collections.abc import Awaitable, Callable, Generator
3
3
  from dataclasses import dataclass
4
- from typing import Any, AsyncContextManager, NoReturn, Protocol, runtime_checkable
4
+ from typing import (
5
+ Any,
6
+ AsyncContextManager,
7
+ NoReturn,
8
+ Protocol,
9
+ runtime_checkable,
10
+ )
11
+
12
+ AsyncSemaphore: Callable[[int], AsyncContextManager[Any]]
13
+
14
+ try:
15
+ import anyio
16
+
17
+ except ImportError: # pragma: no cover
18
+ import asyncio
19
+
20
+ AsyncSemaphore = asyncio.Semaphore
21
+
22
+ else:
23
+ AsyncSemaphore = anyio.Semaphore
5
24
 
6
25
 
7
26
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -45,16 +64,3 @@ class SyncCaller[**P, T](Caller[P, T]):
45
64
 
46
65
  def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
47
66
  return self.callable(*args, **kwargs)
48
-
49
-
50
- try:
51
- import anyio
52
-
53
- def create_semaphore(value: int) -> AsyncContextManager[Any]:
54
- return anyio.Semaphore(value)
55
-
56
- except ImportError: # pragma: no cover
57
- import asyncio
58
-
59
- def create_semaphore(value: int) -> AsyncContextManager[Any]:
60
- return asyncio.Semaphore(value)
@@ -3,5 +3,5 @@ from threading import RLock
3
3
  from typing import Any, ContextManager
4
4
 
5
5
 
6
- def get_lock(threadsafe: bool) -> ContextManager[Any]:
6
+ def get_lock(threadsafe: bool | None = None) -> ContextManager[Any]:
7
7
  return RLock() if threadsafe else nullcontext()
@@ -17,7 +17,7 @@ class LazyInstance[T]:
17
17
  default: T = NotImplemented,
18
18
  *,
19
19
  module: Module | None = None,
20
- threadsafe: bool = False,
20
+ threadsafe: bool | None = None,
21
21
  ) -> None:
22
22
  module = module or mod()
23
23
  self.__value = module.get_lazy_instance(cls, default, threadsafe=threadsafe)
@@ -13,10 +13,7 @@ from typing import (
13
13
  runtime_checkable,
14
14
  )
15
15
 
16
- from injection._core.common.asynchronous import Caller
17
- from injection._core.common.asynchronous import (
18
- create_semaphore as _create_async_semaphore,
19
- )
16
+ from injection._core.common.asynchronous import AsyncSemaphore, Caller
20
17
  from injection._core.scope import (
21
18
  Scope,
22
19
  get_scope,
@@ -64,7 +61,7 @@ class CacheLogic[T]:
64
61
  __semaphore: AsyncContextManager[Any]
65
62
 
66
63
  def __init__(self) -> None:
67
- self.__semaphore = _create_async_semaphore(1)
64
+ self.__semaphore = AsyncSemaphore(1)
68
65
 
69
66
  async def aget_or_create[K](
70
67
  self,
@@ -503,15 +503,15 @@ class Module(Broker, EventListener):
503
503
 
504
504
  return decorator(wrapped) if wrapped else decorator
505
505
 
506
- def constant[T](
506
+ def constant[**P, T](
507
507
  self,
508
- wrapped: type[T] | None = None,
508
+ wrapped: Recipe[P, T] | None = None,
509
509
  /,
510
510
  *,
511
511
  on: TypeInfo[T] = (),
512
512
  mode: Mode | ModeStr = Mode.get_default(),
513
513
  ) -> Any:
514
- def decorator(wp: type[T]) -> type[T]:
514
+ def decorator(wp: Recipe[P, T]) -> Recipe[P, T]:
515
515
  lazy_instance = lazy(wp)
516
516
  self.injectable(
517
517
  lambda: ~lazy_instance,
@@ -560,7 +560,7 @@ class Module(Broker, EventListener):
560
560
  wrapped: Callable[P, T] | None = None,
561
561
  /,
562
562
  *,
563
- threadsafe: bool = False,
563
+ threadsafe: bool | None = None,
564
564
  ) -> Any:
565
565
  def decorator(wp: Callable[P, T]) -> Callable[P, T]:
566
566
  if isclass(wp):
@@ -576,7 +576,7 @@ class Module(Broker, EventListener):
576
576
  self,
577
577
  wrapped: Callable[P, T],
578
578
  /,
579
- threadsafe: bool = ...,
579
+ threadsafe: bool | None = ...,
580
580
  ) -> SyncInjectedFunction[P, T]: ...
581
581
 
582
582
  @overload
@@ -584,14 +584,14 @@ class Module(Broker, EventListener):
584
584
  self,
585
585
  wrapped: Callable[P, Awaitable[T]],
586
586
  /,
587
- threadsafe: bool = ...,
587
+ threadsafe: bool | None = ...,
588
588
  ) -> AsyncInjectedFunction[P, T]: ...
589
589
 
590
590
  def make_injected_function[**P, T](
591
591
  self,
592
592
  wrapped: Callable[P, T],
593
593
  /,
594
- threadsafe: bool = False,
594
+ threadsafe: bool | None = None,
595
595
  ) -> InjectedFunction[P, T]:
596
596
  metadata = InjectMetadata(wrapped, threadsafe)
597
597
 
@@ -609,7 +609,7 @@ class Module(Broker, EventListener):
609
609
  self,
610
610
  wrapped: type[T],
611
611
  /,
612
- threadsafe: bool = False,
612
+ threadsafe: bool | None = None,
613
613
  ) -> Callable[..., Awaitable[T]]:
614
614
  factory: InjectedFunction[..., T] = self.make_injected_function(
615
615
  wrapped,
@@ -621,13 +621,18 @@ class Module(Broker, EventListener):
621
621
  self,
622
622
  cls: InputType[T],
623
623
  *,
624
- threadsafe: bool = False,
624
+ threadsafe: bool | None = None,
625
625
  ) -> T:
626
626
  with get_lock(threadsafe):
627
627
  injectable = self[cls]
628
628
  return await injectable.aget_instance()
629
629
 
630
- def find_instance[T](self, cls: InputType[T], *, threadsafe: bool = False) -> T:
630
+ def find_instance[T](
631
+ self,
632
+ cls: InputType[T],
633
+ *,
634
+ threadsafe: bool | None = None,
635
+ ) -> T:
631
636
  with get_lock(threadsafe):
632
637
  injectable = self[cls]
633
638
  return injectable.get_instance()
@@ -638,7 +643,7 @@ class Module(Broker, EventListener):
638
643
  cls: InputType[T],
639
644
  default: Default,
640
645
  *,
641
- threadsafe: bool = ...,
646
+ threadsafe: bool | None = ...,
642
647
  ) -> T | Default: ...
643
648
 
644
649
  @overload
@@ -647,7 +652,7 @@ class Module(Broker, EventListener):
647
652
  cls: InputType[T],
648
653
  default: T = ...,
649
654
  *,
650
- threadsafe: bool = ...,
655
+ threadsafe: bool | None = ...,
651
656
  ) -> T: ...
652
657
 
653
658
  async def aget_instance[T, Default](
@@ -655,7 +660,7 @@ class Module(Broker, EventListener):
655
660
  cls: InputType[T],
656
661
  default: Default = NotImplemented,
657
662
  *,
658
- threadsafe: bool = False,
663
+ threadsafe: bool | None = None,
659
664
  ) -> T | Default:
660
665
  try:
661
666
  return await self.afind_instance(cls, threadsafe=threadsafe)
@@ -668,7 +673,7 @@ class Module(Broker, EventListener):
668
673
  cls: InputType[T],
669
674
  default: Default,
670
675
  *,
671
- threadsafe: bool = ...,
676
+ threadsafe: bool | None = ...,
672
677
  ) -> T | Default: ...
673
678
 
674
679
  @overload
@@ -677,7 +682,7 @@ class Module(Broker, EventListener):
677
682
  cls: InputType[T],
678
683
  default: T = ...,
679
684
  *,
680
- threadsafe: bool = ...,
685
+ threadsafe: bool | None = ...,
681
686
  ) -> T: ...
682
687
 
683
688
  def get_instance[T, Default](
@@ -685,7 +690,7 @@ class Module(Broker, EventListener):
685
690
  cls: InputType[T],
686
691
  default: Default = NotImplemented,
687
692
  *,
688
- threadsafe: bool = False,
693
+ threadsafe: bool | None = None,
689
694
  ) -> T | Default:
690
695
  try:
691
696
  return self.find_instance(cls, threadsafe=threadsafe)
@@ -698,7 +703,7 @@ class Module(Broker, EventListener):
698
703
  cls: InputType[T],
699
704
  default: Default,
700
705
  *,
701
- threadsafe: bool = ...,
706
+ threadsafe: bool | None = ...,
702
707
  ) -> Awaitable[T | Default]: ...
703
708
 
704
709
  @overload
@@ -707,7 +712,7 @@ class Module(Broker, EventListener):
707
712
  cls: InputType[T],
708
713
  default: T = ...,
709
714
  *,
710
- threadsafe: bool = ...,
715
+ threadsafe: bool | None = ...,
711
716
  ) -> Awaitable[T]: ...
712
717
 
713
718
  def aget_lazy_instance[T, Default](
@@ -715,7 +720,7 @@ class Module(Broker, EventListener):
715
720
  cls: InputType[T],
716
721
  default: Default = NotImplemented,
717
722
  *,
718
- threadsafe: bool = False,
723
+ threadsafe: bool | None = None,
719
724
  ) -> Awaitable[T | Default]:
720
725
  function = self.make_injected_function(
721
726
  lambda instance=default: instance,
@@ -730,7 +735,7 @@ class Module(Broker, EventListener):
730
735
  cls: InputType[T],
731
736
  default: Default,
732
737
  *,
733
- threadsafe: bool = ...,
738
+ threadsafe: bool | None = ...,
734
739
  ) -> Invertible[T | Default]: ...
735
740
 
736
741
  @overload
@@ -739,7 +744,7 @@ class Module(Broker, EventListener):
739
744
  cls: InputType[T],
740
745
  default: T = ...,
741
746
  *,
742
- threadsafe: bool = ...,
747
+ threadsafe: bool | None = ...,
743
748
  ) -> Invertible[T]: ...
744
749
 
745
750
  def get_lazy_instance[T, Default](
@@ -747,7 +752,7 @@ class Module(Broker, EventListener):
747
752
  cls: InputType[T],
748
753
  default: Default = NotImplemented,
749
754
  *,
750
- threadsafe: bool = False,
755
+ threadsafe: bool | None = None,
751
756
  ) -> Invertible[T | Default]:
752
757
  function = self.make_injected_function(
753
758
  lambda instance=default: instance,
@@ -1013,7 +1018,12 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
1013
1018
  __tasks: deque[Callable[..., Any]]
1014
1019
  __wrapped: Callable[P, T]
1015
1020
 
1016
- def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
1021
+ def __init__(
1022
+ self,
1023
+ wrapped: Callable[P, T],
1024
+ /,
1025
+ threadsafe: bool | None = None,
1026
+ ) -> None:
1017
1027
  self.__dependencies = Dependencies.empty()
1018
1028
  self.__lock = get_lock(threadsafe)
1019
1029
  self.__owner = None
@@ -1087,7 +1097,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
1087
1097
  return decorator(wrapped) if wrapped else decorator
1088
1098
 
1089
1099
  @singledispatchmethod
1090
- def on_event(self, event: Event, /) -> ContextManager[None] | None: # type: ignore[override]
1100
+ def on_event(self, event: Event, /) -> ContextManager[None] | None:
1091
1101
  return None
1092
1102
 
1093
1103
  @on_event.register
@@ -130,7 +130,7 @@ async def adefine_scope(
130
130
  name: str,
131
131
  /,
132
132
  kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
133
- threadsafe: bool = False,
133
+ threadsafe: bool | None = None,
134
134
  ) -> AsyncIterator[ScopeFacade]:
135
135
  async with AsyncScope() as scope:
136
136
  with _bind_scope(name, scope, kind, threadsafe) as facade:
@@ -142,7 +142,7 @@ def define_scope(
142
142
  name: str,
143
143
  /,
144
144
  kind: ScopeKind | ScopeKindStr = ScopeKind.get_default(),
145
- threadsafe: bool = False,
145
+ threadsafe: bool | None = None,
146
146
  ) -> Iterator[ScopeFacade]:
147
147
  with SyncScope() as scope:
148
148
  with _bind_scope(name, scope, kind, threadsafe) as facade:
@@ -194,7 +194,7 @@ def _bind_scope(
194
194
  name: str,
195
195
  scope: Scope,
196
196
  kind: ScopeKind | ScopeKindStr,
197
- threadsafe: bool,
197
+ threadsafe: bool | None,
198
198
  ) -> Iterator[ScopeFacade]:
199
199
  lock = get_lock(threadsafe)
200
200
 
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
7
7
  from functools import wraps
8
8
  from types import MethodType
9
9
  from types import ModuleType as PythonModule
10
- from typing import Any, Self, final, overload
10
+ from typing import Any, Concatenate, Self, final, overload
11
11
 
12
12
  from injection import Module
13
13
  from injection.loaders import ProfileLoader, PythonModuleLoader
@@ -16,9 +16,9 @@ __all__ = ("AsyncEntrypoint", "Entrypoint", "autocall", "entrypointmaker")
16
16
 
17
17
  type AsyncEntrypoint[**P, T] = Entrypoint[P, Coroutine[Any, Any, T]]
18
18
  type EntrypointDecorator[**P, T1, T2] = Callable[[Callable[P, T1]], Callable[P, T2]]
19
- type EntrypointSetupMethod[*Ts, **P, T1, T2] = Callable[
20
- [Entrypoint[P, T1], *Ts],
21
- Entrypoint[P, T2],
19
+ type EntrypointSetupMethod[**P, **EPP, T1, T2] = Callable[
20
+ Concatenate[Entrypoint[EPP, T1], P],
21
+ Entrypoint[EPP, T2],
22
22
  ]
23
23
 
24
24
 
@@ -31,35 +31,35 @@ def autocall[**P, T](wrapped: Callable[P, T] | None = None, /) -> Any:
31
31
 
32
32
 
33
33
  @overload
34
- def entrypointmaker[*Ts, **P, T1, T2](
35
- wrapped: EntrypointSetupMethod[*Ts, P, T1, T2],
34
+ def entrypointmaker[**SMP, **EPP, T1, T2](
35
+ wrapped: EntrypointSetupMethod[SMP, EPP, T1, T2],
36
36
  /,
37
37
  *,
38
38
  profile_loader: ProfileLoader = ...,
39
- ) -> EntrypointDecorator[P, T1, T2]: ...
39
+ ) -> EntrypointDecorator[EPP, T1, T2]: ...
40
40
 
41
41
 
42
42
  @overload
43
- def entrypointmaker[*Ts, **P, T1, T2](
43
+ def entrypointmaker[**SMP, **EPP, T1, T2](
44
44
  wrapped: None = ...,
45
45
  /,
46
46
  *,
47
47
  profile_loader: ProfileLoader = ...,
48
48
  ) -> Callable[
49
- [EntrypointSetupMethod[*Ts, P, T1, T2]],
50
- EntrypointDecorator[P, T1, T2],
49
+ [EntrypointSetupMethod[SMP, EPP, T1, T2]],
50
+ EntrypointDecorator[EPP, T1, T2],
51
51
  ]: ...
52
52
 
53
53
 
54
- def entrypointmaker[*Ts, **P, T1, T2](
55
- wrapped: EntrypointSetupMethod[*Ts, P, T1, T2] | None = None,
54
+ def entrypointmaker[**SMP, **EPP, T1, T2](
55
+ wrapped: EntrypointSetupMethod[SMP, EPP, T1, T2] | None = None,
56
56
  /,
57
57
  *,
58
58
  profile_loader: ProfileLoader | None = None,
59
59
  ) -> Any:
60
60
  def decorator(
61
- wp: EntrypointSetupMethod[*Ts, P, T1, T2],
62
- ) -> EntrypointDecorator[P, T1, T2]:
61
+ wp: EntrypointSetupMethod[SMP, EPP, T1, T2],
62
+ ) -> EntrypointDecorator[EPP, T1, T2]:
63
63
  return Entrypoint._make_decorator(wp, profile_loader)
64
64
 
65
65
  return decorator(wrapped) if wrapped else decorator
@@ -143,9 +143,9 @@ class Entrypoint[**P, T]:
143
143
  return type(self)(function, self.profile_loader)
144
144
 
145
145
  @classmethod
146
- def _make_decorator[*Ts, _T](
146
+ def _make_decorator[**_P, _T](
147
147
  cls,
148
- setup_method: EntrypointSetupMethod[*Ts, P, T, _T],
148
+ setup_method: EntrypointSetupMethod[_P, P, T, _T],
149
149
  /,
150
150
  profile_loader: ProfileLoader | None = None,
151
151
  ) -> EntrypointDecorator[P, T, _T]:
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from types import GenericAlias
3
- from typing import Annotated, Any, TypeAliasType
3
+ from typing import Annotated, Any, TypeAlias, TypeAliasType
4
4
 
5
5
  from fastapi import Depends
6
6
 
@@ -12,7 +12,7 @@ __all__ = ("Inject", "InjectThreadSafe")
12
12
  @dataclass(eq=False, frozen=True, slots=True)
13
13
  class FastAPIInject:
14
14
  module: Module = field(default_factory=mod)
15
- threadsafe: bool = field(default=False, kw_only=True)
15
+ threadsafe: bool | None = field(default=None, kw_only=True)
16
16
 
17
17
  def __call__[T](
18
18
  self,
@@ -25,16 +25,16 @@ class FastAPIInject:
25
25
  ) -> Any:
26
26
  module = module or self.module
27
27
  threadsafe = self.threadsafe if threadsafe is None else threadsafe
28
- ainstance = module.aget_lazy_instance(cls, default, threadsafe=threadsafe)
28
+ lazy_instance = module.aget_lazy_instance(cls, default, threadsafe=threadsafe)
29
29
 
30
30
  async def dependency() -> T:
31
- return await ainstance
31
+ return await lazy_instance
32
32
 
33
33
  class_name = getattr(cls, "__name__", str(cls))
34
34
  dependency.__name__ = f"inject({class_name})"
35
35
  return Depends(dependency, use_cache=False)
36
36
 
37
- def __getitem__(self, params: Any, /) -> Any:
37
+ def __getitem__[T, *Ts](self, params: T | tuple[T, *Ts], /) -> TypeAlias:
38
38
  iter_params = iter(params if isinstance(params, tuple) else (params,))
39
39
  cls = next(iter_params)
40
40
  return Annotated[cls, self(cls), *iter_params]
@@ -164,8 +164,11 @@ class ProfileLoader:
164
164
 
165
165
  def load(self, name: str, /) -> LoadedProfile:
166
166
  self.init()
167
- target_module = self.__init_subsets_for(mod(name))
168
- self.module.use(target_module, priority=Priority.HIGH)
167
+
168
+ if not self.__is_default_module(name):
169
+ target_module = self.__init_subsets_for(mod(name))
170
+ self.module.use(target_module, priority=Priority.HIGH)
171
+
169
172
  return _UserLoadedProfile(self, name)
170
173
 
171
174
  def _unload(self, name: str, /) -> None:
@@ -182,6 +185,9 @@ class ProfileLoader:
182
185
 
183
186
  return module
184
187
 
188
+ def __is_default_module(self, module_name: str) -> bool:
189
+ return module_name == self.module.name
190
+
185
191
  def __is_initialized(self, module: Module) -> bool:
186
192
  return module.name in self.__initialized_modules
187
193
 
@@ -15,6 +15,7 @@ dev = [
15
15
  ]
16
16
  doc = [
17
17
  "fastapi",
18
+ "pydantic-settings",
18
19
  "typer",
19
20
  "uvloop",
20
21
  ]
@@ -29,7 +30,7 @@ test = [
29
30
 
30
31
  [project]
31
32
  name = "python-injection"
32
- version = "0.18.13"
33
+ version = "0.19.0"
33
34
  description = "Fast and easy dependency injection framework."
34
35
  license = "MIT"
35
36
  license-files = ["LICENSE"]
@@ -46,6 +47,8 @@ classifiers = [
46
47
  "Programming Language :: Python",
47
48
  "Programming Language :: Python :: 3",
48
49
  "Programming Language :: Python :: 3 :: Only",
50
+ "Programming Language :: Python :: 3.12",
51
+ "Programming Language :: Python :: 3.13",
49
52
  "Operating System :: OS Independent",
50
53
  "Intended Audience :: Developers",
51
54
  "Natural Language :: English",