python-injection 0.13.0__tar.gz → 0.13.2__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. {python_injection-0.13.0 → python_injection-0.13.2}/PKG-INFO +1 -1
  2. {python_injection-0.13.0 → python_injection-0.13.2}/injection/__init__.pyi +7 -1
  3. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/asynchronous.py +5 -15
  4. python_injection-0.13.2/injection/_core/common/lazy.py +55 -0
  5. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/descriptors.py +8 -2
  6. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/injectables.py +3 -3
  7. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/module.py +41 -63
  8. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/scope.py +6 -14
  9. python_injection-0.13.2/injection/integrations/fastapi.py +27 -0
  10. {python_injection-0.13.0 → python_injection-0.13.2}/pyproject.toml +10 -10
  11. python_injection-0.13.0/injection/_core/common/lazy.py +0 -56
  12. python_injection-0.13.0/injection/integrations/fastapi.py +0 -38
  13. {python_injection-0.13.0 → python_injection-0.13.2}/.gitignore +0 -0
  14. {python_injection-0.13.0 → python_injection-0.13.2}/README.md +0 -0
  15. {python_injection-0.13.0 → python_injection-0.13.2}/injection/__init__.py +0 -0
  16. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/__init__.py +0 -0
  17. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/__init__.py +0 -0
  18. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/event.py +0 -0
  19. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/invertible.py +0 -0
  20. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/key.py +0 -0
  21. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/common/type.py +0 -0
  22. {python_injection-0.13.0 → python_injection-0.13.2}/injection/_core/hook.py +0 -0
  23. {python_injection-0.13.0 → python_injection-0.13.2}/injection/exceptions.py +0 -0
  24. {python_injection-0.13.0 → python_injection-0.13.2}/injection/integrations/__init__.py +0 -0
  25. {python_injection-0.13.0 → python_injection-0.13.2}/injection/py.typed +0 -0
  26. {python_injection-0.13.0 → python_injection-0.13.2}/injection/testing/__init__.py +0 -0
  27. {python_injection-0.13.0 → python_injection-0.13.2}/injection/testing/__init__.pyi +0 -0
  28. {python_injection-0.13.0 → 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.0
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
@@ -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__(self, cls: _InputType[T], module: Module = ...) -> None: ...
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, 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
@@ -10,9 +10,15 @@ class LazyInstance[T]:
10
10
 
11
11
  __value: Invertible[T]
12
12
 
13
- def __init__(self, cls: InputType[T], module: Module | None = None) -> None:
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=NotImplemented)
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, 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:
@@ -445,7 +446,7 @@ class Module(Broker, EventListener):
445
446
  mode: Mode | ModeStr = Mode.get_default(),
446
447
  ) -> Any:
447
448
  def decorator(
448
- wp: Callable[P, T]
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(wp):
467
- hint = get_yield_hint(wp)
467
+ if isasyncgenfunction(wrapped):
468
+ hint = get_yield_hint(wrapped)
468
469
  injectable_class = AsyncCMScopedInjectable
469
- wrapper = asynccontextmanager(wp)
470
+ wrapper = asynccontextmanager(wrapped)
470
471
 
471
- elif isgeneratorfunction(wp):
472
- hint = get_yield_hint(wp)
472
+ elif isgeneratorfunction(wrapped):
473
+ hint = get_yield_hint(wrapped)
473
474
  injectable_class = CMScopedInjectable
474
- wrapper = contextmanager(wp)
475
+ wrapper = contextmanager(wrapped)
475
476
 
476
477
  else:
477
478
  injectable_class = SimpleScopedInjectable
478
- hint = wrapper = wp # type: ignore[assignment]
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 wp
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 = 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):
@@ -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(AsyncContextDecorator, BaseScope[AsyncExitStack]):
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(ContextDecorator, BaseScope[ExitStack]):
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]) -> 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.0"
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