python-injection 0.14.1__tar.gz → 0.14.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 (27) hide show
  1. {python_injection-0.14.1 → python_injection-0.14.3}/PKG-INFO +1 -1
  2. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/module.py +27 -40
  3. {python_injection-0.14.1 → python_injection-0.14.3}/injection/exceptions.py +5 -1
  4. python_injection-0.14.3/injection/py.typed +0 -0
  5. {python_injection-0.14.1 → python_injection-0.14.3}/pyproject.toml +1 -1
  6. python_injection-0.14.1/injection/_core/__init__.py +0 -53
  7. python_injection-0.14.1/injection/_core/hook.py +0 -106
  8. {python_injection-0.14.1 → python_injection-0.14.3}/.gitignore +0 -0
  9. {python_injection-0.14.1 → python_injection-0.14.3}/README.md +0 -0
  10. {python_injection-0.14.1 → python_injection-0.14.3}/injection/__init__.py +0 -0
  11. {python_injection-0.14.1 → python_injection-0.14.3}/injection/__init__.pyi +0 -0
  12. {python_injection-0.14.1/injection/_core/common → python_injection-0.14.3/injection/_core}/__init__.py +0 -0
  13. {python_injection-0.14.1/injection/integrations → python_injection-0.14.3/injection/_core/common}/__init__.py +0 -0
  14. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/common/asynchronous.py +0 -0
  15. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/common/event.py +0 -0
  16. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/common/invertible.py +0 -0
  17. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/common/key.py +0 -0
  18. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/common/lazy.py +0 -0
  19. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/common/type.py +0 -0
  20. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/descriptors.py +0 -0
  21. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/injectables.py +0 -0
  22. {python_injection-0.14.1 → python_injection-0.14.3}/injection/_core/scope.py +0 -0
  23. /python_injection-0.14.1/injection/py.typed → /python_injection-0.14.3/injection/integrations/__init__.py +0 -0
  24. {python_injection-0.14.1 → python_injection-0.14.3}/injection/integrations/fastapi.py +0 -0
  25. {python_injection-0.14.1 → python_injection-0.14.3}/injection/testing/__init__.py +0 -0
  26. {python_injection-0.14.1 → python_injection-0.14.3}/injection/testing/__init__.pyi +0 -0
  27. {python_injection-0.14.1 → python_injection-0.14.3}/injection/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.14.1
3
+ Version: 0.14.3
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -57,8 +57,8 @@ from injection._core.common.type import (
57
57
  TypeInfo,
58
58
  get_return_types,
59
59
  get_yield_hint,
60
+ standardize_types,
60
61
  )
