python-injection 0.14.0__tar.gz → 0.14.1__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 (26) hide show
  1. {python_injection-0.14.0 → python_injection-0.14.1}/PKG-INFO +4 -1
  2. {python_injection-0.14.0 → python_injection-0.14.1}/README.md +3 -0
  3. {python_injection-0.14.0 → python_injection-0.14.1}/injection/__init__.pyi +11 -4
  4. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/hook.py +0 -1
  5. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/module.py +32 -25
  6. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/scope.py +2 -5
  7. {python_injection-0.14.0 → python_injection-0.14.1}/injection/testing/__init__.py +2 -2
  8. {python_injection-0.14.0 → python_injection-0.14.1}/injection/testing/__init__.pyi +1 -1
  9. {python_injection-0.14.0 → python_injection-0.14.1}/injection/utils.py +2 -2
  10. {python_injection-0.14.0 → python_injection-0.14.1}/pyproject.toml +1 -1
  11. {python_injection-0.14.0 → python_injection-0.14.1}/.gitignore +0 -0
  12. {python_injection-0.14.0 → python_injection-0.14.1}/injection/__init__.py +0 -0
  13. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/__init__.py +0 -0
  14. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/__init__.py +0 -0
  15. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/asynchronous.py +0 -0
  16. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/event.py +0 -0
  17. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/invertible.py +0 -0
  18. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/key.py +0 -0
  19. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/lazy.py +0 -0
  20. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/common/type.py +0 -0
  21. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/descriptors.py +0 -0
  22. {python_injection-0.14.0 → python_injection-0.14.1}/injection/_core/injectables.py +0 -0
  23. {python_injection-0.14.0 → python_injection-0.14.1}/injection/exceptions.py +0 -0
  24. {python_injection-0.14.0 → python_injection-0.14.1}/injection/integrations/__init__.py +0 -0
  25. {python_injection-0.14.0 → python_injection-0.14.1}/injection/integrations/fastapi.py +0 -0
  26. {python_injection-0.14.0 → python_injection-0.14.1}/injection/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.14.0
3
+ Version: 0.14.1
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -78,6 +78,9 @@ if __name__ == "__main__":
78
78
 
79
79
  ## Resources
80
80
 
