python-injection 0.6.6__tar.gz → 0.6.8__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.
Potentially problematic release.
This version of python-injection might be problematic. Click here for more details.
- {python_injection-0.6.6 → python_injection-0.6.8}/PKG-INFO +14 -5
- {python_injection-0.6.6 → python_injection-0.6.8}/documentation/basic-usage.md +13 -4
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/_pkg.py +3 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/_pkg.pyi +16 -3
- python_injection-0.6.8/injection/common/tools/_type.py +36 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/core/module.py +39 -43
- {python_injection-0.6.6 → python_injection-0.6.8}/pyproject.toml +2 -2
- python_injection-0.6.6/injection/common/tools/_type.py +0 -14
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/__init__.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/common/__init__.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/common/event.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/common/lazy.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/common/tools/__init__.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/core/__init__.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/exceptions.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/integrations/__init__.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/integrations/blacksheep.py +0 -0
- {python_injection-0.6.6 → python_injection-0.6.8}/injection/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-injection
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Fast and easy dependency injection framework.
|
|
5
5
|
Home-page: https://github.com/100nm/python-injection
|
|
6
6
|
License: MIT
|
|
@@ -19,6 +19,8 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
|
|
20
20
|
## Create an injectable
|
|
21
21
|
|
|
22
|
+
> **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
|
|
23
|
+
|
|
22
24
|
If you wish to inject a singleton, use `singleton` decorator.
|
|
23
25
|
|
|
24
26
|
```python
|
|
@@ -39,7 +41,14 @@ class Injectable:
|
|
|
39
41
|
""" class implementation """
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
If you have a constant (such as a global variable) and wish to register it as an injectable, use `set_constant`
|
|
45
|
+
function.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from injection import set_constant
|
|
49
|
+
|
|
50
|
+
app = set_constant(Application())
|
|
51
|
+
```
|
|
43
52
|
|
|
44
53
|
## Inject an instance
|
|
45
54
|
|
|
@@ -57,6 +66,9 @@ def my_function(instance: Injectable):
|
|
|
57
66
|
If `inject` decorates a class, it will be applied to the `__init__` method.
|
|
58
67
|
_Especially useful for dataclasses:_
|
|
59
68
|
|
|
69
|
+
> **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
|
|
70
|
+
> dependencies.
|
|
71
|
+
|
|
60
72
|
```python
|
|
61
73
|
from dataclasses import dataclass
|
|
62
74
|
|
|
@@ -68,9 +80,6 @@ class DataClass:
|
|
|
68
80
|
instance: Injectable = ...
|
|
69
81
|
```
|
|
70
82
|
|
|
71
|
-
> **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
|
|
72
|
-
> dependencies.
|
|
73
|
-
|
|
74
83
|
## Get an instance
|
|
75
84
|
|
|
76
85
|
_Example with `get_instance` function:_
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Create an injectable
|
|
4
4
|
|
|
5
|
+
> **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
|
|
6
|
+
|
|
5
7
|
If you wish to inject a singleton, use `singleton` decorator.
|
|
6
8
|
|
|
7
9
|
```python
|
|
@@ -22,7 +24,14 @@ class Injectable:
|
|
|
22
24
|
""" class implementation """
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
If you have a constant (such as a global variable) and wish to register it as an injectable, use `set_constant`
|
|
28
|
+
function.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from injection import set_constant
|
|
32
|
+
|
|
33
|
+
app = set_constant(Application())
|
|
34
|
+
```
|
|
26
35
|
|
|
27
36
|
## Inject an instance
|
|
28
37
|
|
|
@@ -40,6 +49,9 @@ def my_function(instance: Injectable):
|
|
|
40
49
|
If `inject` decorates a class, it will be applied to the `__init__` method.
|
|
41
50
|
_Especially useful for dataclasses:_
|
|
42
51
|
|
|
52
|
+
> **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
|
|
53
|
+
> dependencies.
|
|
54
|
+
|
|
43
55
|
```python
|
|
44
56
|
from dataclasses import dataclass
|
|
45
57
|
|
|
@@ -51,9 +63,6 @@ class DataClass:
|
|
|
51
63
|
instance: Injectable = ...
|
|
52
64
|
```
|
|
53
65
|
|
|
54
|
-
> **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
|
|
55
|
-
> dependencies.
|
|
56
|
-
|
|
57
66
|
## Get an instance
|
|
58
67
|
|
|
59
68
|
_Example with `get_instance` function:_
|
|
@@ -8,6 +8,7 @@ __all__ = (
|
|
|
8
8
|
"get_lazy_instance",
|
|
9
9
|
"inject",
|
|
10
10
|
"injectable",
|
|
11
|
+
"set_constant",
|
|
11
12
|
"singleton",
|
|
12
13
|
)
|
|
13
14
|
|
|
@@ -19,3 +20,5 @@ get_lazy_instance = default_module.get_lazy_instance
|
|
|
19
20
|
inject = default_module.inject
|
|
20
21
|
injectable = default_module.injectable
|
|
21
22
|
singleton = default_module.singleton
|
|
23
|
+
|
|
24
|
+
set_constant = default_module.set_constant
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Callable, Iterable
|
|
2
2
|
from contextlib import ContextDecorator
|
|
3
3
|
from enum import Enum
|
|
4
|
+
from types import UnionType
|
|
4
5
|
from typing import Any, ContextManager, Final, TypeVar, final
|
|
5
6
|
|
|
6
7
|
from injection.common.lazy import Lazy
|
|
@@ -16,6 +17,8 @@ inject = default_module.inject
|
|
|
16
17
|
injectable = default_module.injectable
|
|
17
18
|
singleton = default_module.singleton
|
|
18
19
|
|
|
20
|
+
set_constant = default_module.set_constant
|
|
21
|
+
|
|
19
22
|
@final
|
|
20
23
|
class Module:
|
|
21
24
|
"""
|
|
@@ -27,7 +30,7 @@ class Module:
|
|
|
27
30
|
"""
|
|
28
31
|
|
|
29
32
|
def __init__(self, name: str = ...): ...
|
|
30
|
-
def __contains__(self, cls: type, /) -> bool: ...
|
|
33
|
+
def __contains__(self, cls: type | UnionType, /) -> bool: ...
|
|
31
34
|
def inject(self, wrapped: Callable[..., Any] = ..., /):
|
|
32
35
|
"""
|
|
33
36
|
Decorator applicable to a class or function. Inject function dependencies using
|
|
@@ -39,7 +42,7 @@ class Module:
|
|
|
39
42
|
wrapped: Callable[..., Any] = ...,
|
|
40
43
|
/,
|
|
41
44
|
*,
|
|
42
|
-
on: type | Iterable[type] = ...,
|
|
45
|
+
on: type | Iterable[type] | UnionType = ...,
|
|
43
46
|
):
|
|
44
47
|
"""
|
|
45
48
|
Decorator applicable to a class or function. It is used to indicate how the
|
|
@@ -51,13 +54,23 @@ class Module:
|
|
|
51
54
|
wrapped: Callable[..., Any] = ...,
|
|
52
55
|
/,
|
|
53
56
|
*,
|
|
54
|
-
on: type | Iterable[type] = ...,
|
|
57
|
+
on: type | Iterable[type] | UnionType = ...,
|
|
55
58
|
):
|
|
56
59
|
"""
|
|
57
60
|
Decorator applicable to a class or function. It is used to indicate how the
|
|
58
61
|
singleton will be constructed. At injection time, the injected instance will
|
|
59
62
|
always be the same.
|
|
60
63
|
"""
|
|
64
|
+
def set_constant(
|
|
65
|
+
self,
|
|
66
|
+
instance: _T,
|
|
67
|
+
on: type | Iterable[type] | UnionType = ...,
|
|
68
|
+
) -> _T:
|
|
69
|
+
"""
|
|
70
|
+
Function for registering a specific instance to be injected. This is useful for
|
|
71
|
+
registering global variables. The difference with the singleton decorator is
|
|
72
|
+
that no dependencies are resolved, so the module doesn't need to be locked.
|
|
73
|
+
"""
|
|
61
74
|
def get_instance(self, cls: type[_T]) -> _T | None:
|
|
62
75
|
"""
|
|
63
76
|
Function used to retrieve an instance associated with the type passed in
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from types import NoneType, UnionType
|
|
3
|
+
from typing import Annotated, Any, Union, get_args, get_origin
|
|
4
|
+
|
|
5
|
+
__all__ = ("format_type", "get_origins")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_type(cls: type | Any) -> str:
|
|
9
|
+
try:
|
|
10
|
+
return f"{cls.__module__}.{cls.__qualname__}"
|
|
11
|
+
except AttributeError:
|
|
12
|
+
return str(cls)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_origins(*classes: type | Any) -> Iterator[type | Any]:
|
|
16
|
+
for cls in classes:
|
|
17
|
+
origin = get_origin(cls) or cls
|
|
18
|
+
|
|
19
|
+
if origin in (None, NoneType):
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
arguments = get_args(cls)
|
|
23
|
+
|
|
24
|
+
if origin in (Union, UnionType):
|
|
25
|
+
yield from get_origins(*arguments)
|
|
26
|
+
|
|
27
|
+
elif origin is Annotated:
|
|
28
|
+
try:
|
|
29
|
+
annotated = arguments[0]
|
|
30
|
+
except IndexError:
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
yield from get_origins(annotated)
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
yield origin
|
|
@@ -16,9 +16,9 @@ from contextlib import ContextDecorator, contextmanager, suppress
|
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
17
|
from enum import Enum, auto
|
|
18
18
|
from functools import singledispatchmethod, wraps
|
|
19
|
-
from inspect import Signature, get_annotations
|
|
19
|
+
from inspect import Signature, get_annotations, isclass, isfunction
|
|
20
20
|
from threading import RLock
|
|
21
|
-
from types import MappingProxyType
|
|
21
|
+
from types import MappingProxyType, UnionType
|
|
22
22
|
from typing import (
|
|
23
23
|
Any,
|
|
24
24
|
ContextManager,
|
|
@@ -32,7 +32,7 @@ from typing import (
|
|
|
32
32
|
|
|
33
33
|
from injection.common.event import Event, EventChannel, EventListener
|
|
34
34
|
from injection.common.lazy import Lazy, LazyMapping
|
|
35
|
-
from injection.common.tools import format_type,
|
|
35
|
+
from injection.common.tools import format_type, get_origins
|
|
36
36
|
from injection.exceptions import (
|
|
37
37
|
ModuleError,
|
|
38
38
|
ModuleLockError,
|
|
@@ -46,6 +46,7 @@ _logger = logging.getLogger(__name__)
|
|
|
46
46
|
_thread_lock = RLock()
|
|
47
47
|
|
|
48
48
|
_T = TypeVar("_T")
|
|
49
|
+
Types = Iterable[type] | UnionType
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
"""
|
|
@@ -193,16 +194,15 @@ class Container:
|
|
|
193
194
|
__data: dict[type, Injectable] = field(default_factory=dict, init=False)
|
|
194
195
|
__channel: EventChannel = field(default_factory=EventChannel, init=False)
|
|
195
196
|
|
|
196
|
-
def __getitem__(self, cls: type[_T], /) -> Injectable[_T]:
|
|
197
|
-
origin
|
|
197
|
+
def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
|
|
198
|
+
for origin in get_origins(cls):
|
|
199
|
+
with suppress(KeyError):
|
|
200
|
+
return self.__data[origin]
|
|
198
201
|
|
|
199
|
-
|
|
200
|
-
return self.__data[origin]
|
|
201
|
-
except KeyError as exc:
|
|
202
|
-
raise NoInjectable(cls) from exc
|
|
202
|
+
raise NoInjectable(cls)
|
|
203
203
|
|
|
204
|
-
def __contains__(self, cls: type, /) -> bool:
|
|
205
|
-
return
|
|
204
|
+
def __contains__(self, cls: type | UnionType, /) -> bool:
|
|
205
|
+
return any(origin in self.__data for origin in get_origins(cls))
|
|
206
206
|
|
|
207
207
|
@property
|
|
208
208
|
def is_locked(self) -> bool:
|
|
@@ -212,8 +212,8 @@ class Container:
|
|
|
212
212
|
def __injectables(self) -> frozenset[Injectable]:
|
|
213
213
|
return frozenset(self.__data.values())
|
|
214
214
|
|
|
215
|
-
def update(self, classes:
|
|
216
|
-
classes = frozenset(
|
|
215
|
+
def update(self, classes: Types, injectable: Injectable):
|
|
216
|
+
classes = frozenset(get_origins(*classes))
|
|
217
217
|
|
|
218
218
|
if classes:
|
|
219
219
|
event = ContainerDependenciesUpdated(self, classes)
|
|
@@ -272,17 +272,17 @@ class Module(EventListener):
|
|
|
272
272
|
def __post_init__(self):
|
|
273
273
|
self.__container.add_listener(self)
|
|
274
274
|
|
|
275
|
-
def __getitem__(self, cls: type[_T], /) -> Injectable[_T]:
|
|
275
|
+
def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
|
|
276
276
|
for broker in self.__brokers:
|
|
277
277
|
with suppress(KeyError):
|
|
278
278
|
return broker[cls]
|
|
279
279
|
|
|
280
280
|
raise NoInjectable(cls)
|
|
281
281
|
|
|
282
|
-
def __setitem__(self, cls: type, injectable: Injectable, /):
|
|
282
|
+
def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
|
|
283
283
|
self.update((cls,), injectable)
|
|
284
284
|
|
|
285
|
-
def __contains__(self, cls: type, /) -> bool:
|
|
285
|
+
def __contains__(self, cls: type | UnionType, /) -> bool:
|
|
286
286
|
return any(cls in broker for broker in self.__brokers)
|
|
287
287
|
|
|
288
288
|
def __str__(self) -> str:
|
|
@@ -309,6 +309,15 @@ class Module(EventListener):
|
|
|
309
309
|
yield from tuple(self.__modules)
|
|
310
310
|
yield self.__container
|
|
311
311
|
|
|
312
|
+
def set_constant(self, instance: _T, on: type | Types = None) -> _T:
|
|
313
|
+
cls = type(instance)
|
|
314
|
+
|
|
315
|
+
@self.injectable(on=(cls, on))
|
|
316
|
+
def get_constant():
|
|
317
|
+
return instance
|
|
318
|
+
|
|
319
|
+
return instance
|
|
320
|
+
|
|
312
321
|
def get_instance(self, cls: type[_T]) -> _T | None:
|
|
313
322
|
try:
|
|
314
323
|
injectable = self[cls]
|
|
@@ -321,7 +330,7 @@ class Module(EventListener):
|
|
|
321
330
|
def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
|
|
322
331
|
return Lazy(lambda: self.get_instance(cls))
|
|
323
332
|
|
|
324
|
-
def update(self, classes:
|
|
333
|
+
def update(self, classes: Types, injectable: Injectable):
|
|
325
334
|
self.__container.update(classes, injectable)
|
|
326
335
|
return self
|
|
327
336
|
|
|
@@ -395,7 +404,7 @@ class Module(EventListener):
|
|
|
395
404
|
|
|
396
405
|
with self.__channel.dispatch(event):
|
|
397
406
|
yield
|
|
398
|
-
_logger.debug(
|
|
407
|
+
_logger.debug(event)
|
|
399
408
|
|
|
400
409
|
def __check_locking(self):
|
|
401
410
|
if self.is_locked:
|
|
@@ -507,7 +516,7 @@ class InjectDecorator:
|
|
|
507
516
|
|
|
508
517
|
def __call__(self, wrapped: Callable[..., Any] = None, /):
|
|
509
518
|
def decorator(wp):
|
|
510
|
-
if
|
|
519
|
+
if isclass(wp):
|
|
511
520
|
return self.__class_decorator(wp)
|
|
512
521
|
|
|
513
522
|
return self.__decorator(wp)
|
|
@@ -549,42 +558,29 @@ class InjectableDecorator:
|
|
|
549
558
|
wrapped: Callable[..., Any] = None,
|
|
550
559
|
/,
|
|
551
560
|
*,
|
|
552
|
-
on: type |
|
|
561
|
+
on: type | Types = None,
|
|
553
562
|
):
|
|
554
563
|
def decorator(wp):
|
|
555
|
-
@lambda fn: fn()
|
|
556
|
-
def classes():
|
|
557
|
-
if cls := self.__get_target_class(wp):
|
|
558
|
-
yield cls
|
|
559
|
-
|
|
560
|
-
if on is None:
|
|
561
|
-
return
|
|
562
|
-
elif isinstance(on, type | str):
|
|
563
|
-
yield on
|
|
564
|
-
else:
|
|
565
|
-
yield from on
|
|
566
|
-
|
|
567
564
|
@self.__module.inject
|
|
568
565
|
@wraps(wp, updated=())
|
|
569
566
|
def factory(*args, **kwargs):
|
|
570
567
|
return wp(*args, **kwargs)
|
|
571
568
|
|
|
572
569
|
injectable = self.__injectable_type(factory)
|
|
570
|
+
classes = self.__get_classes(wp, on)
|
|
573
571
|
self.__module.update(classes, injectable)
|
|
574
|
-
|
|
575
572
|
return wp
|
|
576
573
|
|
|
577
574
|
return decorator(wrapped) if wrapped else decorator
|
|
578
575
|
|
|
579
|
-
@
|
|
580
|
-
def
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if callable(wrapped):
|
|
585
|
-
return_type = get_annotations(wrapped, eval_str=True).get("return")
|
|
576
|
+
@classmethod
|
|
577
|
+
def __get_classes(cls, *objects: Any) -> Iterator[type | UnionType]:
|
|
578
|
+
for obj in objects:
|
|
579
|
+
if isinstance(obj, Iterable) and not isinstance(obj, type | str):
|
|
580
|
+
yield from cls.__get_classes(*obj)
|
|
586
581
|
|
|
587
|
-
|
|
588
|
-
return
|
|
582
|
+
elif isfunction(obj):
|
|
583
|
+
yield get_annotations(obj, eval_str=True).get("return")
|
|
589
584
|
|
|
590
|
-
|
|
585
|
+
else:
|
|
586
|
+
yield obj
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-injection"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.8"
|
|
4
4
|
description = "Fast and easy dependency injection framework."
|
|
5
5
|
authors = ["remimd"]
|
|
6
6
|
keywords = ["dependencies", "inject", "injection"]
|
|
@@ -14,7 +14,7 @@ python = ">=3.10, <4"
|
|
|
14
14
|
|
|
15
15
|
[tool.poetry.group.dev.dependencies]
|
|
16
16
|
black = "*"
|
|
17
|
-
blacksheep = "^2.0.
|
|
17
|
+
blacksheep = "^2.0.5"
|
|
18
18
|
flake8 = "*"
|
|
19
19
|
isort = "*"
|
|
20
20
|
pydantic = "^2.5.3"
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
__all__ = ("format_type", "get_origin")
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def format_type(cls: type | Any) -> str:
|
|
7
|
-
try:
|
|
8
|
-
return f"{cls.__module__}.{cls.__qualname__}"
|
|
9
|
-
except AttributeError:
|
|
10
|
-
return str(cls)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def get_origin(cls: type | Any) -> type | Any:
|
|
14
|
-
return getattr(cls, "__origin__", cls)
|
|
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
|