python-injection 0.14.3__tar.gz → 0.14.4__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.3 → python_injection-0.14.4}/PKG-INFO +1 -1
  2. {python_injection-0.14.3 → python_injection-0.14.4}/injection/__init__.py +4 -0
  3. {python_injection-0.14.3 → python_injection-0.14.4}/injection/__init__.pyi +14 -0
  4. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/type.py +7 -1
  5. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/injectables.py +10 -4
  6. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/module.py +40 -8
  7. python_injection-0.14.4/injection/_core/slots.py +24 -0
  8. {python_injection-0.14.3 → python_injection-0.14.4}/injection/exceptions.py +4 -4
  9. {python_injection-0.14.3 → python_injection-0.14.4}/injection/testing/__init__.py +2 -0
  10. {python_injection-0.14.3 → python_injection-0.14.4}/injection/testing/__init__.pyi +1 -0
  11. {python_injection-0.14.3 → python_injection-0.14.4}/pyproject.toml +1 -1
  12. {python_injection-0.14.3 → python_injection-0.14.4}/.gitignore +0 -0
  13. {python_injection-0.14.3 → python_injection-0.14.4}/README.md +0 -0
  14. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/__init__.py +0 -0
  15. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/__init__.py +0 -0
  16. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/asynchronous.py +0 -0
  17. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/event.py +0 -0
  18. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/invertible.py +0 -0
  19. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/key.py +0 -0
  20. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/common/lazy.py +0 -0
  21. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/descriptors.py +0 -0
  22. {python_injection-0.14.3 → python_injection-0.14.4}/injection/_core/scope.py +0 -0
  23. {python_injection-0.14.3 → python_injection-0.14.4}/injection/integrations/__init__.py +0 -0
  24. {python_injection-0.14.3 → python_injection-0.14.4}/injection/integrations/fastapi.py +0 -0
  25. {python_injection-0.14.3 → python_injection-0.14.4}/injection/py.typed +0 -0
  26. {python_injection-0.14.3 → python_injection-0.14.4}/injection/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.14.3
3
+ Version: 0.14.4
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -2,6 +2,7 @@ from ._core.descriptors import LazyInstance
2
2
  from ._core.injectables import Injectable
3
3
  from ._core.module import Mode, Module, Priority, mod
4
4
  from ._core.scope import adefine_scope, define_scope
5
+ from ._core.slots import Slot
5
6
 
