Skip to content

Commit 32f50b2

Browse files
authored
feat: ✨ Add constant injectable
1 parent e567d63 commit 32f50b2

File tree

7 files changed

+88
-50
lines changed

7 files changed

+88
-50
lines changed

documentation/basic-usage.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Create an injectable
44

5+
> **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
6+
57
If you wish to inject a singleton, use `singleton` decorator.
68

79
```python
@@ -22,7 +24,13 @@ class Injectable:
2224
""" class implementation """
2325
```
2426

25-
> **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
27+
If you have a constant (such as a global variable) and wish to register it as an injectable, use `constant` function.
28+
29+
```python
30+
from injection import constant
31+
32+
app = constant(Application())
33+
```
2634

2735
## Inject an instance
2836

@@ -40,6 +48,9 @@ def my_function(instance: Injectable):
4048
If `inject` decorates a class, it will be applied to the `__init__` method.
4149
_Especially useful for dataclasses:_
4250

51+
> **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
52+
> dependencies.
53+
4354
```python
4455
from dataclasses import dataclass
4556

@@ -51,9 +62,6 @@ class DataClass:
5162
instance: Injectable = ...
5263
```
5364

54-
> **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
55-
> dependencies.
56-
5765
## Get an instance
5866

5967
_Example with `get_instance` function:_

injection/_pkg.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
__all__ = (
44
"Module",
55
"ModulePriorities",
6+
"constant",
67
"default_module",
78
"get_instance",
89
"get_lazy_instance",
@@ -13,6 +14,7 @@
1314

1415
default_module = Module(f"{__name__}:default_module")
1516

17+
constant = default_module.constant
1618
get_instance = default_module.get_instance
1719
get_lazy_instance = default_module.get_lazy_instance
1820

injection/_pkg.pyi

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ _T = TypeVar("_T")
1010

1111
default_module: Final[Module] = ...
1212

13+
constant = default_module.constant
1314
get_instance = default_module.get_instance
1415
get_lazy_instance = default_module.get_lazy_instance
1516

@@ -59,12 +60,22 @@ class Module:
5960
singleton will be constructed. At injection time, the injected instance will
6061
always be the same.
6162
"""
62-
def get_instance(self, cls: type[_T] | UnionType) -> _T | None:
63+
def constant(
64+
self,
65+
instance: _T,
66+
on: type | Iterable[type] | UnionType = ...,
67+
) -> _T:
68+
"""
69+
Function for registering a specific instance to be injected. This is useful for
70+
registering global variables. The difference with the singleton decorator is
71+
that no dependencies are resolved, so the module doesn't need to be locked.
72+
"""
73+
def get_instance(self, cls: type[_T]) -> _T | None:
6374
"""
6475
Function used to retrieve an instance associated with the type passed in
6576
parameter or return `None`.
6677
"""
67-
def get_lazy_instance(self, cls: type[_T] | UnionType) -> Lazy[_T | None]:
78+
def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
6879
"""
6980
Function used to retrieve an instance associated with the type passed in
7081
parameter or `None`. Return a `Lazy` object. To access the instance contained

injection/core/module.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
_thread_lock = RLock()
4747

4848
_T = TypeVar("_T")
49+
Types = Iterable[type] | UnionType
4950

5051

5152
"""
@@ -211,7 +212,7 @@ def is_locked(self) -> bool:
211212
def __injectables(self) -> frozenset[Injectable]:
212213
return frozenset(self.__data.values())
213214

214-
def update(self, classes: Iterable[type] | UnionType, injectable: Injectable):
215+
def update(self, classes: Types, injectable: Injectable):
215216
classes = frozenset(get_origins(*classes))
216217

217218
if classes:
@@ -308,7 +309,16 @@ def __brokers(self) -> Iterator[Container | Module]:
308309
yield from tuple(self.__modules)
309310
yield self.__container
310311

311-
def get_instance(self, cls: type[_T] | UnionType) -> _T | None:
312+
def 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+
321+
def get_instance(self, cls: type[_T]) -> _T | None:
312322
try:
313323
injectable = self[cls]
314324
except KeyError:
@@ -317,10 +327,10 @@ def get_instance(self, cls: type[_T] | UnionType) -> _T | None:
317327
instance = injectable.get_instance()
318328
return cast(cls, instance)
319329

320-
def get_lazy_instance(self, cls: type[_T] | UnionType) -> Lazy[_T | None]:
330+
def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
321331
return Lazy(lambda: self.get_instance(cls))
322332

323-
def update(self, classes: Iterable[type] | UnionType, injectable: Injectable):
333+
def update(self, classes: Types, injectable: Injectable):
324334
self.__container.update(classes, injectable)
325335
return self
326336

@@ -548,39 +558,29 @@ def __call__(
548558
wrapped: Callable[..., Any] = None,
549559
/,
550560
*,
551-
on: type | Iterable[type] | UnionType = None,
561+
on: type | Types = None,
552562
):
553563
def decorator(wp):
554-
@lambda fn: fn()
555-
def classes():
556-
if cls := self.__get_class(wp):
557-
yield cls
558-
559-
if on is None:
560-
return
561-
elif isinstance(on, type | str):
562-
yield on
563-
else:
564-
yield from on
565-
566564
@self.__module.inject
567565
@wraps(wp, updated=())
568566
def factory(*args, **kwargs):
569567
return wp(*args, **kwargs)
570568

571569
injectable = self.__injectable_type(factory)
570+
classes = self.__get_classes(wp, on)
572571
self.__module.update(classes, injectable)
573-
574572
return wp
575573

576574
return decorator(wrapped) if wrapped else decorator
577575

578-
@staticmethod
579-
def __get_class(wrapped: Callable[..., Any]) -> type | None:
580-
if isclass(wrapped):
581-
return wrapped
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)
582581

583-
if isfunction(wrapped):
584-
return get_annotations(wrapped, eval_str=True).get("return")
582+
elif isfunction(obj):
583+
yield get_annotations(obj, eval_str=True).get("return")
585584

586-
return None
585+
else:
586+
yield obj

poetry.lock

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ python = ">=3.10, <4"
1414

1515
[tool.poetry.group.dev.dependencies]
1616
black = "*"
17-
blacksheep = "^2.0.4"
17+
blacksheep = "^2.0.5"
1818
flake8 = "*"
1919
isort = "*"
2020
pydantic = "^2.5.3"

tests/core/test_module.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,23 @@ def test_get_lazy_instance_with_no_injectable_return_lazy_none(self, module):
113113
assert ~lazy_instance is None
114114
assert lazy_instance.is_set
115115

116+
"""
117+
constant
118+
"""
119+
120+
def test_constant_with_success_return_instance(self, module):
121+
instance = SomeClass()
122+
return_value = module.constant(instance)
123+
assert instance is return_value is module.get_instance(SomeClass)
124+
125+
def test_constant_with_on_return_instance(self, module):
126+
class Class(SomeClass):
127+
...
128+
129+
instance = Class()
130+
module.constant(instance, on=SomeClass)
131+
assert instance is module.get_instance(Class) is module.get_instance(SomeClass)
132+
116133
"""
117134
use
118135
"""

0 commit comments

Comments
 (0)