61
- from injection._core.hook import Hook, apply_hooks
62
62
  from injection._core.injectables import (
63
63
  AsyncCMScopedInjectable,
64
64
  CMScopedInjectable,
@@ -73,6 +73,7 @@ from injection.exceptions import (
73
73
  ModuleLockError,
74
74
  ModuleNotUsedError,
75
75
  NoInjectable,
76
+ SkipInjectable,
76
77
  )
77
78
 
78
79
  """
@@ -222,22 +223,6 @@ class Updater[T]:
222
223
  return Record(self.injectable, self.mode)
223
224
 
224
225
 
225
- @dataclass(repr=False, eq=False, frozen=True, slots=True)
226
- class LocatorHooks[T]:
227
- on_conflict: Hook[[Record[T], Record[T], InputType[T]], bool] = field(
228
- default_factory=Hook,
229
- init=False,
230
- )
231
- on_input: Hook[[Iterable[InputType[T]]], Iterable[InputType[T]]] = field(
232
- default_factory=Hook,
233
- init=False,
234
- )
235
- on_update: Hook[[Updater[T]], Updater[T]] = field(
236
- default_factory=Hook,
237
- init=False,
238
- )
239
-
240
-
241
226
  @dataclass(repr=False, frozen=True, slots=True)
242
227
  class Locator(Broker):
243
228
  __records: dict[InputType[Any], Record[Any]] = field(
@@ -249,8 +234,6 @@ class Locator(Broker):
249
234
  init=False,
250
235
  )
251
236
 
252
- static_hooks: ClassVar[LocatorHooks[Any]] = LocatorHooks()
253
-
254
237
  def __getitem__[T](self, cls: InputType[T], /) -> Injectable[T]:
255
238
  for input_class in self.__standardize_inputs((cls,)):
256
239
  try:
@@ -325,25 +308,30 @@ class Locator(Broker):
325
308
 
326
309
  yield cls, record
327
310
 
311
+ @staticmethod
328
312
  def __keep_new_record[T](
329
- self,
330
313
  new: Record[T],
331
314
  existing: Record[T],
332
315
  cls: InputType[T],
333
316
  ) -> bool:
334
- return apply_hooks(
335
- lambda *args, **kwargs: False,
336
- self.static_hooks.on_conflict,
337
- )(new, existing, cls)
317
+ new_mode, existing_mode = new.mode, existing.mode
318
+ is_override = new_mode == Mode.OVERRIDE
319
+
320
+ if new_mode == existing_mode and not is_override:
321
+ raise RuntimeError(f"An injectable already exists for the class `{cls}`.")
322
+
323
+ return is_override or new_mode.rank > existing_mode.rank
338
324
 
325
+ @staticmethod
339
326
  def __standardize_inputs[T](
340
- self,
341
327
  classes: Iterable[InputType[T]],
342
328
  ) -> Iterable[InputType[T]]:
343
- return apply_hooks(lambda c: c, self.static_hooks.on_input)(classes)
329
+ return tuple(standardize_types(*classes, with_origin=True))
344
330
 
345
- def __update_preprocessing[T](self, updater: Updater[T]) -> Updater[T]:
346
- return apply_hooks(lambda u: u, self.static_hooks.on_update)(updater)
331
+ @staticmethod
332
+ def __update_preprocessing[T](updater: Updater[T]) -> Updater[T]:
333
+ updater.classes = frozenset(standardize_types(*updater.classes))
334
+ return updater
347
335
 
348
336
 
349
337
  """
@@ -620,7 +608,7 @@ class Module(Broker, EventListener):
620
608
  async def aget_instance(self, cls, default=None): # type: ignore[no-untyped-def]
621
609
  try:
622
610
  return await self.afind_instance(cls)
623
- except KeyError:
611
+ except (KeyError, SkipInjectable):
624
612
  return default
625
613
 
626
614
  @overload
@@ -640,7 +628,7 @@ class Module(Broker, EventListener):
640
628
  def get_instance(self, cls, default=None): # type: ignore[no-untyped-def]
641
629
  try:
642
630
  return self.find_instance(cls)
643
- except KeyError:
631
+ except (KeyError, SkipInjectable):
644
632
  return default
645
633
 
646
634
  @overload
@@ -866,29 +854,28 @@ class Dependencies:
866
854
  lazy_mapping: Lazy[Mapping[str, Injectable[Any]]]
867
855
 
868
856
  def __iter__(self) -> Iterator[tuple[str, Any]]:
869
- for name, injectable in self.mapping.items():
870
- instance = injectable.get_instance()
871
- yield name, instance
857
+ for name, injectable in self.items():
858
+ with suppress(SkipInjectable):
859
+ yield name, injectable.get_instance()
872
860
 
873
861
  async def __aiter__(self) -> AsyncIterator[tuple[str, Any]]:
874
- for name, injectable in self.mapping.items():
875
- instance = await injectable.aget_instance()
876
- yield name, instance
862
+ for name, injectable in self.items():
863
+ with suppress(SkipInjectable):
864
+ yield name, await injectable.aget_instance()
877
865
 
878
866
  @property
879
867
  def are_resolved(self) -> bool:
880
868
  return self.lazy_mapping.is_set
881
869
 
882
- @property
883
- def mapping(self) -> Mapping[str, Injectable[Any]]:
884
- return ~self.lazy_mapping
885
-
886
870
  async def aget_arguments(self) -> dict[str, Any]:
887
871
  return {key: value async for key, value in self}
888
872
 
889
873
  def get_arguments(self) -> dict[str, Any]:
890
874
  return dict(self)
891
875
 
876
+ def items(self) -> Iterator[tuple[str, Injectable[Any]]]:
877
+ return iter((~self.lazy_mapping).items())
878
+
892
879
  @classmethod
893
880
  def from_iterable(cls, iterable: Iterable[tuple[str, Injectable[Any]]]) -> Self:
894
881
  lazy_mapping = Lazy(lambda: dict(iterable))
@@ -10,6 +10,7 @@ __all__ = (
10
10
  "ScopeAlreadyDefinedError",
11
11
  "ScopeError",
12
12
  "ScopeUndefinedError",
13
+ "SkipInjectable",
13
14
  )
14
15
 
15
16
 
@@ -30,6 +31,9 @@ class NoInjectable[T](KeyError, InjectionError):
30
31
  return self.__class
31
32
 
32
33
 
34
+ class SkipInjectable(InjectionError): ...
35
+
36
+
33
37
  class ModuleError(InjectionError): ...
34
38
 
35
39
 
@@ -42,7 +46,7 @@ class ModuleNotUsedError(KeyError, ModuleError): ...
42
46
  class ScopeError(InjectionError): ...
43
47
 
44
48
 
45
- class ScopeUndefinedError(LookupError, ScopeError): ...
49
+ class ScopeUndefinedError(LookupError, SkipInjectable, ScopeError): ...
46
50
 
47
51
 
48
52
  class ScopeAlreadyDefinedError(ScopeError): ...
File without changes
@@ -24,7 +24,7 @@ test = [
24
24
 
25
25
  [project]
26
26
  name = "python-injection"
27
- version = "0.14.1"
27
+ version = "0.14.3"
28
28
  description = "Fast and easy dependency injection framework."
29
29
  license = { text = "MIT" }
30
30
  readme = "README.md"
@@ -1,53 +0,0 @@
1
- from collections.abc import Iterable
2
- from typing import Any
3
-
4
- from injection._core.common.type import InputType, standardize_types
5
- from injection._core.hook import HookGenerator
6
- from injection._core.module import Locator, Mode, Record, Updater
7
-
8
- __all__ = ()
9
-
10
-
11
- @Locator.static_hooks.on_conflict
12
- def check_mode[T](
13
- new: Record[T],
14
- existing: Record[T],
15
- cls: InputType[T],
16
- *_: Any,
17
- **__: Any,
18
- ) -> HookGenerator[bool]:
19
- new_mode = new.mode
20
- is_override = new_mode == Mode.OVERRIDE
21
-
22
- if new_mode == existing.mode and not is_override:
23
- raise RuntimeError(f"An injectable already exists for the class `{cls}`.")
24
-
25
- value = yield
26
- return value or is_override
27
-
28
-
29
- @Locator.static_hooks.on_conflict
30
- def compare_mode_rank[T](
31
- new: Record[T],
32
- existing: Record[T],
33
- *_: Any,
34
- **__: Any,
35
- ) -> HookGenerator[bool]:
36
- value = yield
37
- return value or new.mode.rank > existing.mode.rank
38
-
39
-
40
- @Locator.static_hooks.on_input
41
- def standardize_input_classes[T](
42
- *_: Any,
43
- **__: Any,
44
- ) -> HookGenerator[Iterable[InputType[T]]]:
45
- classes = yield
46
- return tuple(standardize_types(*classes, with_origin=True))
47
-
48
-
49
- @Locator.static_hooks.on_update
50
- def standardize_classes[T](*_: Any, **__: Any) -> HookGenerator[Updater[T]]:
51
- updater = yield
52
- updater.classes = frozenset(standardize_types(*updater.classes))
53
- return updater
@@ -1,106 +0,0 @@
1
- import itertools
2
- from collections.abc import Callable, Generator, Iterator
3
- from dataclasses import dataclass, field
4
- from inspect import isclass, isgeneratorfunction
5
- from typing import Any, Self, TypeGuard
6
-
7
- from injection.exceptions import HookError
8
-
9
- type HookGenerator[T] = Generator[None, T, T]
10
- type HookGeneratorFunction[**P, T] = Callable[P, HookGenerator[T]]
11
- type HookFunction[**P, T] = Callable[P, T] | HookGeneratorFunction[P, T]
12
-
13
-
14
- @dataclass(eq=False, frozen=True, slots=True)
15
- class Hook[**P, T]:
16
- __functions: list[HookFunction[P, T]] = field(
17
- default_factory=list,
18
- init=False,
19
- repr=False,
20
- )
21
-
22
- def __call__(
23
- self,
24
- wrapped: HookFunction[P, T] | type[HookFunction[P, T]] | None = None,
25
- /,
26
- ) -> Any:
27
- def decorator(wp: Any) -> Any:
28
- self.add(wp() if isclass(wp) else wp)
29
- return wp
30
-
31
- return decorator(wrapped) if wrapped else decorator
32
-
33
- @property
34
- def __stack(self) -> Iterator[HookFunction[P, T]]:
35
- return iter(self.__functions)
36
-
37
- def add(self, *functions: HookFunction[P, T]) -> Self:
38
- self.__functions.extend(reversed(functions))
39
- return self
40
-
41
- @classmethod
42
- def apply_several(cls, handler: Callable[P, T], *hooks: Self) -> Callable[P, T]:
43
- stack = itertools.chain.from_iterable((hook.__stack for hook in hooks))
44
- return cls.__apply_stack(handler, stack)
45
-
46
- @classmethod
47
- def __apply_function(
48
- cls,
49
- handler: Callable[P, T],
50
- function: HookFunction[P, T],
51
- ) -> Callable[P, T]:
52
- if not cls.__is_hook_generator_function(function):
53
- return function # type: ignore[return-value]
54
-
55
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
56
- hook: HookGenerator[T] = function(*args, **kwargs)
57
-
58
- try:
59
- next(hook)
60
-
61
- try:
62
- value = handler(*args, **kwargs)
63
- except BaseException as exc:
64
- hook.throw(exc)
65
- else:
66
- hook.send(value)
67
-
68
- except StopIteration as stop:
69
- return stop.value
70
-
71
- finally:
72
- hook.close()
73
-
74
- raise HookError("Missing return value.")
75
-
76
- return wrapper
77
-
78
- @classmethod
79
- def __apply_stack(
80
- cls,
81
- handler: Callable[P, T],
82
- stack: Iterator[HookFunction[P, T]],
83
- ) -> Callable[P, T]:
84
- for function in stack:
85
- new_handler = cls.__apply_function(handler, function)
86
- return cls.__apply_stack(new_handler, stack)
87
-
88
- return handler
89
-
90
- @staticmethod
91
- def __is_hook_generator_function[**_P, _T](
92
- function: HookFunction[_P, _T],
93
- ) -> TypeGuard[HookGeneratorFunction[_P, _T]]:
94
- for fn in function, getattr(function, "__call__", None):
95
- if isgeneratorfunction(fn):
96
- return True
97
-
98
- return False
99
-
100
-
101
- def apply_hooks[**P, T](
102
- handler: Callable[P, T],
103
- hook: Hook[P, T],
104
- *hooks: Hook[P, T],
105
- ) -> Callable[P, T]:
106
- return Hook.apply_several(handler, hook, *hooks)