6
7
  __all__ = (
7
8
  "Injectable",
@@ -9,6 +10,7 @@ __all__ = (
9
10
  "Mode",
10
11
  "Module",
11
12
  "Priority",
13
+ "Slot",
12
14
  "adefine_scope",
13
15
  "afind_instance",
14
16
  "aget_instance",
@@ -21,6 +23,7 @@ __all__ = (
21
23
  "inject",
22
24
  "injectable",
23
25
  "mod",
26
+ "reserve_scoped_slot",
24
27
  "scoped",
25
28
  "set_constant",
26
29
  "should_be_injectable",
@@ -36,6 +39,7 @@ get_instance = mod().get_instance
36
39
  get_lazy_instance = mod().get_lazy_instance
37
40
  inject = mod().inject
38
41
  injectable = mod().injectable
42
+ reserve_scoped_slot = mod().reserve_scoped_slot
39
43
  scoped = mod().scoped
40
44
  set_constant = mod().set_constant
41
45
  should_be_injectable = mod().should_be_injectable
@@ -23,6 +23,7 @@ get_instance = __MODULE.get_instance
23
23
  get_lazy_instance = __MODULE.get_lazy_instance
24
24
  inject = __MODULE.inject
25
25
  injectable = __MODULE.injectable
26
+ reserve_scoped_slot = __MODULE.reserve_scoped_slot
26
27
  scoped = __MODULE.scoped
27
28
  set_constant = __MODULE.set_constant
28
29
  should_be_injectable = __MODULE.should_be_injectable
@@ -46,6 +47,11 @@ class Injectable[T](Protocol):
46
47
  @abstractmethod
47
48
  def get_instance(self) -> T: ...
48
49
 
50
+ @runtime_checkable
51
+ class Slot[T](Protocol):
52
+ @abstractmethod
53
+ def set(self, instance: T, /) -> Self: ...
54
+
49
55
  class LazyInstance[T]:
50
56
  def __init__(
51
57
  self,
@@ -171,6 +177,14 @@ class Module:
171
177
  that no dependencies are resolved, so the module doesn't need to be locked.
172
178
  """
173
179
 
180
+ def reserve_scoped_slot[T](
181
+ self,
182
+ on: _TypeInfo[T],
183
+ /,
184
+ scope_name: str,
185
+ *,
186
+ mode: Mode | ModeStr = ...,
187
+ ) -> Slot[T]: ...
174
188
  def make_injected_function[**P, T](
175
189
  self,
176
190
  wrapped: Callable[P, T],
@@ -2,6 +2,7 @@ from collections.abc import (
2
2
  AsyncGenerator,
3
3
  AsyncIterable,
4
4
  AsyncIterator,
5
+ Awaitable,
5
6
  Callable,
6
7
  Generator,
7
8
  Iterable,
@@ -21,7 +22,12 @@ from typing import (
21
22
 
22
23
  type TypeDef[T] = type[T] | TypeAliasType | GenericAlias
23
24
  type InputType[T] = TypeDef[T] | UnionType
24
- type TypeInfo[T] = InputType[T] | Callable[..., T] | Iterable[TypeInfo[T]]
25
+ type TypeInfo[T] = (
26
+ InputType[T]
27
+ | Callable[..., T]
28
+ | Callable[..., Awaitable[T]]
29
+ | Iterable[TypeInfo[T]]
30
+ )
25
31
 
26
32
 
27
33
  def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
@@ -107,25 +107,31 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
107
107
  raise NotImplementedError
108
108
 
109
109
  async def aget_instance(self) -> T:
110
- scope = get_scope(self.scope_name)
110
+ scope = self.get_scope()
111
111
 
112
112
  with suppress(KeyError):
113
113
  return scope.cache[self]
114
114
 
115
115
  instance = await self.abuild(scope)
116
- scope.cache[self] = instance
116
+ self.set_instance(instance, scope)
117
117
  return instance
118
118
 
119
119
  def get_instance(self) -> T:
120
- scope = get_scope(self.scope_name)
120
+ scope = self.get_scope()
121
121
 
122
122
  with suppress(KeyError):
123
123
  return scope.cache[self]
124
124
 
125
125
  instance = self.build(scope)
126
- scope.cache[self] = instance
126
+ self.set_instance(instance, scope)
127
127
  return instance
128
128
 
129
+ def get_scope(self) -> Scope:
130
+ return get_scope(self.scope_name)
131
+
132
+ def set_instance(self, instance: T, scope: Scope) -> None:
133
+ scope.cache[self] = instance
134
+
129
135
  def unlock(self) -> None:
130
136
  if self.is_locked:
131
137
  raise RuntimeError(f"To unlock, close the `{self.scope_name}` scope.")
@@ -16,7 +16,7 @@ from collections.abc import (
16
16
  from contextlib import asynccontextmanager, contextmanager, nullcontext, suppress
17
17
  from dataclasses import dataclass, field
18
18
  from enum import StrEnum
19
- from functools import partialmethod, singledispatchmethod, update_wrapper
19
+ from functools import partial, partialmethod, singledispatchmethod, update_wrapper
20
20
  from inspect import (
21
21
  Signature,
22
22
  isasyncgenfunction,
@@ -63,12 +63,15 @@ from injection._core.injectables import (
63
63
  AsyncCMScopedInjectable,
64
64
  CMScopedInjectable,
65
65
  Injectable,
66
+ ScopedInjectable,
66
67
  ShouldBeInjectable,
67
68
  SimpleInjectable,
68
69
  SimpleScopedInjectable,
69
70
  SingletonInjectable,
70
71
  )
72
+ from injection._core.slots import ScopedSlot, Slot
71
73
  from injection.exceptions import (
74
+ EmptySlotError,
72
75
  ModuleError,
73
76
  ModuleLockError,
74
77
  ModuleNotUsedError,
@@ -222,6 +225,20 @@ class Updater[T]:
222
225
  def make_record(self) -> Record[T]:
223
226
  return Record(self.injectable, self.mode)
224
227
 
228
+ @classmethod
229
+ def with_basics(
230
+ cls,
231
+ on: TypeInfo[T],
232
+ /,
233
+ injectable: Injectable[T],
234
+ mode: Mode | ModeStr,
235
+ ) -> Self:
236
+ return cls(
237
+ classes=get_return_types(on),
238
+ injectable=injectable,
239
+ mode=Mode(mode),
240
+ )
241
+
225
242
 
226
243
  @dataclass(repr=False, frozen=True, slots=True)
227
244
  class Locator(Broker):
@@ -420,12 +437,9 @@ class Module(Broker, EventListener):
420
437
  ) -> Any:
421
438
  def decorator(wp: Recipe[P, T]) -> Recipe[P, T]:
422
439
  factory = extract_caller(self.make_injected_function(wp) if inject else wp)
440
+ injectable = cls(factory) # type: ignore[arg-type]
423
441
  hints = on if ignore_type_hint else (wp, on)
424
- updater = Updater(
425
- classes=get_return_types(hints),
426
- injectable=cls(factory), # type: ignore[arg-type]
427
- mode=Mode(mode),
428
- )
442
+ updater = Updater.with_basics(hints, injectable, mode)
429
443
  self.update(updater)
430
444
  return wp
431
445
 
@@ -445,7 +459,7 @@ class Module(Broker, EventListener):
445
459
  def decorator(
446
460
  wrapped: Recipe[P, T] | GeneratorRecipe[P, T],
447
461
  ) -> Recipe[P, T] | GeneratorRecipe[P, T]:
448
- injectable_class: Callable[[Caller[P, Any], str], Injectable[T]]
462
+ injectable_class: type[ScopedInjectable[Any, T]]
449
463
  wrapper: Recipe[P, T] | ContextManagerLikeRecipe[P, T]
450
464
 
451
465
  if isasyncgenfunction(wrapped):
@@ -465,7 +479,7 @@ class Module(Broker, EventListener):
465
479
  hints = on if hint is None else (hint, on)
466
480
  self.injectable(
467
481
  wrapper,
468
- cls=lambda factory: injectable_class(factory, scope_name),
482
+ cls=partial(injectable_class, scope_name=scope_name),
469
483
  ignore_type_hint=True,
470
484
  inject=inject,
471
485
  on=hints,
@@ -526,6 +540,24 @@ class Module(Broker, EventListener):
526
540
  )
527
541
  return self
528
542
 
543
+ def reserve_scoped_slot[T](
544
+ self,
545
+ on: TypeInfo[T],
546
+ /,
547
+ scope_name: str,
548
+ *,
549
+ mode: Mode | ModeStr = Mode.get_default(),
550
+ ) -> Slot[T]:
551
+ def when_empty() -> T:
552
+ raise EmptySlotError(
553
+ f"The slot for `{on}` is unset in the current `{scope_name}` scope."
554
+ )
555
+
556
+ injectable = SimpleScopedInjectable(SyncCaller(when_empty), scope_name)
557
+ updater = Updater.with_basics(on, injectable, mode)
558
+ self.update(updater)
559
+ return ScopedSlot(injectable)
560
+
529
561
  def inject[**P, T](
530
562
  self,
531
563
  wrapped: Callable[P, T] | None = None,
@@ -0,0 +1,24 @@
1
+ from abc import abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Any, Protocol, runtime_checkable
4
+
5
+ from injection._core.injectables import ScopedInjectable
6
+
7
+
8
+ @runtime_checkable
9
+ class Slot[T](Protocol):
10
+ __slots__ = ()
11
+
12
+ @abstractmethod
13
+ def set(self, instance: T, /) -> None:
14
+ raise NotImplementedError
15
+
16
+
17
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
18
+ class ScopedSlot[T](Slot[T]):
19
+ injectable: ScopedInjectable[Any, T]
20
+
21
+ def set(self, instance: T, /) -> None:
22
+ injectable = self.injectable
23
+ scope = injectable.get_scope()
24
+ injectable.set_instance(instance, scope)
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  __all__ = (
4
- "HookError",
4
+ "EmptySlotError",
5
5
  "InjectionError",
6
6
  "ModuleError",
7
7
  "ModuleLockError",
@@ -34,6 +34,9 @@ class NoInjectable[T](KeyError, InjectionError):
34
34
  class SkipInjectable(InjectionError): ...
35
35
 
36
36
 
37
+ class EmptySlotError(SkipInjectable, InjectionError): ...
38
+
39
+
37
40
  class ModuleError(InjectionError): ...
38
41
 
39
42
 
@@ -50,6 +53,3 @@ class ScopeUndefinedError(LookupError, SkipInjectable, ScopeError): ...
50
53
 
51
54
 
52
55
  class ScopeAlreadyDefinedError(ScopeError): ...
53
-
54
-
55
- class HookError(InjectionError): ...
@@ -5,6 +5,7 @@ from injection.utils import load_profile
5
5
 
6
6
  __all__ = (
7
7
  "load_test_profile",
8
+ "reserve_scoped_test_slot",
8
9
  "set_test_constant",
9
10
  "should_be_test_injectable",
10
11
  "test_constant",
@@ -15,6 +16,7 @@ __all__ = (
15
16
 
16
17
  _TEST_PROFILE_NAME: Final[str] = "__testing__"
17
18
 
19
+ reserve_scoped_test_slot = mod(_TEST_PROFILE_NAME).reserve_scoped_slot
18
20
  set_test_constant = mod(_TEST_PROFILE_NAME).set_constant
19
21
  should_be_test_injectable = mod(_TEST_PROFILE_NAME).should_be_injectable
20
22
  test_constant = mod(_TEST_PROFILE_NAME).constant
@@ -4,6 +4,7 @@ from injection import Module
4
4
 
5
5
  __MODULE: Final[Module] = ...
6
6
 
7
+ reserve_scoped_test_slot = __MODULE.reserve_scoped_slot
7
8
  set_test_constant = __MODULE.set_constant
8
9
  should_be_test_injectable = __MODULE.should_be_injectable
9
10
  test_constant = __MODULE.constant
@@ -24,7 +24,7 @@ test = [
24
24
 
25
25
  [project]
26
26
  name = "python-injection"
27
- version = "0.14.3"
27
+ version = "0.14.4"
28
28
  description = "Fast and easy dependency injection framework."
29
29
  license = { text = "MIT" }
30
30
  readme = "README.md"