python-injection 0.11.0__py3-none-any.whl → 0.12.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
injection/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from ._core.descriptors import LazyInstance
2
- from ._core.module import Injectable, Mode, Module, Priority
2
+ from ._core.injectables import Injectable
3
+ from ._core.module import Mode, Module, Priority, mod
3
4
 
4
5
  __all__ = (
5
6
  "Injectable",
@@ -7,6 +8,9 @@ __all__ = (
7
8
  "Mode",
8
9
  "Module",
9
10
  "Priority",
11
+ "afind_instance",
12
+ "aget_instance",
13
+ "aget_lazy_instance",
10
14
  "constant",
11
15
  "find_instance",
12
16
  "get_instance",
@@ -19,14 +23,9 @@ __all__ = (
19
23
  "singleton",
20
24
  )
21
25
 
22
-
23
- def mod(name: str | None = None, /) -> Module:
24
- if name is None:
25
- return Module.default()
26
-
27
- return Module.from_name(name)
28
-
29
-
26
+ afind_instance = mod().afind_instance
27
+ aget_instance = mod().aget_instance
28
+ aget_lazy_instance = mod().aget_lazy_instance
30
29
  constant = mod().constant
31
30
  find_instance = mod().find_instance
32
31
  get_instance = mod().get_instance
injection/__init__.pyi CHANGED
@@ -1,5 +1,5 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import Callable
2
+ from collections.abc import Awaitable, Callable
3
3
  from contextlib import ContextDecorator
4
4
  from enum import Enum
5
5
  from logging import Logger
@@ -21,6 +21,9 @@ from ._core.module import ModeStr, PriorityStr
21
21
 
22
22
  _: Module = ...
23
23
 
24
+ afind_instance = _.afind_instance
25
+ aget_instance = _.aget_instance
26
+ aget_lazy_instance = _.aget_lazy_instance
24
27
  constant = _.constant
25
28
  find_instance = _.find_instance
26
29
  get_instance = _.get_instance
@@ -53,59 +56,59 @@ class Module:
53
56
  def __contains__(self, cls: _InputType[Any], /) -> bool: ...
54
57
  @property
55
58
  def is_locked(self) -> bool: ...
56
- def inject[**P, T](self, wrapped: Callable[P, T] = ..., /): # type: ignore[no-untyped-def]
59
+ def inject[**P, T](self, wrapped: Callable[P, T] = ..., /) -> Any:
57
60
  """
58
61
  Decorator applicable to a class or function. Inject function dependencies using
59
62
  parameter type annotations. If applied to a class, the dependencies resolved
60
63
  will be those of the `__init__` method.
61
64
  """
62
65
 
63
- def injectable[**P, T]( # type: ignore[no-untyped-def]
66
+ def injectable[**P, T](
64
67
  self,
65
- wrapped: Callable[P, T] = ...,
68
+ wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
66
69
  /,
67
70
  *,
68
71
  cls: _InjectableFactory[T] = ...,
69
72
  inject: bool = ...,
70
73
  on: _TypeInfo[T] = ...,
71
74
  mode: Mode | ModeStr = ...,
72
- ):
75
+ ) -> Any:
73
76
  """
74
77
  Decorator applicable to a class or function. It is used to indicate how the
75
78
  injectable will be constructed. At injection time, a new instance will be
76
79
  injected each time.
77
80
  """
78
81
 
79
- def singleton[**P, T]( # type: ignore[no-untyped-def]
82
+ def singleton[**P, T](
80
83
  self,
81
- wrapped: Callable[P, T] = ...,
84
+ wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
82
85
  /,
83
86
  *,
84
87
  inject: bool = ...,
85
88
  on: _TypeInfo[T] = ...,
86
89
  mode: Mode | ModeStr = ...,
87
- ):
90
+ ) -> Any:
88
91
  """
89
92
  Decorator applicable to a class or function. It is used to indicate how the
90
93
  singleton will be constructed. At injection time, the injected instance will
91
94
  always be the same.
92
95
  """
93
96
 
94
- def should_be_injectable[T](self, wrapped: type[T] = ..., /): # type: ignore[no-untyped-def]
97
+ def should_be_injectable[T](self, wrapped: type[T] = ..., /) -> Any:
95
98
  """
96
99
  Decorator applicable to a class. It is used to specify whether an injectable
97
100
  should be registered. Raise an exception at injection time if the class isn't
98
101
  registered.
99
102
  """
100
103
 
101
- def constant[T]( # type: ignore[no-untyped-def]
104
+ def constant[T](
102
105
  self,
103
106
  wrapped: type[T] = ...,
104
107
  /,
105
108
  *,
106
109
  on: _TypeInfo[T] = ...,
107
110
  mode: Mode | ModeStr = ...,
108
- ):
111
+ ) -> Any:
109
112
  """
110
113
  Decorator applicable to a class or function. It is used to indicate how the
111
114
  constant is constructed. At injection time, the injected instance will always
@@ -131,12 +134,25 @@ class Module:
131
134
  wrapped: Callable[P, T],
132
135
  /,
133
136
  ) -> Callable[P, T]: ...
137
+ async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
134
138
  def find_instance[T](self, cls: _InputType[T]) -> T:
135
139
  """
136
140
  Function used to retrieve an instance associated with the type passed in
137
141
  parameter or an exception will be raised.
138
142
  """
139
143
 
144
+ @overload
145
+ async def aget_instance[T, Default](
146
+ self,
147
+ cls: _InputType[T],
148
+ default: Default,
149
+ ) -> T | Default: ...
150
+ @overload
151
+ async def aget_instance[T](
152
+ self,
153
+ cls: _InputType[T],
154
+ default: None = ...,
155
+ ) -> T | None: ...
140
156
  @overload
141
157
  def get_instance[T, Default](
142
158
  self,
@@ -149,12 +165,28 @@ class Module:
149
165
  """
150
166
 
151
167
  @overload
152
- def get_instance[T, _](
168
+ def get_instance[T](
153
169
  self,
154
170
  cls: _InputType[T],
155
171
  default: None = ...,
156
172
  ) -> T | None: ...
157
173
  @overload
174
+ def aget_lazy_instance[T, Default](
175
+ self,
176
+ cls: _InputType[T],
177
+ default: Default,
178
+ *,
179
+ cache: bool = ...,
180
+ ) -> Awaitable[T | Default]: ...
181
+ @overload
182
+ def aget_lazy_instance[T](
183
+ self,
184
+ cls: _InputType[T],
185
+ default: None = ...,
186
+ *,
187
+ cache: bool = ...,
188
+ ) -> Awaitable[T | None]: ...
189
+ @overload
158
190
  def get_lazy_instance[T, Default](
159
191
  self,
160
192
  cls: _InputType[T],
@@ -172,7 +204,7 @@ class Module:
172
204
  """
173
205
 
174
206
  @overload
175
- def get_lazy_instance[T, _](
207
+ def get_lazy_instance[T](
176
208
  self,
177
209
  cls: _InputType[T],
178
210
  default: None = ...,
@@ -229,6 +261,7 @@ class Module:
229
261
  Function to unlock the module by deleting cached instances of singletons.
230
262
  """
231
263
 
264
+ async def all_ready(self) -> None: ...
232
265
  def add_logger(self, logger: Logger) -> Self: ...
233
266
  @classmethod
234
267
  def from_name(cls, name: str) -> Module:
@@ -253,6 +286,8 @@ class Injectable[T](Protocol):
253
286
  def is_locked(self) -> bool: ...
254
287
  def unlock(self) -> None: ...
255
288
  @abstractmethod
289
+ async def aget_instance(self) -> T: ...
290
+ @abstractmethod
256
291
  def get_instance(self) -> T: ...
257
292
 
258
293
  @final
@@ -49,5 +49,5 @@ def standardize_input_classes[T](
49
49
  @Locator.static_hooks.on_update
50
50
  def standardize_classes[T](*_: Any, **__: Any) -> HookGenerator[Updater[T]]:
51
51
  updater = yield
52
- updater.classes = set(standardize_types(*updater.classes))
52
+ updater.classes = frozenset(standardize_types(*updater.classes))
53
53
  return updater
@@ -0,0 +1,49 @@
1
+ from abc import abstractmethod
2
+ from collections.abc import Awaitable, Callable, Generator
3
+ from dataclasses import dataclass
4
+ from typing import Any, NoReturn, Protocol, runtime_checkable
5
+
6
+
7
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
8
+ class SimpleAwaitable[T](Awaitable[T]):
9
+ callable: Callable[..., Awaitable[T]]
10
+
11
+ def __await__(self) -> Generator[Any, Any, T]:
12
+ return self.callable().__await__()
13
+
14
+
15
+ @runtime_checkable
16
+ class Caller[**P, T](Protocol):
17
+ __slots__ = ()
18
+
19
+ @abstractmethod
20
+ async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
21
+ raise NotImplementedError
22
+
23
+ @abstractmethod
24
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
25
+ raise NotImplementedError
26
+
27
+
28
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
29
+ class AsyncCaller[**P, T](Caller[P, T]):
30
+ callable: Callable[P, Awaitable[T]]
31
+
32
+ async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
33
+ return await self.callable(*args, **kwargs)
34
+
35
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> NoReturn:
36
+ raise RuntimeError(
37
+ "Synchronous call isn't supported for an asynchronous Callable."
38
+ )
39
+
40
+
41
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
42
+ class SyncCaller[**P, T](Caller[P, T]):
43
+ callable: Callable[P, T]
44
+
45
+ async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
46
+ return self.callable(*args, **kwargs)
47
+
48
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
49
+ return self.callable(*args, **kwargs)
@@ -1,7 +1,7 @@
1
1
  from abc import abstractmethod
2
2
  from collections.abc import Callable
3
3
  from dataclasses import dataclass
4
- from typing import Protocol, override, runtime_checkable
4
+ from typing import Protocol, runtime_checkable
5
5
 
6
6
 
7
7
  @runtime_checkable
@@ -13,8 +13,7 @@ class Invertible[T](Protocol):
13
13
 
14
14
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
15
15
  class SimpleInvertible[T](Invertible[T]):
16
- getter: Callable[..., T]
16
+ callable: Callable[..., T]
17
17
 
18
- @override
19
18
  def __invert__(self) -> T:
20
- return self.getter()
19
+ return self.callable()
@@ -1,6 +1,5 @@
1
1
  from collections.abc import Callable, Iterator, Mapping
2
2
  from types import MappingProxyType
3
- from typing import override
4
3
 
5
4
  from injection._core.common.invertible import Invertible
6
5
 
@@ -14,7 +13,6 @@ class Lazy[T](Invertible[T]):
14
13
  def __init__(self, factory: Callable[..., T]) -> None:
15
14
  self.__setup_cache(factory)
16
15
 
17
- @override
18
16
  def __invert__(self) -> T:
19
17
  return next(self.__iterator)
20
18
 
@@ -44,15 +42,12 @@ class LazyMapping[K, V](Mapping[K, V]):
44
42
  def __init__(self, iterator: Iterator[tuple[K, V]]) -> None:
45
43
  self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
46
44
 
47
- @override
48
45
  def __getitem__(self, key: K, /) -> V:
49
46
  return (~self.__lazy)[key]
50
47
 
51
- @override
52
48
  def __iter__(self) -> Iterator[K]:
53
49
  yield from ~self.__lazy
54
50
 
55
- @override
56
51
  def __len__(self) -> int:
57
52
  return len(~self.__lazy)
58
53
 
@@ -1,5 +1,5 @@
1
- from collections.abc import Awaitable, Callable, Iterable, Iterator
2
- from inspect import iscoroutinefunction, isfunction
1
+ from collections.abc import Callable, Iterable, Iterator
2
+ from inspect import isfunction
3
3
  from types import GenericAlias, UnionType
4
4
  from typing import (
5
5
  Annotated,
@@ -24,9 +24,6 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
24
24
  inner_args = arg
25
25
 
26
26
  elif isfunction(arg) and (return_type := get_type_hints(arg).get("return")):
27
- if iscoroutinefunction(arg):
28
- return_type = Awaitable[return_type] # type: ignore[valid-type]
29
-
30
27
  inner_args = (return_type,)
31
28
 
32
29
  else:
@@ -2,7 +2,7 @@ from typing import Self
2
2
 
3
3
  from injection._core.common.invertible import Invertible
4
4
  from injection._core.common.type import InputType
5
- from injection._core.module import Module
5
+ from injection._core.module import Module, mod
6
6
 
7
7
 
8
8
  class LazyInstance[T]:
@@ -11,8 +11,8 @@ class LazyInstance[T]:
11
11
  __value: Invertible[T]
12
12
 
13
13
  def __init__(self, cls: InputType[T], module: Module | None = None) -> None:
14
- module = module or Module.default()
15
- self.__value = module.get_lazy_instance(cls, default=NotImplemented) # type: ignore[assignment]
14
+ module = module or mod()
15
+ self.__value = module.get_lazy_instance(cls, default=NotImplemented)
16
16
 
17
17
  def __get__(
18
18
  self,
injection/_core/hook.py CHANGED
@@ -2,12 +2,13 @@ import itertools
2
2
  from collections.abc import Callable, Generator, Iterator
3
3
  from dataclasses import dataclass, field
4
4
  from inspect import isclass, isgeneratorfunction
5
- from typing import Any, Self
5
+ from typing import Any, Self, TypeGuard
6
6
 
7
7
  from injection.exceptions import HookError
8
8
 
9
9
  type HookGenerator[T] = Generator[None, T, T]
10
- type HookFunction[**P, T] = Callable[P, T | HookGenerator[T]]
10
+ type HookGeneratorFunction[**P, T] = Callable[P, HookGenerator[T]]
11
+ type HookFunction[**P, T] = Callable[P, T] | HookGeneratorFunction[P, T]
11
12
 
12
13
 
13
14
  @dataclass(eq=False, frozen=True, slots=True)
@@ -18,12 +19,12 @@ class Hook[**P, T]:
18
19
  repr=False,
19
20
  )
20
21
 
21
- def __call__( # type: ignore[no-untyped-def]
22
+ def __call__(
22
23
  self,
23
24
  wrapped: HookFunction[P, T] | type[HookFunction[P, T]] | None = None,
24
25
  /,
25
- ):
26
- def decorator(wp): # type: ignore[no-untyped-def]
26
+ ) -> Any:
27
+ def decorator(wp: Any) -> Any:
27
28
  self.add(wp() if isclass(wp) else wp)
28
29
  return wp
29
30
 
@@ -48,11 +49,11 @@ class Hook[**P, T]:
48
49
  handler: Callable[P, T],
49
50
  function: HookFunction[P, T],
50
51
  ) -> Callable[P, T]:
51
- if not cls.__is_generator_function(function):
52
+ if not cls.__is_hook_generator_function(function):
52
53
  return function # type: ignore[return-value]
53
54
 
54
55
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
55
- hook: HookGenerator[T] = function(*args, **kwargs) # type: ignore[assignment]
56
+ hook: HookGenerator[T] = function(*args, **kwargs)
56
57
 
57
58
  try:
58
59
  next(hook)
@@ -88,9 +89,11 @@ class Hook[**P, T]:
88
89
  return handler
89
90
 
90
91
  @staticmethod
91
- def __is_generator_function(obj: Any) -> bool:
92
- for o in obj, getattr(obj, "__call__", None):
93
- if isgeneratorfunction(o):
92
+ def __is_hook_generator_function[**_P, _T](
93
+ function: HookFunction[_P, _T],
94
+ ) -> TypeGuard[HookGeneratorFunction[_P, _T]]:
95
+ for fn in function, getattr(function, "__call__", None):
96
+ if isgeneratorfunction(fn):
94
97
  return True
95
98
 
96
99
  return False
@@ -0,0 +1,98 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import MutableMapping
3
+ from contextlib import suppress
4
+ from dataclasses import dataclass
5
+ from typing import Any, ClassVar, NoReturn, Protocol, runtime_checkable
6
+
7
+ from injection._core.common.asynchronous import Caller
8
+ from injection._core.common.threading import synchronized
9
+ from injection.exceptions import InjectionError
10
+
11
+
12
+ @runtime_checkable
13
+ class Injectable[T](Protocol):
14
+ __slots__ = ()
15
+
16
+ @property
17
+ def is_locked(self) -> bool:
18
+ return False
19
+
20
+ def unlock(self) -> None:
21
+ return
22
+
23
+ @abstractmethod
24
+ async def aget_instance(self) -> T:
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def get_instance(self) -> T:
29
+ raise NotImplementedError
30
+
31
+
32
+ @dataclass(repr=False, frozen=True, slots=True)
33
+ class BaseInjectable[T](Injectable[T], ABC):
34
+ factory: Caller[..., T]
35
+
36
+
37
+ class SimpleInjectable[T](BaseInjectable[T]):
38
+ __slots__ = ()
39
+
40
+ async def aget_instance(self) -> T:
41
+ return await self.factory.acall()
42
+
43
+ def get_instance(self) -> T:
44
+ return self.factory.call()
45
+
46
+
47
+ class SingletonInjectable[T](BaseInjectable[T]):
48
+ __slots__ = ("__dict__",)
49
+
50
+ __key: ClassVar[str] = "$instance"
51
+
52
+ @property
53
+ def cache(self) -> MutableMapping[str, Any]:
54
+ return self.__dict__
55
+
56
+ @property
57
+ def is_locked(self) -> bool:
58
+ return self.__key in self.cache
59
+
60
+ def unlock(self) -> None:
61
+ self.cache.clear()
62
+
63
+ async def aget_instance(self) -> T:
64
+ with suppress(KeyError):
65
+ return self.__check_instance()
66
+
67
+ with synchronized():
68
+ instance = await self.factory.acall()
69
+ self.__set_instance(instance)
70
+
71
+ return instance
72
+
73
+ def get_instance(self) -> T:
74
+ with suppress(KeyError):
75
+ return self.__check_instance()
76
+
77
+ with synchronized():
78
+ instance = self.factory.call()
79
+ self.__set_instance(instance)
80
+
81
+ return instance
82
+
83
+ def __check_instance(self) -> T:
84
+ return self.cache[self.__key]
85
+
86
+ def __set_instance(self, value: T) -> None:
87
+ self.cache[self.__key] = value
88
+
89
+
90
+ @dataclass(repr=False, frozen=True, slots=True)
91
+ class ShouldBeInjectable[T](Injectable[T]):
92
+ cls: type[T]
93
+
94
+ async def aget_instance(self) -> T:
95
+ return self.get_instance()
96
+
97
+ def get_instance(self) -> NoReturn:
98
+ raise InjectionError(f"`{self.cls}` should be an injectable.")