python-injection 0.9.0__py3-none-any.whl → 0.9.2__py3-none-any.whl

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.

Potentially problematic release.


This version of python-injection might be problematic. Click here for more details.

injection/__init__.py CHANGED
@@ -7,7 +7,6 @@ __all__ = (
7
7
  "InjectableMode",
8
8
  "Module",
9
9
  "ModulePriority",
10
- "default_module",
11
10
  "get_instance",
12
11
  "get_lazy_instance",
13
12
  "inject",
@@ -17,12 +16,14 @@ __all__ = (
17
16
  "singleton",
18
17
  )
19
18
 
20
- default_module = Module(f"{__name__}:default_module")
19
+ _module = Module.default()
21
20
 
22
- get_instance = default_module.get_instance
23
- get_lazy_instance = default_module.get_lazy_instance
24
- inject = default_module.inject
25
- injectable = default_module.injectable
26
- set_constant = default_module.set_constant
27
- should_be_injectable = default_module.should_be_injectable
28
- singleton = default_module.singleton
21
+ get_instance = _module.get_instance
22
+ get_lazy_instance = _module.get_lazy_instance
23
+ inject = _module.inject
24
+ injectable = _module.injectable
25
+ set_constant = _module.set_constant
26
+ should_be_injectable = _module.should_be_injectable
27
+ singleton = _module.singleton
28
+
29
+ del _module
injection/__init__.pyi CHANGED
@@ -6,7 +6,6 @@ from types import UnionType
6
6
  from typing import (
7
7
  Any,
8
8
  ContextManager,
9
- Final,
10
9
  Protocol,
11
10
  Self,
12
11
  final,
@@ -19,15 +18,17 @@ from .core import InjectableFactory
19
18
  from .core import ModeStr as InjectableModeStr
20
19
  from .core import PriorityStr as ModulePriorityStr
21
20
 
22
- default_module: Final[Module] = ...
21
+ _module: Module = ...
23
22
 
24
- get_instance = default_module.get_instance
25
- get_lazy_instance = default_module.get_lazy_instance
26
- inject = default_module.inject
27
- injectable = default_module.injectable
28
- set_constant = default_module.set_constant
29
- should_be_injectable = default_module.should_be_injectable
30
- singleton = default_module.singleton
23
+ get_instance = _module.get_instance
24
+ get_lazy_instance = _module.get_lazy_instance
25
+ inject = _module.inject
26
+ injectable = _module.injectable
27
+ set_constant = _module.set_constant
28
+ should_be_injectable = _module.should_be_injectable
29
+ singleton = _module.singleton
30
+
31
+ del _module
31
32
 
32
33
  @final
33
34
  class Module:
@@ -178,6 +179,18 @@ class Module:
178
179
  Function to unlock the module by deleting cached instances of singletons.
179
180
  """
180
181
 
182
+ @classmethod
183
+ def from_name(cls, name: str) -> Self:
184
+ """
185
+ Class method for getting or creating a module by name.
186
+ """
187
+
188
+ @classmethod
189
+ def default(cls) -> Self:
190
+ """
191
+ Class method for getting the default module.
192
+ """
193
+
181
194
  @final
182
195
  class ModulePriority(StrEnum):
183
196
  LOW = ...
@@ -15,7 +15,7 @@ class Invertible[T](Protocol):
15
15
 
16
16
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
17
17
  class SimpleInvertible[T](Invertible[T]):
18
- callable: Callable[[], T]
18
+ callable: Callable[..., T]
19
19
 
20
20
  def __invert__(self) -> T:
21
21
  return self.callable()
injection/common/lazy.py CHANGED
@@ -9,7 +9,7 @@ __all__ = ("Lazy", "LazyMapping")
9
9
  class Lazy[T](Invertible[T]):
10
10
  __slots__ = ("__cache", "__is_set")
11
11
 
12
- def __init__(self, factory: Callable[[], T]):
12
+ def __init__(self, factory: Callable[..., T]):
13
13
  self.__setup_cache(factory)
14
14
 
15
15
  def __invert__(self) -> T:
@@ -19,7 +19,7 @@ class Lazy[T](Invertible[T]):
19
19
  def is_set(self) -> bool:
20
20
  return self.__is_set
21
21
 
22
- def __setup_cache(self, factory: Callable[[], T]):
22
+ def __setup_cache(self, factory: Callable[..., T]):
23
23
  def cache_generator() -> Iterator[T]:
24
24
  nonlocal factory
25
25
  cached = factory()
@@ -6,5 +6,7 @@ __all__ = ("synchronized",)
6
6
 
7
7
  @contextmanager
8
8
  def synchronized():
9
- with RLock():
10
- yield
9
+ lock = RLock()
10
+
11
+ with lock:
12
+ yield lock
@@ -21,11 +21,13 @@ class TypeReport[T](NamedTuple):
21
21
  args: tuple[Any, ...]
22
22
 
23
23
  @property
24
- def cls(self) -> type[T]:
25
- if self.args:
26
- return self.origin[*self.args]
24
+ def type(self) -> type[T]:
25
+ origin = self.origin
26
+
27
+ if args := self.args:
28
+ return origin[*args]
27
29
 
28
- return self.origin
30
+ return origin
29
31
 
30
32
  @property
31
33
  def no_args(self) -> Self:
injection/core/module.py CHANGED
@@ -17,7 +17,7 @@ from dataclasses import dataclass, field
17
17
  from enum import StrEnum
18
18
  from functools import partialmethod, singledispatchmethod, update_wrapper
19
19
  from inspect import Signature, isclass
20
- from queue import Queue
20
+ from queue import Empty, Queue
21
21
  from types import MethodType, UnionType
22
22
  from typing import (
23
23
  Any,
@@ -78,10 +78,10 @@ class ContainerDependenciesUpdated(ContainerEvent):
78
78
 
79
79
  def __str__(self) -> str:
80
80
  length = len(self.reports)
81
- formatted_classes = ", ".join(f"`{report.cls}`" for report in self.reports)
81
+ formatted_types = ", ".join(f"`{report.type}`" for report in self.reports)
82
82
  return (
83
- f"{length} container dependenc{'ies' if length > 1 else 'y'} have been "
84
- f"updated{f': {formatted_classes}' if formatted_classes else ''}."
83
+ f"{length} container dependenc{'ies' if length > 1 else 'y'} have "
84
+ f"been updated{f': {formatted_types}' if formatted_types else ''}."
85
85
  )
86
86
 
87
87
 
@@ -161,7 +161,7 @@ class Injectable[T](Protocol):
161
161
 
162
162
  @dataclass(repr=False, frozen=True, slots=True)
163
163
  class BaseInjectable[T](Injectable[T], ABC):
164
- factory: Callable[[], T]
164
+ factory: Callable[..., T]
165
165
 
166
166
 
167
167
  class NewInjectable[T](BaseInjectable[T]):
@@ -174,7 +174,7 @@ class NewInjectable[T](BaseInjectable[T]):
174
174
  class SingletonInjectable[T](BaseInjectable[T]):
175
175
  __slots__ = ("__dict__",)
176
176
 
177
- __INSTANCE_KEY: ClassVar[str] = "$instance"
177
+ __key: ClassVar[str] = "$instance"
178
178
 
179
179
  @property
180
180
  def cache(self) -> MutableMapping[str, Any]:
@@ -182,18 +182,18 @@ class SingletonInjectable[T](BaseInjectable[T]):
182
182
 
183
183
  @property
184
184
  def is_locked(self) -> bool:
185
- return self.__INSTANCE_KEY in self.cache
185
+ return self.__key in self.cache
186
186
 
187
187
  def unlock(self):
188
188
  self.cache.clear()
189
189
 
190
190
  def get_instance(self) -> T:
191
191
  with suppress(KeyError):
192
- return self.cache[self.__INSTANCE_KEY]
192
+ return self.cache[self.__key]
193
193
 
194
194
  with synchronized():
195
195
  instance = self.factory()
196
- self.cache[self.__INSTANCE_KEY] = instance
196
+ self.cache[self.__key] = instance
197
197
 
198
198
  return instance
199
199
 
@@ -340,7 +340,7 @@ class Container(Broker):
340
340
  else:
341
341
  if mode == current_mode and mode != Mode.OVERRIDE:
342
342
  raise RuntimeError(
343
- f"An injectable already exists for the class `{report.cls}`."
343
+ f"An injectable already exists for the class `{report.type}`."
344
344
  )
345
345
 
346
346
  elif rank < current_mode.rank:
@@ -368,16 +368,27 @@ type PriorityStr = Literal["low", "high"]
368
368
  type InjectableFactory[T] = Callable[[Callable[..., T]], Injectable[T]]
369
369
 
370
370
 
371
- @dataclass(repr=False, eq=False, frozen=True, slots=True)
371
+ @dataclass(eq=False, frozen=True, slots=True)
372
372
  class Module(EventListener, Broker):
373
373
  name: str | None = field(default=None)
374
- __channel: EventChannel = field(default_factory=EventChannel, init=False)
375
- __container: Container = field(default_factory=Container, init=False)
374
+ __channel: EventChannel = field(
375
+ default_factory=EventChannel,
376
+ init=False,
377
+ repr=False,
378
+ )
379
+ __container: Container = field(
380
+ default_factory=Container,
381
+ init=False,
382
+ repr=False,
383
+ )
376
384
  __modules: OrderedDict[Module, None] = field(
377
385
  default_factory=OrderedDict,
378
386
  init=False,
387
+ repr=False,
379
388
  )
380
389
 
390
+ __instances: ClassVar[dict[str, Module]] = {}
391
+
381
392
  def __post_init__(self):
382
393
  self.__container.add_listener(self)
383
394
 
@@ -394,9 +405,6 @@ class Module(EventListener, Broker):
394
405
  def __contains__(self, cls: type | UnionType, /) -> bool:
395
406
  return any(cls in broker for broker in self.__brokers)
396
407
 
397
- def __str__(self) -> str:
398
- return self.name or object.__str__(self)
399
-
400
408
  @property
401
409
  def is_locked(self) -> bool:
402
410
  return any(broker.is_locked for broker in self.__brokers)
@@ -611,6 +619,19 @@ class Module(EventListener, Broker):
611
619
  f"`{module}` can't be found in the modules used by `{self}`."
612
620
  ) from exc
613
621
 
622
+ @classmethod
623
+ def from_name(cls, name: str) -> Self:
624
+ with suppress(KeyError):
625
+ return cls.__instances[name]
626
+
627
+ instance = cls(name)
628
+ cls.__instances[name] = instance
629
+ return instance
630
+
631
+ @classmethod
632
+ def default(cls) -> Self:
633
+ return cls.from_name("default")
634
+
614
635
 
615
636
  """
616
637
  InjectedFunction
@@ -641,7 +662,7 @@ class Dependencies:
641
662
 
642
663
  @classmethod
643
664
  def from_mapping(cls, mapping: Mapping[str, Injectable]) -> Self:
644
- return cls(mapping=mapping)
665
+ return cls(mapping)
645
666
 
646
667
  @classmethod
647
668
  def empty(cls) -> Self:
@@ -702,10 +723,8 @@ class InjectedFunction(EventListener):
702
723
  update_wrapper(self, wrapped, updated=())
703
724
  self.__dependencies = Dependencies.empty()
704
725
  self.__owner = None
705
-
706
- queue = Queue[Callable[[], Any]]()
707
- queue.put_nowait(self.__set_signature)
708
- self.__setup_queue = queue
726
+ self.__setup_queue = Queue[Callable[..., Any]](maxsize=2)
727
+ self.on_setup(self.__set_signature)
709
728
 
710
729
  def __repr__(self) -> str: # pragma: no cover
711
730
  return repr(self.wrapped)
@@ -714,13 +733,7 @@ class InjectedFunction(EventListener):
714
733
  return str(self.wrapped)
715
734
 
716
735
  def __call__(self, /, *args, **kwargs) -> Any:
717
- queue = self.__setup_queue
718
-
719
- while not queue.empty():
720
- setup = queue.get()
721
- setup()
722
- queue.task_done()
723
-
736
+ self.__setup()
724
737
  arguments = self.bind(args, kwargs)
725
738
  return self.wrapped(*arguments.args, **arguments.kwargs)
726
739
 
@@ -775,15 +788,15 @@ class InjectedFunction(EventListener):
775
788
  self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
776
789
  return self
777
790
 
778
- def on_setup(self, wrapped: Callable[[], Any] = None, /):
791
+ def on_setup(self, wrapped: Callable[..., Any] = None, /):
779
792
  def decorator(wp):
780
- self.__setup_queue.put(wp)
793
+ self.__setup_queue.put_nowait(wp)
781
794
  return wp
782
795
 
783
796
  return decorator(wrapped) if wrapped else decorator
784
797
 
785
798
  @singledispatchmethod
786
- def on_event(self, event: Event, /) -> None: # type: ignore
799
+ def on_event(self, event: Event, /) -> ContextManager | None: # type: ignore
787
800
  return None
788
801
 
789
802
  @on_event.register
@@ -792,6 +805,20 @@ class InjectedFunction(EventListener):
792
805
  yield
793
806
  self.update(event.on_module)
794
807
 
808
+ def __setup(self):
809
+ queue = self.__setup_queue
810
+
811
+ while True:
812
+ try:
813
+ task = queue.get_nowait()
814
+ except Empty:
815
+ break
816
+
817
+ task()
818
+ queue.task_done()
819
+
820
+ queue.join()
821
+
795
822
  def __set_signature(self) -> Self:
796
823
  self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
797
824
  return self
@@ -2,7 +2,7 @@ from typing import Any
2
2
 
3
3
  from rodi import ContainerProtocol
4
4
 
5
- from injection import Module, default_module
5
+ from injection import Module
6
6
 
7
7
  __all__ = ("InjectionServices",)
8
8
 
@@ -14,8 +14,8 @@ class InjectionServices(ContainerProtocol):
14
14
 
15
15
  __slots__ = ("__module",)
16
16
 
17
- def __init__(self, module: Module = default_module):
18
- self.__module = module
17
+ def __init__(self, module: Module = None):
18
+ self.__module = module or Module.default()
19
19
 
20
20
  def __contains__(self, item: Any) -> bool:
21
21
  return item in self.__module
@@ -0,0 +1,40 @@
1
+ from contextlib import contextmanager
2
+
3
+ from injection import Module, ModulePriority
4
+
5
+ __all__ = (
6
+ "set_test_constant",
7
+ "should_be_test_injectable",
8
+ "test_injectable",
9
+ "test_singleton",
10
+ "use_test_injectables",
11
+ )
12
+
13
+
14
+ def get_test_module() -> Module:
15
+ return Module.from_name("testing")
16
+
17
+
18
+ _module = get_test_module()
19
+
20
+ set_test_constant = _module.set_constant
21
+ should_be_test_injectable = _module.should_be_injectable
22
+ test_injectable = _module.injectable
23
+ test_singleton = _module.singleton
24
+
25
+ del _module
26
+
27
+
28
+ @contextmanager
29
+ def use_test_injectables(*, on: Module = None, test_module: Module = None):
30
+ on = on or Module.default()
31
+ test_module = test_module or get_test_module()
32
+
33
+ for module in (on, test_module):
34
+ module.unlock()
35
+
36
+ del module
37
+
38
+ with on.use_temporarily(test_module, priority=ModulePriority.HIGH):
39
+ yield
40
+ on.unlock()
@@ -0,0 +1,22 @@
1
+ from contextlib import ContextDecorator
2
+ from typing import ContextManager
3
+
4
+ from injection import Module
5
+
6
+ _module: Module = ...
7
+
8
+ set_test_constant = _module.set_constant
9
+ should_be_test_injectable = _module.should_be_injectable
10
+ test_injectable = _module.injectable
11
+ test_singleton = _module.singleton
12
+
13
+ del _module
14
+
15
+ def use_test_injectables(
16
+ *,
17
+ on: Module = ...,
18
+ test_module: Module = ...,
19
+ ) -> ContextManager | ContextDecorator:
20
+ """
21
+ Context manager or decorator for temporary use test module.
22
+ """
injection/utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections.abc import Callable
1
2
  from importlib import import_module
2
3
  from pkgutil import walk_packages
3
4
  from types import ModuleType
@@ -5,9 +6,10 @@ from types import ModuleType
5
6
  __all__ = ("load_package",)
6
7
 
7
8
 
8
- def load_package(package: ModuleType | str):
9
+ def load_package(package: ModuleType | str, predicate: Callable[[str], bool] = None):
9
10
  """
10
11
  Function for importing all modules in a Python package.
12
+ Pass the `predicate` parameter if you want to filter the modules to be imported.
11
13
  """
12
14
 
13
15
  if isinstance(package, str):
@@ -20,8 +22,15 @@ def load_package(package: ModuleType | str):
20
22
  "Package has no `__path__` attribute, as it's probably a module."
21
23
  ) from exc
22
24
 
25
+ if predicate is None:
26
+
27
+ def predicate(_: str) -> bool:
28
+ return True
29
+
23
30
  for info in walk_packages(path=path, prefix=f"{package.__name__}."):
24
- if info.ispkg:
31
+ name = info.name
32
+
33
+ if info.ispkg or not predicate(name):
25
34
  continue
26
35
 
27
- import_module(info.name)
36
+ import_module(name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  # Basic usage
17
17
 
18
- ## Create an injectable
18
+ ## Register an injectable
19
19
 
20
20
  > **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
21
21
 
@@ -0,0 +1,21 @@
1
+ injection/__init__.py,sha256=LhfwYFMBw6tJ7XA_C353w61OPt4IuQQgs3zIjrwMIz8,654
2
+ injection/__init__.pyi,sha256=dfIQAR8L8Yr7dM-DODR4czwUd2a7zzFQHzb8mr1Q6P0,6235
3
+ injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ injection/common/event.py,sha256=5Rdb2m3vAMCic8cQAVkStJDbrDrW_lk6kav8wYwmexM,1283
5
+ injection/common/invertible.py,sha256=a-fht4TxMnki-oFFaZrDz52X_LWx0NU60KQlw72pzs4,528
6
+ injection/common/lazy.py,sha256=SpQhlGBpMpeD9R5R-CdIInSVDv1XZWvnkERp1J6wsus,1315
7
+ injection/common/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ injection/common/tools/threading.py,sha256=HlvP6k_-eZaK8JbB2b9PP171IZVe_0W2oMYsw3ebdKA,187
9
+ injection/common/tools/type.py,sha256=ThM3Z1_gHmsdT4Jp7PlZgJY0lsufc4InPO65485Ugsg,1739
10
+ injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
11
+ injection/core/module.py,sha256=5Hqdll6gxiaHQCZl9OVWKojmzj36azzOPvIgi-6PGdg,21723
12
+ injection/exceptions.py,sha256=RsWWiWwKSMU0vxXQqQSn6CKHLMrGu4SSzYUAy9OJRXk,626
13
+ injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ injection/integrations/blacksheep.py,sha256=82P_owhF3FKIJxxalnSic3AJnoOr1mkojkWMefpO6QI,731
15
+ injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ injection/testing/__init__.py,sha256=cr4e3VnoKbWzkc6uyC03sffT59deKgLN-SKiZjhzooI,880
17
+ injection/testing/__init__.pyi,sha256=_u95cJVHBkt69HUvnu2bbfYWmi7GOH_1qyFyvC1PxBk,518
18
+ injection/utils.py,sha256=a_H6RC6cNWbG1NQ-zvtrlgwnfS5Jfu0X6VJwIUIHlSU,974
19
+ python_injection-0.9.2.dist-info/METADATA,sha256=UkMpmL3GAAQ5nqRyf-3-j-_hB8pJrXLW7D3E9JSEsE0,3572
20
+ python_injection-0.9.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
21
+ python_injection-0.9.2.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- injection/__init__.py,sha256=Bf6S99E2srD3752xlJf3uAdiGIzY2YHOZafcwEiwY70,739
2
- injection/__init__.pyi,sha256=HEk1HXAmwtq2ZEFLf5A7VWhdpPVWVQpjsLip9JzepEY,6023
3
- injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- injection/common/event.py,sha256=5Rdb2m3vAMCic8cQAVkStJDbrDrW_lk6kav8wYwmexM,1283
5
- injection/common/invertible.py,sha256=noNcmJ96IQi0XJms0dyfrx_AvKZnQM0sZyxhc2l6qo0,527
6
- injection/common/lazy.py,sha256=FEas6ewwOGWvRR8cflmyVvSLZd_-Fxd0QeIdvAM5I9c,1313
7
- injection/common/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- injection/common/tools/threading.py,sha256=Urcy59Rp34psRWmzzQyNDu4Zm-DuCkPsp4qO1x_W4qQ,165
9
- injection/common/tools/type.py,sha256=5Ixswd9-sSqJdmh9lTd3jADFHWgD0wZSwecVi1opeO0,1715
10
- injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
11
- injection/core/module.py,sha256=nCa10YQu-o-W0jMt-EnjCuXc1VioqN5CZXK2EoS4zgo,21241
12
- injection/exceptions.py,sha256=RsWWiWwKSMU0vxXQqQSn6CKHLMrGu4SSzYUAy9OJRXk,626
13
- injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- injection/integrations/blacksheep.py,sha256=GQBZCyl_DNDuCJdV56EYaiYCXGDMmP66cEahRzUZpmc,737
15
- injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- injection/utils.py,sha256=_79aiciimZpuoUTz5lojKySUMMzpkU-e7SotiHIFTI8,676
17
- python_injection-0.9.0.dist-info/METADATA,sha256=j_ikavu9sPJ2xkKcX6Vr8MmhoNayLEiVNLPQ99TzWNY,3570
18
- python_injection-0.9.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
19
- python_injection-0.9.0.dist-info/RECORD,,