81
+ > ⚠️ The package isn't threadsafe, for better performance in single-threaded applications and those using `asyncio`.
82
+ > So remember to use `threading.Lock` if you're writing a multithreaded program.
83
+
81
84
  * [**Basic usage**](https://github.com/100nm/python-injection/tree/prod/documentation/basic-usage.md)
82
85
  * [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
83
86
  * [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)
@@ -55,6 +55,9 @@ if __name__ == "__main__":
55
55
 
56
56
  ## Resources
57
57
 
58
+ > ⚠️ The package isn't threadsafe, for better performance in single-threaded applications and those using `asyncio`.
59
+ > So remember to use `threading.Lock` if you're writing a multithreaded program.
60
+
58
61
  * [**Basic usage**](https://github.com/100nm/python-injection/tree/prod/documentation/basic-usage.md)
59
62
  * [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
60
63
  * [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)
@@ -10,6 +10,7 @@ from ._core.common.type import InputType as _InputType
10
10
  from ._core.common.type import TypeInfo as _TypeInfo
11
11
  from ._core.module import InjectableFactory as _InjectableFactory
12
12
  from ._core.module import ModeStr, PriorityStr
13
+ from ._core.module import Recipe as _Recipe
13
14
 
14
15
  __MODULE: Final[Module] = ...
15
16
 
@@ -91,7 +92,7 @@ class Module:
91
92
 
92
93
  def injectable[**P, T](
93
94
  self,
94
- wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
95
+ wrapped: _Recipe[P, T] = ...,
95
96
  /,
96
97
  *,
97
98
  cls: _InjectableFactory[T] = ...,
@@ -107,7 +108,7 @@ class Module:
107
108
 
108
109
  def singleton[**P, T](
109
110
  self,
110
- wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
111
+ wrapped: _Recipe[P, T] = ...,
111
112
  /,
112
113
  *,
113
114
  inject: bool = ...,
@@ -176,6 +177,12 @@ class Module:
176
177
  /,
177
178
  threadsafe: bool = ...,
178
179
  ) -> Callable[P, T]: ...
180
+ def make_async_factory[T](
181
+ self,
182
+ wrapped: type[T],
183
+ /,
184
+ threadsafe: bool = ...,
185
+ ) -> Callable[..., Awaitable[T]]: ...
179
186
  async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
180
187
  def find_instance[T](self, cls: _InputType[T]) -> T:
181
188
  """
@@ -281,7 +288,7 @@ class Module:
281
288
  module: Module,
282
289
  *,
283
290
  priority: Priority | PriorityStr = ...,
284
- ) -> Iterator[None]:
291
+ ) -> Iterator[Self]:
285
292
  """
286
293
  Context manager or decorator for temporary use of a module.
287
294
  """
@@ -305,7 +312,7 @@ class Module:
305
312
  """
306
313
 
307
314
  @contextmanager
308
- def load_profile(self, *names: str) -> Iterator[None]: ...
315
+ def load_profile(self, *names: str) -> Iterator[Self]: ...
309
316
  async def all_ready(self) -> None: ...
310
317
  def add_logger(self, logger: Logger) -> Self: ...
311
318
  @classmethod
@@ -64,7 +64,6 @@ class Hook[**P, T]:
64
64
  hook.throw(exc)
65
65
  else:
66
66
  hook.send(value)
67
- return value
68
67
 
69
68
  except StopIteration as stop:
70
69
  return stop.value
@@ -3,10 +3,12 @@ from __future__ import annotations
3
3
  from abc import ABC, abstractmethod
4
4
  from collections import OrderedDict, deque
5
5
  from collections.abc import (
6
+ AsyncGenerator,
6
7
  AsyncIterator,
7
8
  Awaitable,
8
9
  Callable,
9
10
  Collection,
11
+ Generator,
10
12
  Iterable,
11
13
  Iterator,
12
14
  Mapping,
@@ -360,6 +362,14 @@ class Priority(StrEnum):
360
362
 
361
363
  type PriorityStr = Literal["low", "high"]
362
364
 
365
+ type ContextManagerLikeRecipe[**P, T] = (
366
+ Callable[P, ContextManager[T]] | Callable[P, AsyncContextManager[T]]
367
+ )
368
+ type GeneratorRecipe[**P, T] = (
369
+ Callable[P, Generator[T, Any, Any]] | Callable[P, AsyncGenerator[T, Any]]
370
+ )
371
+ type Recipe[**P, T] = Callable[P, T] | Callable[P, Awaitable[T]]
372
+
363
373
 
364
374
  @dataclass(eq=False, frozen=True, slots=True)
365
375
  class Module(Broker, EventListener):
@@ -411,7 +421,7 @@ class Module(Broker, EventListener):
411
421
 
412
422
  def injectable[**P, T](
413
423
  self,
414
- wrapped: Callable[P, T] | Callable[P, Awaitable[T]] | None = None,
424
+ wrapped: Recipe[P, T] | None = None,
415
425
  /,
416
426
  *,
417
427
  cls: InjectableFactory[T] = SimpleInjectable,
@@ -420,9 +430,7 @@ class Module(Broker, EventListener):
420
430
  on: TypeInfo[T] = (),
421
431
  mode: Mode | ModeStr = Mode.get_default(),
422
432
  ) -> Any:
423
- def decorator(
424
- wp: Callable[P, T] | Callable[P, Awaitable[T]],
425
- ) -> Callable[P, T] | Callable[P, Awaitable[T]]:
433
+ def decorator(wp: Recipe[P, T]) -> Recipe[P, T]:
426
434
  factory = extract_caller(self.make_injected_function(wp) if inject else wp)
427
435
  hints = on if ignore_type_hint else (wp, on)
428
436
  updater = Updater(
@@ -447,23 +455,10 @@ class Module(Broker, EventListener):
447
455
  mode: Mode | ModeStr = Mode.get_default(),
448
456
  ) -> Any:
449
457
  def decorator(
450
- wrapped: Callable[P, T]
451
- | Callable[P, Awaitable[T]]
452
- | Callable[P, Iterator[T]]
453
- | Callable[P, AsyncIterator[T]],
454
- ) -> (
455
- Callable[P, T]
456
- | Callable[P, Awaitable[T]]
457
- | Callable[P, Iterator[T]]
458
- | Callable[P, AsyncIterator[T]]
459
- ):
458
+ wrapped: Recipe[P, T] | GeneratorRecipe[P, T],
459
+ ) -> Recipe[P, T] | GeneratorRecipe[P, T]:
460
460
  injectable_class: Callable[[Caller[P, Any], str], Injectable[T]]
461
- wrapper: (
462
- Callable[P, T]
463
- | Callable[P, Awaitable[T]]
464
- | Callable[P, ContextManager[T]]
465
- | Callable[P, AsyncContextManager[T]]
466
- )
461
+ wrapper: Recipe[P, T] | ContextManagerLikeRecipe[P, T]
467
462
 
468
463
  if isasyncgenfunction(wrapped):
469
464
  hint = get_yield_hint(wrapped)
@@ -588,6 +583,18 @@ class Module(Broker, EventListener):
588
583
 
589
584
  return SyncInjectedFunction(metadata)
590
585
 
586
+ def make_async_factory[T](
587
+ self,
588
+ wrapped: type[T],
589
+ /,
590
+ threadsafe: bool = False,
591
+ ) -> Callable[..., Awaitable[T]]:
592
+ factory: InjectedFunction[..., T] = self.make_injected_function(
593
+ wrapped,
594
+ threadsafe,
595
+ )
596
+ return factory.__inject_metadata__.acall
597
+
591
598
  async def afind_instance[T](self, cls: InputType[T]) -> T:
592
599
  injectable = self[cls]
593
600
  return await injectable.aget_instance()
@@ -739,11 +746,11 @@ class Module(Broker, EventListener):
739
746
  module: Module,
740
747
  *,
741
748
  priority: Priority | PriorityStr = Priority.get_default(),
742
- ) -> Iterator[None]:
749
+ ) -> Iterator[Self]:
743
750
  self.use(module, priority=priority)
744
751
 
745
752
  try:
746
- yield
753
+ yield self
747
754
  finally:
748
755
  self.stop_using(module)
749
756
 
@@ -762,7 +769,7 @@ class Module(Broker, EventListener):
762
769
 
763
770
  return self
764
771
 
765
- def load_profile(self, *names: str) -> ContextManager[None]:
772
+ def load_profile(self, *names: str) -> ContextManager[Self]:
766
773
  modules = tuple(self.from_name(name) for name in names)
767
774
 
768
775
  for module in modules:
@@ -773,8 +780,8 @@ class Module(Broker, EventListener):
773
780
  del module, modules
774
781
 
775
782
  @contextmanager
776
- def cleaner() -> Iterator[None]:
777
- yield
783
+ def cleaner() -> Iterator[Self]:
784
+ yield self
778
785
  self.unlock().init_modules()
779
786
 
780
787
  return cleaner()
@@ -121,11 +121,8 @@ def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
121
121
  f"Scope `{name}` is already defined in the current context."
122
122
  )
123
123
 
124
- strategy = (
125
- state.bind_shared_scope(scope) if shared else state.bind_contextual_scope(scope)
126
- )
127
-
128
- with strategy:
124
+ strategy = state.bind_shared_scope if shared else state.bind_contextual_scope
125
+ with strategy(scope):
129
126
  yield
130
127
 
131
128
 
@@ -1,6 +1,6 @@
1
1
  from typing import ContextManager, Final
2
2
 
3
- from injection import mod
3
+ from injection import Module, mod
4
4
  from injection.utils import load_profile
5
5
 
6
6
  __all__ = (
@@ -23,5 +23,5 @@ test_scoped = mod(_TEST_PROFILE_NAME).scoped
23
23
  test_singleton = mod(_TEST_PROFILE_NAME).singleton
24
24
 
25
25
 
26
- def load_test_profile(*names: str) -> ContextManager[None]:
26
+ def load_test_profile(*names: str) -> ContextManager[Module]:
27
27
  return load_profile(_TEST_PROFILE_NAME, *names)
@@ -11,7 +11,7 @@ test_injectable = __MODULE.injectable
11
11
  test_scoped = __MODULE.scoped
12
12
  test_singleton = __MODULE.singleton
13
13
 
14
- def load_test_profile(*names: str) -> ContextManager[None]:
14
+ def load_test_profile(*names: str) -> ContextManager[Module]:
15
15
  """
16
16
  Context manager or decorator for temporary use test module.
17
17
  """
@@ -5,13 +5,13 @@ from pkgutil import walk_packages
5
5
  from types import ModuleType as PythonModule
6
6
  from typing import ContextManager
7
7
 
8
+ from injection import Module, mod
8
9
  from injection import __name__ as injection_package_name
9
- from injection import mod
10
10
 
11
11
  __all__ = ("load_modules_with_keywords", "load_packages", "load_profile")
12
12
 
13
13
 
14
- def load_profile(*names: str) -> ContextManager[None]:
14
+ def load_profile(*names: str) -> ContextManager[Module]:
15
15
  """
16
16
  Injection module initialization function based on profile name.
17
17
  A profile name is equivalent to an injection module name.
@@ -24,7 +24,7 @@ test = [
24
24
 
25
25
  [project]
26
26
  name = "python-injection"
27
- version = "0.14.0"
27
+ version = "0.14.1"
28
28
  description = "Fast and easy dependency injection framework."
29
29
  license = { text = "MIT" }
30
30
  readme = "README.md"