python-injection 0.13.0__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.
- {python_injection-0.13.0 → python_injection-0.13.2}/PKG-INFO +1 -1
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/__init__.pyi +7 -1
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/asynchronous.py +5 -15
- python_injection-0.13.2/injection/_core/common/lazy.py +55 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/descriptors.py +8 -2
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/injectables.py +3 -3
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/module.py +41 -63
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/scope.py +6 -14
- python_injection-0.13.2/injection/integrations/fastapi.py +27 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/pyproject.toml +10 -10
- python_injection-0.13.0/injection/_core/common/lazy.py +0 -56
- python_injection-0.13.0/injection/integrations/fastapi.py +0 -38
- {python_injection-0.13.0 → python_injection-0.13.2}/.gitignore +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/README.md +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/__init__.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/__init__.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/event.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/invertible.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/key.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/type.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/hook.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/exceptions.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/integrations/__init__.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/py.typed +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/testing/__init__.py +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/testing/__init__.pyi +0 -0
- {python_injection-0.13.0 → python_injection-0.13.2}/injection/utils.py +0 -0
@@ -46,7 +46,13 @@ class Injectable[T](Protocol):
|
|
46
46
|
def get_instance(self) -> T: ...
|
47
47
|
|
48
48
|
class LazyInstance[T]:
|
49
|
-
def __init__(
|
49
|
+
def __init__(
|
50
|
+
self,
|
51
|
+
cls: _InputType[T],
|
52
|
+
/,
|
53
|
+
default: T = ...,
|
54
|
+
module: Module = ...,
|
55
|
+
) -> None: ...
|
50
56
|
@overload
|
51
57
|
def __get__(self, instance: object, owner: type | None = ...) -> T: ...
|
52
58
|
@overload
|
@@ -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)
|
@@ -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
|
@@ -10,9 +10,15 @@ class LazyInstance[T]:
|
|
10
10
|
|
11
11
|
__value: Invertible[T]
|
12
12
|
|
13
|
-
def __init__(
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
cls: InputType[T],
|
16
|
+
/,
|
17
|
+
default: T = NotImplemented,
|
18
|
+
module: Module | None = None,
|
19
|
+
) -> None:
|
14
20
|
module = module or mod()
|
15
|
-
self.__value = module.get_lazy_instance(cls, default
|
21
|
+
self.__value = module.get_lazy_instance(cls, default)
|
16
22
|
|
17
23
|
def __get__(
|
18
24
|
self,
|
@@ -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]):
|
@@ -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,
|
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:
|
@@ -445,7 +446,7 @@ class Module(Broker, EventListener):
|
|
445
446
|
mode: Mode | ModeStr = Mode.get_default(),
|
446
447
|
) -> Any:
|
447
448
|
def decorator(
|
448
|
-
|
449
|
+
wrapped: Callable[P, T]
|
449
450
|
| Callable[P, Awaitable[T]]
|
450
451
|
| Callable[P, Iterator[T]]
|
451
452
|
| Callable[P, AsyncIterator[T]],
|
@@ -463,19 +464,19 @@ class Module(Broker, EventListener):
|
|
463
464
|
| Callable[P, AsyncContextManager[T]]
|
464
465
|
)
|
465
466
|
|
466
|
-
if isasyncgenfunction(
|
467
|
-
hint = get_yield_hint(
|
467
|
+
if isasyncgenfunction(wrapped):
|
468
|
+
hint = get_yield_hint(wrapped)
|
468
469
|
injectable_class = AsyncCMScopedInjectable
|
469
|
-
wrapper = asynccontextmanager(
|
470
|
+
wrapper = asynccontextmanager(wrapped)
|
470
471
|
|
471
|
-
elif isgeneratorfunction(
|
472
|
-
hint = get_yield_hint(
|
472
|
+
elif isgeneratorfunction(wrapped):
|
473
|
+
hint = get_yield_hint(wrapped)
|
473
474
|
injectable_class = CMScopedInjectable
|
474
|
-
wrapper = contextmanager(
|
475
|
+
wrapper = contextmanager(wrapped)
|
475
476
|
|
476
477
|
else:
|
477
478
|
injectable_class = SimpleScopedInjectable
|
478
|
-
hint = wrapper =
|
479
|
+
hint = wrapper = wrapped # type: ignore[assignment]
|
479
480
|
|
480
481
|
hints = on if hint is None else (hint, on)
|
481
482
|
self.injectable(
|
@@ -486,7 +487,7 @@ class Module(Broker, EventListener):
|
|
486
487
|
on=hints,
|
487
488
|
mode=mode,
|
488
489
|
)
|
489
|
-
return
|
490
|
+
return wrapped
|
490
491
|
|
491
492
|
return decorator
|
492
493
|
|
@@ -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 =
|
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.
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
853
|
-
return False
|
847
|
+
return self.lazy_mapping.is_set
|
854
848
|
|
855
|
-
|
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
|
865
|
-
|
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.
|
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
|
-
|
879
|
-
return cls.
|
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.
|
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.
|
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.
|
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
|
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
|
-
|
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
|
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
|
-
|
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):
|
@@ -3,14 +3,7 @@ from __future__ import annotations
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from collections import defaultdict
|
5
5
|
from collections.abc import AsyncIterator, Iterator, MutableMapping
|
6
|
-
from contextlib import
|
7
|
-
AsyncContextDecorator,
|
8
|
-
AsyncExitStack,
|
9
|
-
ContextDecorator,
|
10
|
-
ExitStack,
|
11
|
-
asynccontextmanager,
|
12
|
-
contextmanager,
|
13
|
-
)
|
6
|
+
from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager
|
14
7
|
from contextvars import ContextVar
|
15
8
|
from dataclasses import dataclass, field
|
16
9
|
from types import TracebackType
|
@@ -19,6 +12,7 @@ from typing import (
|
|
19
12
|
AsyncContextManager,
|
20
13
|
ContextManager,
|
21
14
|
Final,
|
15
|
+
NoReturn,
|
22
16
|
Protocol,
|
23
17
|
Self,
|
24
18
|
runtime_checkable,
|
@@ -163,7 +157,7 @@ class BaseScope[T](Scope, ABC):
|
|
163
157
|
)
|
164
158
|
|
165
159
|
|
166
|
-
class AsyncScope(
|
160
|
+
class AsyncScope(BaseScope[AsyncExitStack]):
|
167
161
|
__slots__ = ()
|
168
162
|
|
169
163
|
def __init__(self) -> None:
|
@@ -188,7 +182,7 @@ class AsyncScope(AsyncContextDecorator, BaseScope[AsyncExitStack]):
|
|
188
182
|
return self.delegate.enter_context(context_manager)
|
189
183
|
|
190
184
|
|
191
|
-
class SyncScope(
|
185
|
+
class SyncScope(BaseScope[ExitStack]):
|
192
186
|
__slots__ = ()
|
193
187
|
|
194
188
|
def __init__(self) -> None:
|
@@ -206,10 +200,8 @@ class SyncScope(ContextDecorator, BaseScope[ExitStack]):
|
|
206
200
|
) -> Any:
|
207
201
|
return self.delegate.__exit__(exc_type, exc_value, traceback)
|
208
202
|
|
209
|
-
async def aenter[T](self, context_manager: AsyncContextManager[T]) ->
|
210
|
-
raise ScopeError(
|
211
|
-
"Synchronous scope doesn't support asynchronous context manager."
|
212
|
-
)
|
203
|
+
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> NoReturn:
|
204
|
+
raise ScopeError("Synchronous scope doesn't support async context manager.")
|
213
205
|
|
214
206
|
def enter[T](self, context_manager: ContextManager[T]) -> T:
|
215
207
|
return self.delegate.enter_context(context_manager)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from types import GenericAlias
|
2
|
+
from typing import Any, TypeAliasType
|
3
|
+
|
4
|
+
from fastapi import Depends
|
5
|
+
|
6
|
+
from injection import Module, mod
|
7
|
+
|
8
|
+
__all__ = ("Inject",)
|
9
|
+
|
10
|
+
|
11
|
+
def Inject[T]( # noqa: N802
|
12
|
+
cls: type[T] | TypeAliasType | GenericAlias,
|
13
|
+
/,
|
14
|
+
default: T = NotImplemented,
|
15
|
+
module: Module | None = None,
|
16
|
+
) -> Any:
|
17
|
+
"""
|
18
|
+
Declare a FastAPI dependency with `python-injection`.
|
19
|
+
"""
|
20
|
+
|
21
|
+
module = module or mod()
|
22
|
+
lazy_instance = module.aget_lazy_instance(cls, default)
|
23
|
+
|
24
|
+
async def getter() -> T:
|
25
|
+
return await lazy_instance
|
26
|
+
|
27
|
+
return Depends(getter, use_cache=False)
|
@@ -24,7 +24,7 @@ test = [
|
|
24
24
|
|
25
25
|
[project]
|
26
26
|
name = "python-injection"
|
27
|
-
version = "0.13.
|
27
|
+
version = "0.13.2"
|
28
28
|
description = "Fast and easy dependency injection framework."
|
29
29
|
license = { text = "MIT" }
|
30
30
|
readme = "README.md"
|
@@ -61,6 +61,15 @@ exclude_lines = [
|
|
61
61
|
[tool.coverage.run]
|
62
62
|
omit = ["bench.py"]
|
63
63
|
|
64
|
+
[tool.hatch.build]
|
65
|
+
skip-excluded-dirs = true
|
66
|
+
|
67
|
+
[tool.hatch.build.targets.sdist]
|
68
|
+
include = ["injection"]
|
69
|
+
|
70
|
+
[tool.hatch.build.targets.wheel]
|
71
|
+
packages = ["injection"]
|
72
|
+
|
64
73
|
[tool.mypy]
|
65
74
|
check_untyped_defs = true
|
66
75
|
disallow_any_generics = true
|
@@ -77,15 +86,6 @@ plugins = ["pydantic.mypy"]
|
|
77
86
|
warn_redundant_casts = true
|
78
87
|
warn_unused_ignores = true
|
79
88
|
|
80
|
-
[tool.hatch.build]
|
81
|
-
skip-excluded-dirs = true
|
82
|
-
|
83
|
-
[tool.hatch.build.targets.sdist]
|
84
|
-
include = ["injection"]
|
85
|
-
|
86
|
-
[tool.hatch.build.targets.wheel]
|
87
|
-
packages = ["injection"]
|
88
|
-
|
89
89
|
[tool.pydantic-mypy]
|
90
90
|
init_forbid_extra = true
|
91
91
|
init_typed = true
|
@@ -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
|
@@ -1,38 +0,0 @@
|
|
1
|
-
from collections.abc import Awaitable
|
2
|
-
from types import GenericAlias
|
3
|
-
from typing import Any, TypeAliasType
|
4
|
-
|
5
|
-
from fastapi import Depends
|
6
|
-
|
7
|
-
from injection import Module, mod
|
8
|
-
|
9
|
-
__all__ = ("Inject",)
|
10
|
-
|
11
|
-
|
12
|
-
def Inject[T]( # noqa: N802
|
13
|
-
cls: type[T] | TypeAliasType | GenericAlias,
|
14
|
-
/,
|
15
|
-
module: Module | None = None,
|
16
|
-
) -> Any:
|
17
|
-
"""
|
18
|
-
Declare a FastAPI dependency with `python-injection`.
|
19
|
-
"""
|
20
|
-
|
21
|
-
dependency: InjectionDependency[T] = InjectionDependency(cls, module or mod())
|
22
|
-
return Depends(dependency, use_cache=False)
|
23
|
-
|
24
|
-
|
25
|
-
class InjectionDependency[T]:
|
26
|
-
__slots__ = ("__awaitable",)
|
27
|
-
|
28
|
-
__awaitable: Awaitable[T]
|
29
|
-
|
30
|
-
def __init__(
|
31
|
-
self,
|
32
|
-
cls: type[T] | TypeAliasType | GenericAlias,
|
33
|
-
module: Module,
|
34
|
-
) -> None:
|
35
|
-
self.__awaitable = module.aget_lazy_instance(cls, default=NotImplemented)
|
36
|
-
|
37
|
-
async def __call__(self) -> T:
|
38
|
-
return await self.__awaitable
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|