python-injection 0.13.1__tar.gz → 0.13.2__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.13.1 → python_injection-0.13.2}/PKG-INFO +1 -1
  2. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/common/asynchronous.py +5 -15
  3. python_injection-0.13.2/injection/_core/common/lazy.py +55 -0
  4. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/injectables.py +3 -3
  5. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/module.py +32 -54
  6. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/scope.py +1 -3
  7. {python_injection-0.13.1 → python_injection-0.13.2}/pyproject.toml +1 -1
  8. python_injection-0.13.1/injection/_core/common/lazy.py +0 -56
  9. {python_injection-0.13.1 → python_injection-0.13.2}/.gitignore +0 -0
  10. {python_injection-0.13.1 → python_injection-0.13.2}/README.md +0 -0
  11. {python_injection-0.13.1 → python_injection-0.13.2}/injection/__init__.py +0 -0
  12. {python_injection-0.13.1 → python_injection-0.13.2}/injection/__init__.pyi +0 -0
  13. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/__init__.py +0 -0
  14. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/common/__init__.py +0 -0
  15. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/common/event.py +0 -0
  16. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/common/invertible.py +0 -0
  17. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/common/key.py +0 -0
  18. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/common/type.py +0 -0
  19. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/descriptors.py +0 -0
  20. {python_injection-0.13.1 → python_injection-0.13.2}/injection/_core/hook.py +0 -0
  21. {python_injection-0.13.1 → python_injection-0.13.2}/injection/exceptions.py +0 -0
  22. {python_injection-0.13.1 → python_injection-0.13.2}/injection/integrations/__init__.py +0 -0
  23. {python_injection-0.13.1 → python_injection-0.13.2}/injection/integrations/fastapi.py +0 -0
  24. {python_injection-0.13.1 → python_injection-0.13.2}/injection/py.typed +0 -0
  25. {python_injection-0.13.1 → python_injection-0.13.2}/injection/testing/__init__.py +0 -0
  26. {python_injection-0.13.1 → python_injection-0.13.2}/injection/testing/__init__.pyi +0 -0
  27. {python_injection-0.13.1 → python_injection-0.13.2}/injection/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.13.1
3
+ Version: 0.13.2
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -1,17 +1,7 @@
1
- import asyncio
2
1
  from abc import abstractmethod
3
- from collections.abc import Awaitable, Callable, Coroutine, Generator
2
+ from collections.abc import Awaitable, Callable, Generator
4
3
  from dataclasses import dataclass
5
- from typing import Any, Protocol, runtime_checkable
6
-
7
-
8
- def run_sync[T](coroutine: Coroutine[Any, Any, T]) -> T:
9
- loop = asyncio.get_event_loop()
10
-
11
- try:
12
- return loop.run_until_complete(coroutine)
13
- finally:
14
- coroutine.close()
4
+ from typing import Any, NoReturn, Protocol, runtime_checkable
15
5
 
16
6
 
17
7
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -37,13 +27,13 @@ class Caller[**P, T](Protocol):
37
27
 
38
28
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
39
29
  class AsyncCaller[**P, T](Caller[P, T]):
40
- callable: Callable[P, Coroutine[Any, Any, T]]
30
+ callable: Callable[P, Awaitable[T]]
41
31
 
42
32
  async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
43
33
  return await self.callable(*args, **kwargs)
44
34
 
45
- def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
46
- return run_sync(self.callable(*args, **kwargs))
35
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> NoReturn:
36
+ raise RuntimeError("Can't call async callable synchronously.")
47
37
 
48
38
 
49
39
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -0,0 +1,55 @@
1
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
2
+ from functools import partial
3
+
4
+ from injection._core.common.asynchronous import SimpleAwaitable
5
+ from injection._core.common.invertible import Invertible, SimpleInvertible
6
+
7
+
8
+ def lazy[T](factory: Callable[..., T]) -> Invertible[T]:
9
+ def cache() -> Iterator[T]:
10
+ nonlocal factory
11
+ value = factory()
12
+ del factory
13
+
14
+ while True:
15
+ yield value
16
+
17
+ getter = partial(next, cache())
18
+ return SimpleInvertible(getter)
19
+
20
+
21
+ def alazy[T](factory: Callable[..., Awaitable[T]]) -> Awaitable[T]:
22
+ async def cache() -> AsyncIterator[T]:
23
+ nonlocal factory
24
+ value = await factory()
25
+ del factory
26
+
27
+ while True:
28
+ yield value
29
+
30
+ getter = partial(anext, cache())
31
+ return SimpleAwaitable(getter)
32
+
33
+
34
+ class Lazy[T](Invertible[T]):
35
+ __slots__ = ("__invertible", "__is_set")
36
+
37
+ __invertible: Invertible[T]
38
+ __is_set: bool
39
+
40
+ def __init__(self, factory: Callable[..., T]) -> None:
41
+ @lazy
42
+ def invertible() -> T:
43
+ value = factory()
44
+ self.__is_set = True
45
+ return value
46
+
47
+ self.__invertible = invertible
48
+ self.__is_set = False
49
+
50
+ def __invert__(self) -> T:
51
+ return ~self.__invertible
52
+
53
+ @property
54
+ def is_set(self) -> bool:
55
+ return self.__is_set
@@ -12,7 +12,7 @@ from typing import (
12
12
  runtime_checkable,
13
13
  )
