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.
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