python-injection 0.13.1__py3-none-any.whl → 0.14.0__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.
- injection/__init__.pyi +12 -1
- injection/_core/common/asynchronous.py +5 -15
- injection/_core/common/lazy.py +37 -38
- injection/_core/injectables.py +3 -3
- injection/_core/module.py +74 -63
- injection/_core/scope.py +12 -17
- injection/utils.py +11 -26
- {python_injection-0.13.1.dist-info → python_injection-0.14.0.dist-info}/METADATA +1 -1
- {python_injection-0.13.1.dist-info → python_injection-0.14.0.dist-info}/RECORD +10 -10
- {python_injection-0.13.1.dist-info → python_injection-0.14.0.dist-info}/WHEEL +0 -0
injection/__init__.pyi
CHANGED
@@ -74,11 +74,19 @@ class Module:
|
|
74
74
|
def __contains__(self, cls: _InputType[Any], /) -> bool: ...
|
75
75
|
@property
|
76
76
|
def is_locked(self) -> bool: ...
|
77
|
-
def inject[**P, T](
|
77
|
+
def inject[**P, T](
|
78
|
+
self,
|
79
|
+
wrapped: Callable[P, T] = ...,
|
80
|
+
/,
|
81
|
+
*,
|
82
|
+
threadsafe: bool = ...,
|
83
|
+
) -> Any:
|
78
84
|
"""
|
79
85
|
Decorator applicable to a class or function. Inject function dependencies using
|
80
86
|
parameter type annotations. If applied to a class, the dependencies resolved
|
81
87
|
will be those of the `__init__` method.
|
88
|
+
|
89
|
+
With `threadsafe=True`, the injection logic is wrapped in a `threading.Lock`.
|
82
90
|
"""
|
83
91
|
|
84
92
|
def injectable[**P, T](
|
@@ -166,6 +174,7 @@ class Module:
|
|
166
174
|
self,
|
167
175
|
wrapped: Callable[P, T],
|
168
176
|
/,
|
177
|
+
threadsafe: bool = ...,
|
169
178
|
) -> Callable[P, T]: ...
|
170
179
|
async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
|
171
180
|
def find_instance[T](self, cls: _InputType[T]) -> T:
|
@@ -295,6 +304,8 @@ class Module:
|
|
295
304
|
Function to unlock the module by deleting cached instances of singletons.
|
296
305
|
"""
|
297
306
|
|
307
|
+
@contextmanager
|
308
|
+
def load_profile(self, *names: str) -> Iterator[None]: ...
|
298
309
|
async def all_ready(self) -> None: ...
|
299
310
|
def add_logger(self, logger: Logger) -> Self: ...
|
300
311
|
@classmethod
|
@@ -1,17 +1,7 @@
|
|
1
|
-
import asyncio
|
2
1
|
from abc import abstractmethod
|
3
|
-
from collections.abc import Awaitable, Callable,
|
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,
|
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) ->
|
46
|
-
|
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)
|
injection/_core/common/lazy.py
CHANGED
@@ -1,56 +1,55 @@
|
|
1
|
-
from collections.abc import Callable, Iterator
|
2
|
-
from
|
1
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
2
|
+
from functools import partial
|
3
3
|
|
4
|
-
from injection._core.common.
|
4
|
+
from injection._core.common.asynchronous import SimpleAwaitable
|
5
|
+
from injection._core.common.invertible import Invertible, SimpleInvertible
|
5
6
|
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
def lazy[T](factory: Callable[..., T]) -> Invertible[T]:
|
9
|
+
def cache() -> Iterator[T]:
|
10
|
+
nonlocal factory
|
11
|
+
value = factory()
|
12
|
+
del factory
|
9
13
|
|
10
|
-
|
11
|
-
|
14
|
+
while True:
|
15
|
+
yield value
|
12
16
|
|
13
|
-
|
14
|
-
|
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
|
17
|
+
getter = partial(next, cache())
|
18
|
+
return SimpleInvertible(getter)
|
22
19
|
|
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
20
|
|
30
|
-
|
31
|
-
|
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
|
32
26
|
|
33
|
-
|
34
|
-
|
27
|
+
while True:
|
28
|
+
yield value
|
35
29
|
|
30
|
+
getter = partial(anext, cache())
|
31
|
+
return SimpleAwaitable(getter)
|
36
32
|
|
37
|
-
class LazyMapping[K, V](Mapping[K, V]):
|
38
|
-
__slots__ = ("__lazy",)
|
39
33
|
|
40
|
-
|
34
|
+
class Lazy[T](Invertible[T]):
|
35
|
+
__slots__ = ("__invertible", "__is_set")
|
41
36
|
|
42
|
-
|
43
|
-
|
37
|
+
__invertible: Invertible[T]
|
38
|
+
__is_set: bool
|
44
39
|
|
45
|
-
def
|
46
|
-
|
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
|
47
46
|
|
48
|
-
|
49
|
-
|
47
|
+
self.__invertible = invertible
|
48
|
+
self.__is_set = False
|
50
49
|
|
51
|
-
def
|
52
|
-
return
|
50
|
+
def __invert__(self) -> T:
|
51
|
+
return ~self.__invertible
|
53
52
|
|
54
53
|
@property
|
55
54
|
def is_set(self) -> bool:
|
56
|
-
return self.
|
55
|
+
return self.__is_set
|
injection/_core/injectables.py
CHANGED
@@ -12,7 +12,7 @@ from typing import (
|
|
12
12
|
runtime_checkable,
|
13
13
|
)
|
14
14
|
|
15
|
-
from injection._core.common.asynchronous import Caller
|
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) ->
|
142
|
-
|
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]):
|
injection/_core/module.py
CHANGED
@@ -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,
|
@@ -12,7 +11,7 @@ from collections.abc import (
|
|
12
11
|
Iterator,
|
13
12
|
Mapping,
|
14
13
|
)
|
15
|
-
from contextlib import asynccontextmanager, contextmanager, suppress
|
14
|
+
from contextlib import asynccontextmanager, contextmanager, nullcontext, suppress
|
16
15
|
from dataclasses import dataclass, field
|
17
16
|
from enum import StrEnum
|
18
17
|
from functools import partialmethod, singledispatchmethod, update_wrapper
|
@@ -26,7 +25,7 @@ from inspect import (
|
|
26
25
|
)
|
27
26
|
from inspect import signature as inspect_signature
|
28
27
|
from logging import Logger, getLogger
|
29
|
-
from
|
28
|
+
from threading import Lock
|
30
29
|
from types import MethodType
|
31
30
|
from typing import (
|
32
31
|
Any,
|
@@ -50,7 +49,7 @@ from injection._core.common.asynchronous import (
|
|
50
49
|
from injection._core.common.event import Event, EventChannel, EventListener
|
51
50
|
from injection._core.common.invertible import Invertible, SimpleInvertible
|
52
51
|
from injection._core.common.key import new_short_key
|
53
|
-
from injection._core.common.lazy import Lazy,
|
52
|
+
from injection._core.common.lazy import Lazy, alazy, lazy
|
54
53
|
from injection._core.common.type import (
|
55
54
|
InputType,
|
56
55
|
TypeInfo,
|
@@ -296,6 +295,9 @@ class Locator(Broker):
|
|
296
295
|
|
297
296
|
async def all_ready(self) -> None:
|
298
297
|
for injectable in self.__injectables:
|
298
|
+
if injectable.is_locked:
|
299
|
+
continue
|
300
|
+
|
299
301
|
await injectable.aget_instance()
|
300
302
|
|
301
303
|
def add_listener(self, listener: EventListener) -> Self:
|
@@ -511,7 +513,7 @@ class Module(Broker, EventListener):
|
|
511
513
|
mode: Mode | ModeStr = Mode.get_default(),
|
512
514
|
) -> Any:
|
513
515
|
def decorator(wp: type[T]) -> type[T]:
|
514
|
-
lazy_instance =
|
516
|
+
lazy_instance = lazy(wp)
|
515
517
|
self.injectable(
|
516
518
|
lambda: ~lazy_instance,
|
517
519
|
ignore_type_hint=True,
|
@@ -541,13 +543,19 @@ class Module(Broker, EventListener):
|
|
541
543
|
)
|
542
544
|
return self
|
543
545
|
|
544
|
-
def inject[**P, T](
|
546
|
+
def inject[**P, T](
|
547
|
+
self,
|
548
|
+
wrapped: Callable[P, T] | None = None,
|
549
|
+
/,
|
550
|
+
*,
|
551
|
+
threadsafe: bool = False,
|
552
|
+
) -> Any:
|
545
553
|
def decorator(wp: Callable[P, T]) -> Callable[P, T]:
|
546
554
|
if isclass(wp):
|
547
|
-
wp.__init__ = self.inject(wp.__init__)
|
555
|
+
wp.__init__ = self.inject(wp.__init__, threadsafe=threadsafe)
|
548
556
|
return wp
|
549
557
|
|
550
|
-
return self.make_injected_function(wp)
|
558
|
+
return self.make_injected_function(wp, threadsafe)
|
551
559
|
|
552
560
|
return decorator(wrapped) if wrapped else decorator
|
553
561
|
|
@@ -556,6 +564,7 @@ class Module(Broker, EventListener):
|
|
556
564
|
self,
|
557
565
|
wrapped: Callable[P, T],
|
558
566
|
/,
|
567
|
+
threadsafe: bool = ...,
|
559
568
|
) -> SyncInjectedFunction[P, T]: ...
|
560
569
|
|
561
570
|
@overload
|
@@ -563,12 +572,13 @@ class Module(Broker, EventListener):
|
|
563
572
|
self,
|
564
573
|
wrapped: Callable[P, Awaitable[T]],
|
565
574
|
/,
|
575
|
+
threadsafe: bool = ...,
|
566
576
|
) -> AsyncInjectedFunction[P, T]: ...
|
567
577
|
|
568
|
-
def make_injected_function(self, wrapped,
|
569
|
-
metadata = InjectMetadata(wrapped)
|
578
|
+
def make_injected_function(self, wrapped, /, threadsafe=False): # type: ignore[no-untyped-def]
|
579
|
+
metadata = InjectMetadata(wrapped, threadsafe)
|
570
580
|
|
571
|
-
@metadata.
|
581
|
+
@metadata.task
|
572
582
|
def listen() -> None:
|
573
583
|
metadata.update(self)
|
574
584
|
self.add_listener(metadata)
|
@@ -646,12 +656,10 @@ class Module(Broker, EventListener):
|
|
646
656
|
|
647
657
|
def aget_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
|
648
658
|
if cache:
|
649
|
-
|
650
|
-
return asyncio.ensure_future(coroutine)
|
659
|
+
return alazy(lambda: self.aget_instance(cls, default))
|
651
660
|
|
652
661
|
function = self.make_injected_function(lambda instance=default: instance)
|
653
|
-
metadata = function.__inject_metadata__
|
654
|
-
metadata.set_owner(cls)
|
662
|
+
metadata = function.__inject_metadata__.set_owner(cls)
|
655
663
|
return SimpleAwaitable(metadata.acall)
|
656
664
|
|
657
665
|
@overload
|
@@ -674,11 +682,10 @@ class Module(Broker, EventListener):
|
|
674
682
|
|
675
683
|
def get_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
|
676
684
|
if cache:
|
677
|
-
return
|
685
|
+
return lazy(lambda: self.get_instance(cls, default))
|
678
686
|
|
679
687
|
function = self.make_injected_function(lambda instance=default: instance)
|
680
|
-
metadata = function.__inject_metadata__
|
681
|
-
metadata.set_owner(cls)
|
688
|
+
metadata = function.__inject_metadata__.set_owner(cls)
|
682
689
|
return SimpleInvertible(metadata.call)
|
683
690
|
|
684
691
|
def update[T](self, updater: Updater[T]) -> Self:
|
@@ -755,6 +762,23 @@ class Module(Broker, EventListener):
|
|
755
762
|
|
756
763
|
return self
|
757
764
|
|
765
|
+
def load_profile(self, *names: str) -> ContextManager[None]:
|
766
|
+
modules = tuple(self.from_name(name) for name in names)
|
767
|
+
|
768
|
+
for module in modules:
|
769
|
+
module.unlock()
|
770
|
+
|
771
|
+
self.unlock().init_modules(*modules)
|
772
|
+
|
773
|
+
del module, modules
|
774
|
+
|
775
|
+
@contextmanager
|
776
|
+
def cleaner() -> Iterator[None]:
|
777
|
+
yield
|
778
|
+
self.unlock().init_modules()
|
779
|
+
|
780
|
+
return cleaner()
|
781
|
+
|
758
782
|
async def all_ready(self) -> None:
|
759
783
|
for broker in self.__brokers:
|
760
784
|
await broker.all_ready()
|
@@ -832,10 +856,7 @@ InjectedFunction
|
|
832
856
|
|
833
857
|
@dataclass(repr=False, frozen=True, slots=True)
|
834
858
|
class Dependencies:
|
835
|
-
|
836
|
-
|
837
|
-
def __bool__(self) -> bool:
|
838
|
-
return bool(self.mapping)
|
859
|
+
lazy_mapping: Lazy[Mapping[str, Injectable[Any]]]
|
839
860
|
|
840
861
|
def __iter__(self) -> Iterator[tuple[str, Any]]:
|
841
862
|
for name, injectable in self.mapping.items():
|
@@ -849,10 +870,11 @@ class Dependencies:
|
|
849
870
|
|
850
871
|
@property
|
851
872
|
def are_resolved(self) -> bool:
|
852
|
-
|
853
|
-
return False
|
873
|
+
return self.lazy_mapping.is_set
|
854
874
|
|
855
|
-
|
875
|
+
@property
|
876
|
+
def mapping(self) -> Mapping[str, Injectable[Any]]:
|
877
|
+
return ~self.lazy_mapping
|
856
878
|
|
857
879
|
async def aget_arguments(self) -> dict[str, Any]:
|
858
880
|
return {key: value async for key, value in self}
|
@@ -861,12 +883,13 @@ class Dependencies:
|
|
861
883
|
return dict(self)
|
862
884
|
|
863
885
|
@classmethod
|
864
|
-
def
|
865
|
-
|
886
|
+
def from_iterable(cls, iterable: Iterable[tuple[str, Injectable[Any]]]) -> Self:
|
887
|
+
lazy_mapping = Lazy(lambda: dict(iterable))
|
888
|
+
return cls(lazy_mapping)
|
866
889
|
|
867
890
|
@classmethod
|
868
891
|
def empty(cls) -> Self:
|
869
|
-
return cls.
|
892
|
+
return cls.from_iterable(())
|
870
893
|
|
871
894
|
@classmethod
|
872
895
|
def resolve(
|
@@ -875,8 +898,8 @@ class Dependencies:
|
|
875
898
|
module: Module,
|
876
899
|
owner: type | None = None,
|
877
900
|
) -> Self:
|
878
|
-
|
879
|
-
return cls.
|
901
|
+
iterable = cls.__resolver(signature, module, owner)
|
902
|
+
return cls.from_iterable(iterable)
|
880
903
|
|
881
904
|
@classmethod
|
882
905
|
def __resolver(
|
@@ -916,22 +939,25 @@ class Arguments(NamedTuple):
|
|
916
939
|
class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
917
940
|
__slots__ = (
|
918
941
|
"__dependencies",
|
942
|
+
"__lock",
|
919
943
|
"__owner",
|
920
|
-
"__setup_queue",
|
921
944
|
"__signature",
|
945
|
+
"__tasks",
|
922
946
|
"__wrapped",
|
923
947
|
)
|
924
948
|
|
925
949
|
__dependencies: Dependencies
|
950
|
+
__lock: ContextManager[Any]
|
926
951
|
__owner: type | None
|
927
|
-
__setup_queue: Queue[Callable[..., Any]] | None
|
928
952
|
__signature: Signature
|
953
|
+
__tasks: deque[Callable[..., Any]]
|
929
954
|
__wrapped: Callable[P, T]
|
930
955
|
|
931
|
-
def __init__(self, wrapped: Callable[P, T],
|
956
|
+
def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
|
932
957
|
self.__dependencies = Dependencies.empty()
|
958
|
+
self.__lock = Lock() if threadsafe else nullcontext()
|
933
959
|
self.__owner = None
|
934
|
-
self.
|
960
|
+
self.__tasks = deque()
|
935
961
|
self.__wrapped = wrapped
|
936
962
|
|
937
963
|
@property
|
@@ -964,13 +990,17 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
964
990
|
return self.__bind(args, kwargs, additional_arguments)
|
965
991
|
|
966
992
|
async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
967
|
-
self.
|
968
|
-
|
993
|
+
with self.__lock:
|
994
|
+
self.__run_tasks()
|
995
|
+
arguments = await self.abind(args, kwargs)
|
996
|
+
|
969
997
|
return self.wrapped(*arguments.args, **arguments.kwargs)
|
970
998
|
|
971
999
|
def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
972
|
-
self.
|
973
|
-
|
1000
|
+
with self.__lock:
|
1001
|
+
self.__run_tasks()
|
1002
|
+
arguments = self.bind(args, kwargs)
|
1003
|
+
|
974
1004
|
return self.wrapped(*arguments.args, **arguments.kwargs)
|
975
1005
|
|
976
1006
|
def set_owner(self, owner: type) -> Self:
|
@@ -989,14 +1019,9 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
989
1019
|
self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
|
990
1020
|
return self
|
991
1021
|
|
992
|
-
def
|
1022
|
+
def task[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
|
993
1023
|
def decorator(wp: Callable[_P, _T]) -> Callable[_P, _T]:
|
994
|
-
|
995
|
-
|
996
|
-
if queue is None:
|
997
|
-
raise RuntimeError(f"`{self}` is already up.")
|
998
|
-
|
999
|
-
queue.put_nowait(wp)
|
1024
|
+
self.__tasks.append(wp)
|
1000
1025
|
return wp
|
1001
1026
|
|
1002
1027
|
return decorator(wrapped) if wrapped else decorator
|
@@ -1027,24 +1052,10 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
|
|
1027
1052
|
bound.arguments = bound.arguments | additional_arguments | bound.arguments
|
1028
1053
|
return Arguments(bound.args, bound.kwargs)
|
1029
1054
|
|
1030
|
-
def
|
1031
|
-
self.
|
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
|
-
|
1055
|
+
def __run_tasks(self) -> None:
|
1056
|
+
while tasks := self.__tasks:
|
1057
|
+
task = tasks.popleft()
|
1043
1058
|
task()
|
1044
|
-
queue.task_done()
|
1045
|
-
|
1046
|
-
queue.join()
|
1047
|
-
self.__close_setup_queue()
|
1048
1059
|
|
1049
1060
|
|
1050
1061
|
class InjectedFunction[**P, T](ABC):
|
injection/_core/scope.py
CHANGED
@@ -34,12 +34,12 @@ class _ScopeState:
|
|
34
34
|
default_factory=lambda: ContextVar(f"scope@{new_short_key()}"),
|
35
35
|
init=False,
|
36
36
|
)
|
37
|
-
|
38
|
-
|
37
|
+
__default: Scope | None = field(
|
38
|
+
default=None,
|
39
39
|
init=False,
|
40
40
|
)
|
41
|
-
|
42
|
-
|
41
|
+
__references: set[Scope] = field(
|
42
|
+
default_factory=set,
|
43
43
|
init=False,
|
44
44
|
)
|
45
45
|
|
@@ -47,8 +47,8 @@ class _ScopeState:
|
|
47
47
|
def active_scopes(self) -> Iterator[Scope]:
|
48
48
|
yield from self.__references
|
49
49
|
|
50
|
-
if
|
51
|
-
yield
|
50
|
+
if default := self.__default:
|
51
|
+
yield default
|
52
52
|
|
53
53
|
@contextmanager
|
54
54
|
def bind_contextual_scope(self, scope: Scope) -> Iterator[None]:
|
@@ -69,15 +69,15 @@ class _ScopeState:
|
|
69
69
|
"are defined on the same name."
|
70
70
|
)
|
71
71
|
|
72
|
-
self.
|
72
|
+
self.__default = scope
|
73
73
|
|
74
74
|
try:
|
75
75
|
yield
|
76
76
|
finally:
|
77
|
-
self.
|
77
|
+
self.__default = None
|
78
78
|
|
79
79
|
def get_scope(self) -> Scope | None:
|
80
|
-
return self.__context_var.get(self.
|
80
|
+
return self.__context_var.get(self.__default)
|
81
81
|
|
82
82
|
|
83
83
|
__SCOPES: Final[defaultdict[str, _ScopeState]] = defaultdict(_ScopeState)
|
@@ -125,11 +125,8 @@ def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
|
|
125
125
|
state.bind_shared_scope(scope) if shared else state.bind_contextual_scope(scope)
|
126
126
|
)
|
127
127
|
|
128
|
-
|
129
|
-
|
130
|
-
yield
|
131
|
-
finally:
|
132
|
-
scope.cache.clear()
|
128
|
+
with strategy:
|
129
|
+
yield
|
133
130
|
|
134
131
|
|
135
132
|
@runtime_checkable
|
@@ -201,9 +198,7 @@ class SyncScope(BaseScope[ExitStack]):
|
|
201
198
|
return self.delegate.__exit__(exc_type, exc_value, traceback)
|
202
199
|
|
203
200
|
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> NoReturn:
|
204
|
-
raise ScopeError(
|
205
|
-
"Synchronous scope doesn't support asynchronous context manager."
|
206
|
-
)
|
201
|
+
raise ScopeError("Synchronous scope doesn't support async context manager.")
|
207
202
|
|
208
203
|
def enter[T](self, context_manager: ContextManager[T]) -> T:
|
209
204
|
return self.delegate.enter_context(context_manager)
|
injection/utils.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
from collections.abc import Callable,
|
2
|
-
from contextlib import contextmanager
|
1
|
+
from collections.abc import Callable, Collection, Iterator
|
3
2
|
from importlib import import_module
|
4
3
|
from importlib.util import find_spec
|
5
4
|
from pkgutil import walk_packages
|
@@ -18,26 +17,12 @@ def load_profile(*names: str) -> ContextManager[None]:
|
|
18
17
|
A profile name is equivalent to an injection module name.
|
19
18
|
"""
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
for module in modules:
|
24
|
-
module.unlock()
|
25
|
-
|
26
|
-
target = mod().unlock().init_modules(*modules)
|
27
|
-
|
28
|
-
del module, modules
|
29
|
-
|
30
|
-
@contextmanager
|
31
|
-
def cleaner() -> Iterator[None]:
|
32
|
-
yield
|
33
|
-
target.unlock().init_modules()
|
34
|
-
|
35
|
-
return cleaner()
|
20
|
+
return mod().load_profile(*names)
|
36
21
|
|
37
22
|
|
38
23
|
def load_modules_with_keywords(
|
39
24
|
*packages: PythonModule | str,
|
40
|
-
keywords:
|
25
|
+
keywords: Collection[str] | None = None,
|
41
26
|
) -> dict[str, PythonModule]:
|
42
27
|
"""
|
43
28
|
Function to import modules from a Python package if one of the keywords is contained in the Python script.
|
@@ -54,16 +39,16 @@ def load_modules_with_keywords(
|
|
54
39
|
f"import {injection_package_name}",
|
55
40
|
)
|
56
41
|
|
57
|
-
b_keywords = tuple(keyword.encode() for keyword in keywords)
|
58
|
-
|
59
42
|
def predicate(module_name: str) -> bool:
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
43
|
+
spec = find_spec(module_name)
|
44
|
+
|
45
|
+
if spec and (module_path := spec.origin):
|
46
|
+
with open(module_path, "r") as file:
|
47
|
+
python_script = file.read()
|
64
48
|
|
65
|
-
|
66
|
-
|
49
|
+
return bool(python_script) and any(
|
50
|
+
keyword in python_script for keyword in keywords
|
51
|
+
)
|
67
52
|
|
68
53
|
return False
|
69
54
|
|
@@ -1,25 +1,25 @@
|
|
1
1
|
injection/__init__.py,sha256=X0vIAoN4MDlhR7YIkup1qHgqbOkwZ3PWgdSi7h-udaM,1049
|
2
|
-
injection/__init__.pyi,sha256=
|
2
|
+
injection/__init__.pyi,sha256=f3EhwOaJCnf37YZQdANvbUsAq360zn-62zFLNLJxvoQ,9916
|
3
3
|
injection/exceptions.py,sha256=T__732aXxWWUz0sKc39ySteyolCS5tpqQC0oCnzUF2E,917
|
4
4
|
injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
injection/utils.py,sha256=
|
5
|
+
injection/utils.py,sha256=s4wwe98owmgVIkxzAg-mu9yvFISWOzz1AvF7eF7-2xY,2752
|
6
6
|
injection/_core/__init__.py,sha256=XERocCxCZBxPGIaOR37yeiQOZyvjHQ6a4rgRmlkUSuU,1367
|
7
7
|
injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
|
8
8
|
injection/_core/hook.py,sha256=Qv505pr3kjOE6UitftlLh9JKX9OCNqZBRtHbFha1gqM,3130
|
9
|
-
injection/_core/injectables.py,sha256=
|
10
|
-
injection/_core/module.py,sha256=
|
11
|
-
injection/_core/scope.py,sha256=
|
9
|
+
injection/_core/injectables.py,sha256=GIumNp0TXf8Voxe1sCPhcqq2gyw4E_hl7I45IJ_tyHE,4512
|
10
|
+
injection/_core/module.py,sha256=kotXKuOFv8Gt31J1R8nSgVnu-83_MhIV4B5Etu8_kzo,30968
|
11
|
+
injection/_core/scope.py,sha256=uZps_kQsXDL9tXRzF1ECK8ua3cdEXiIAxrrhPt8JNqg,5461
|
12
12
|
injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
injection/_core/common/asynchronous.py,sha256=
|
13
|
+
injection/_core/common/asynchronous.py,sha256=9bQDVRE6eqo9K0d5H9RzyFalf0WGoGP7cDrKDGbvZPI,1500
|
14
14
|
injection/_core/common/event.py,sha256=XjzV8gxtGlGvzZs_ykvoC60qmdpd3RN08Eiqz5QUwes,1236
|
15
15
|
injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
|
16
16
|
injection/_core/common/key.py,sha256=ghkZD-Y8Moz6SEPNgMh3xgsZUjDVq-XYAmXaCu5VuCA,80
|
17
|
-
injection/_core/common/lazy.py,sha256=
|
17
|
+
injection/_core/common/lazy.py,sha256=6xh5h0lmaNvl32V0WoX4VCTsNJ3zUJdWVqpLJ_YeIIU,1363
|
18
18
|
injection/_core/common/type.py,sha256=QbBBhJp7i1p6gLzWX0TgofvfG7yDH-gHfEQcssVZeHo,2186
|
19
19
|
injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
20
|
injection/integrations/fastapi.py,sha256=YHSs85_3m6TUVtOwUcV157b3UZJQIw_aXWAg199a-YE,594
|
21
21
|
injection/testing/__init__.py,sha256=ALcKuDYNdslmpgqotZzSWrXAW0kNNFUs9nzfO1ZgIGc,783
|
22
22
|
injection/testing/__init__.pyi,sha256=6rv5NOYHEaiKMd82E1IIc8lFlLV9ttY57DLiMqGTXt8,482
|
23
|
-
python_injection-0.
|
24
|
-
python_injection-0.
|
25
|
-
python_injection-0.
|
23
|
+
python_injection-0.14.0.dist-info/METADATA,sha256=A25sC1rCj-HHSw-Sf57IIyHLhiPuDXUt53BdLBpKPnY,2996
|
24
|
+
python_injection-0.14.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
+
python_injection-0.14.0.dist-info/RECORD,,
|
File without changes
|