14
14
 
15
- from injection._core.common.asynchronous import Caller, run_sync
15
+ from injection._core.common.asynchronous import Caller
16
16
  from injection._core.scope import Scope, get_active_scopes, get_scope
17
17
  from injection.exceptions import InjectionError
18
18
 
@@ -138,8 +138,8 @@ class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
138
138
  cm = await self.factory.acall()
139
139
  return await scope.aenter(cm)
140
140
 
141
- def build(self, scope: Scope) -> T:
142
- return run_sync(self.abuild(scope))
141
+ def build(self, scope: Scope) -> NoReturn:
142
+ raise RuntimeError("Can't use async context manager synchronously.")
143
143
 
144
144
 
145
145
  class CMScopedInjectable[T](ScopedInjectable[ContextManager[T], T]):
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  from abc import ABC, abstractmethod
5
- from collections import OrderedDict
4
+ from collections import OrderedDict, deque
6
5
  from collections.abc import (
7
6
  AsyncIterator,
8
7
  Awaitable,
@@ -26,7 +25,6 @@ from inspect import (
26
25
  )
27
26
  from inspect import signature as inspect_signature
28
27
  from logging import Logger, getLogger
29
- from queue import Empty, Queue
30
28
  from types import MethodType
31
29
  from typing import (
32
30
  Any,
@@ -50,7 +48,7 @@ from injection._core.common.asynchronous import (
50
48
  from injection._core.common.event import Event, EventChannel, EventListener
51
49
  from injection._core.common.invertible import Invertible, SimpleInvertible
52
50
  from injection._core.common.key import new_short_key
53
- from injection._core.common.lazy import Lazy, LazyMapping
51
+ from injection._core.common.lazy import Lazy, alazy, lazy
54
52
  from injection._core.common.type import (
55
53
  InputType,
56
54
  TypeInfo,
@@ -296,6 +294,9 @@ class Locator(Broker):
296
294
 
297
295
  async def all_ready(self) -> None:
298
296
  for injectable in self.__injectables:
297
+ if injectable.is_locked:
298
+ continue
299
+
299
300
  await injectable.aget_instance()
300
301
 
301
302
  def add_listener(self, listener: EventListener) -> Self:
@@ -511,7 +512,7 @@ class Module(Broker, EventListener):
511
512
  mode: Mode | ModeStr = Mode.get_default(),
512
513
  ) -> Any:
513
514
  def decorator(wp: type[T]) -> type[T]:
514
- lazy_instance = Lazy(wp)
515
+ lazy_instance = lazy(wp)
515
516
  self.injectable(
516
517
  lambda: ~lazy_instance,
517
518
  ignore_type_hint=True,
@@ -568,7 +569,7 @@ class Module(Broker, EventListener):
568
569
  def make_injected_function(self, wrapped, /): # type: ignore[no-untyped-def]
569
570
  metadata = InjectMetadata(wrapped)
570
571
 
571
- @metadata.on_setup
572
+ @metadata.task
572
573
  def listen() -> None:
573
574
  metadata.update(self)
574
575
  self.add_listener(metadata)
@@ -646,12 +647,10 @@ class Module(Broker, EventListener):
646
647
 
647
648
  def aget_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
648
649
  if cache:
649
- coroutine = self.aget_instance(cls, default)
650
- return asyncio.ensure_future(coroutine)
650
+ return alazy(lambda: self.aget_instance(cls, default))
651
651
 
652
652
  function = self.make_injected_function(lambda instance=default: instance)
653
- metadata = function.__inject_metadata__
654
- metadata.set_owner(cls)
653
+ metadata = function.__inject_metadata__.set_owner(cls)
655
654
  return SimpleAwaitable(metadata.acall)
656
655
 
657
656
  @overload
@@ -674,11 +673,10 @@ class Module(Broker, EventListener):
674
673
 
675
674
  def get_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
676
675
  if cache:
677
- return Lazy(lambda: self.get_instance(cls, default))
676
+ return lazy(lambda: self.get_instance(cls, default))
678
677
 
679
678
  function = self.make_injected_function(lambda instance=default: instance)
680
- metadata = function.__inject_metadata__
681
- metadata.set_owner(cls)
679
+ metadata = function.__inject_metadata__.set_owner(cls)
682
680
  return SimpleInvertible(metadata.call)
683
681
 
684
682
  def update[T](self, updater: Updater[T]) -> Self:
@@ -832,10 +830,7 @@ InjectedFunction
832
830
 
833
831
  @dataclass(repr=False, frozen=True, slots=True)
834
832
  class Dependencies:
835
- mapping: Mapping[str, Injectable[Any]]
836
-
837
- def __bool__(self) -> bool:
838
- return bool(self.mapping)
833
+ lazy_mapping: Lazy[Mapping[str, Injectable[Any]]]
839
834
 
840
835
  def __iter__(self) -> Iterator[tuple[str, Any]]:
841
836
  for name, injectable in self.mapping.items():
@@ -849,10 +844,11 @@ class Dependencies:
849
844
 
850
845
  @property
851
846
  def are_resolved(self) -> bool:
852
- if isinstance(self.mapping, LazyMapping) and not self.mapping.is_set:
853
- return False
847
+ return self.lazy_mapping.is_set
854
848
 
855
- return bool(self)
849
+ @property
850
+ def mapping(self) -> Mapping[str, Injectable[Any]]:
851
+ return ~self.lazy_mapping
856
852
 
857
853
  async def aget_arguments(self) -> dict[str, Any]:
858
854
  return {key: value async for key, value in self}
@@ -861,12 +857,13 @@ class Dependencies:
861
857
  return dict(self)
862
858
 
863
859
  @classmethod
864
- def from_mapping(cls, mapping: Mapping[str, Injectable[Any]]) -> Self:
865
- return cls(mapping)
860
+ def from_iterable(cls, iterable: Iterable[tuple[str, Injectable[Any]]]) -> Self:
861
+ lazy_mapping = Lazy(lambda: dict(iterable))
862
+ return cls(lazy_mapping)
866
863
 
867
864
  @classmethod
868
865
  def empty(cls) -> Self:
869
- return cls.from_mapping({})
866
+ return cls.from_iterable(())
870
867
 
871
868
  @classmethod
872
869
  def resolve(
@@ -875,8 +872,8 @@ class Dependencies:
875
872
  module: Module,
876
873
  owner: type | None = None,
877
874
  ) -> Self:
878
- dependencies = LazyMapping(cls.__resolver(signature, module, owner))
879
- return cls.from_mapping(dependencies)
875
+ iterable = cls.__resolver(signature, module, owner)
876
+ return cls.from_iterable(iterable)
880
877
 
881
878
  @classmethod
882
879
  def __resolver(
@@ -917,21 +914,21 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
917
914
  __slots__ = (
918
915
  "__dependencies",
919
916
  "__owner",
920
- "__setup_queue",
921
917
  "__signature",
918
+ "__tasks",
922
919
  "__wrapped",
923
920
  )
924
921
 
925
922
  __dependencies: Dependencies
926
923
  __owner: type | None
927
- __setup_queue: Queue[Callable[..., Any]] | None
928
924
  __signature: Signature
925
+ __tasks: deque[Callable[..., Any]]
929
926
  __wrapped: Callable[P, T]
930
927
 
931
928
  def __init__(self, wrapped: Callable[P, T], /) -> None:
932
929
  self.__dependencies = Dependencies.empty()
933
930
  self.__owner = None
934
- self.__setup_queue = Queue()
931
+ self.__tasks = deque()
935
932
  self.__wrapped = wrapped
936
933
 
937
934
  @property
@@ -964,12 +961,12 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
964
961
  return self.__bind(args, kwargs, additional_arguments)
965
962
 
966
963
  async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
967
- self.__setup()
964
+ self.__run_tasks()
968
965
  arguments = await self.abind(args, kwargs)
969
966
  return self.wrapped(*arguments.args, **arguments.kwargs)
970
967
 
971
968
  def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
972
- self.__setup()
969
+ self.__run_tasks()
973
970
  arguments = self.bind(args, kwargs)
974
971
  return self.wrapped(*arguments.args, **arguments.kwargs)
975
972
 
@@ -989,14 +986,9 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
989
986
  self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
990
987
  return self
991
988
 
992
- def on_setup[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
989
+ def task[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
993
990
  def decorator(wp: Callable[_P, _T]) -> Callable[_P, _T]:
994
- queue = self.__setup_queue
995
-
996
- if queue is None:
997
- raise RuntimeError(f"`{self}` is already up.")
998
-
999
- queue.put_nowait(wp)
991
+ self.__tasks.append(wp)
1000
992
  return wp
1001
993
 
1002
994
  return decorator(wrapped) if wrapped else decorator
@@ -1027,24 +1019,10 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
1027
1019
  bound.arguments = bound.arguments | additional_arguments | bound.arguments
1028
1020
  return Arguments(bound.args, bound.kwargs)
1029
1021
 
1030
- def __close_setup_queue(self) -> None:
1031
- self.__setup_queue = None
1032
-
1033
- def __setup(self) -> None:
1034
- if (queue := self.__setup_queue) is None:
1035
- return
1036
-
1037
- while True:
1038
- try:
1039
- task = queue.get_nowait()
1040
- except Empty:
1041
- break
1042
-
1022
+ def __run_tasks(self) -> None:
1023
+ while tasks := self.__tasks:
1024
+ task = tasks.popleft()
1043
1025
  task()
1044
- queue.task_done()
1045
-
1046
- queue.join()
1047
- self.__close_setup_queue()
1048
1026
 
1049
1027
 
1050
1028
  class InjectedFunction[**P, T](ABC):
@@ -201,9 +201,7 @@ class SyncScope(BaseScope[ExitStack]):
201
201
  return self.delegate.__exit__(exc_type, exc_value, traceback)
202
202
 
203
203
  async def aenter[T](self, context_manager: AsyncContextManager[T]) -> NoReturn:
204
- raise ScopeError(
205
- "Synchronous scope doesn't support asynchronous context manager."
206
- )
204
+ raise ScopeError("Synchronous scope doesn't support async context manager.")
207
205
 
208
206
  def enter[T](self, context_manager: ContextManager[T]) -> T:
209
207
  return self.delegate.enter_context(context_manager)
@@ -24,7 +24,7 @@ test = [
24
24
 
25
25
  [project]
26
26
  name = "python-injection"
27
- version = "0.13.1"
27
+ version = "0.13.2"
28
28
  description = "Fast and easy dependency injection framework."
29
29
  license = { text = "MIT" }
30
30
  readme = "README.md"
@@ -1,56 +0,0 @@
1
- from collections.abc import Callable, Iterator, Mapping
2
- from types import MappingProxyType
3
-
4
- from injection._core.common.invertible import Invertible
5
-
6
-
7
- class Lazy[T](Invertible[T]):
8
- __slots__ = ("__iterator", "__is_set")
9
-
10
- __iterator: Iterator[T]
11
- __is_set: bool
12
-
13
- def __init__(self, factory: Callable[..., T]) -> None:
14
- self.__setup_cache(factory)
15
-
16
- def __invert__(self) -> T:
17
- return next(self.__iterator)
18
-
19
- @property
20
- def is_set(self) -> bool:
21
- return self.__is_set
22
-
23
- def __setup_cache(self, factory: Callable[..., T]) -> None:
24
- def infinite_yield() -> Iterator[T]:
25
- nonlocal factory
26
- cached = factory()
27
- self.__is_set = True
28
- del factory
29
-
30
- while True:
31
- yield cached
32
-
33
- self.__iterator = infinite_yield()
34
- self.__is_set = False
35
-
36
-
37
- class LazyMapping[K, V](Mapping[K, V]):
38
- __slots__ = ("__lazy",)
39
-
40
- __lazy: Lazy[Mapping[K, V]]
41
-
42
- def __init__(self, iterator: Iterator[tuple[K, V]]) -> None:
43
- self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
44
-
45
- def __getitem__(self, key: K, /) -> V:
46
- return (~self.__lazy)[key]
47
-
48
- def __iter__(self) -> Iterator[K]:
49
- yield from ~self.__lazy
50
-
51
- def __len__(self) -> int:
52
- return len(~self.__lazy)
53
-
54
- @property
55
- def is_set(self) -> bool:
56
- return self.__lazy.is_set