python-injection 0.23.3__tar.gz → 0.25.0__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.
- {python_injection-0.23.3 → python_injection-0.25.0}/PKG-INFO +2 -1
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/type.py +2 -45
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/descriptors.py +1 -5
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/injectables.py +5 -4
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/module.py +96 -76
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/scope.py +4 -4
- python_injection-0.25.0/injection/testing/__init__.py +30 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/testing/__init__.pyi +2 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/pyproject.toml +8 -6
- python_injection-0.23.3/injection/testing/__init__.py +0 -29
- {python_injection-0.23.3 → python_injection-0.25.0}/.gitignore +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/LICENSE +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/README.md +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/__init__.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/__init__.pyi +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/__init__.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/asfunction.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/__init__.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/asynchronous.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/event.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/invertible.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/key.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/lazy.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/common/threading.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/_core/slots.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/entrypoint.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/exceptions.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/ext/__init__.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/ext/fastapi.py +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/ext/fastapi.pyi +0 -0
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/loaders.py +1 -1
- {python_injection-0.23.3 → python_injection-0.25.0}/injection/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-injection
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.0
|
|
4
4
|
Summary: Fast and easy dependency injection framework.
|
|
5
5
|
Project-URL: Repository, https://github.com/100nm/python-injection
|
|
6
6
|
Author: remimd
|
|
@@ -23,6 +23,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
23
23
|
Classifier: Topic :: Software Development :: Testing
|
|
24
24
|
Classifier: Typing :: Typed
|
|
25
25
|
Requires-Python: <3.15,>=3.12
|
|
26
|
+
Requires-Dist: type-analyzer
|
|
26
27
|
Provides-Extra: async
|
|
27
28
|
Requires-Dist: anyio; extra == 'async'
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
@@ -10,19 +10,16 @@ from collections.abc import (
|
|
|
10
10
|
Iterator,
|
|
11
11
|
)
|
|
12
12
|
from inspect import isclass, isfunction
|
|
13
|
-
from types import GenericAlias,
|
|
13
|
+
from types import GenericAlias, UnionType
|
|
14
14
|
from typing import (
|
|
15
|
-
Annotated,
|
|
16
15
|
Any,
|
|
17
16
|
TypeAliasType,
|
|
18
|
-
Union,
|
|
19
17
|
get_args,
|
|
20
18
|
get_origin,
|
|
21
19
|
get_type_hints,
|
|
22
20
|
)
|
|
23
21
|
|
|
24
|
-
type
|
|
25
|
-
type InputType[T] = TypeDef[T] | UnionType
|
|
22
|
+
type InputType[T] = type[T] | TypeAliasType | GenericAlias | UnionType
|
|
26
23
|
type TypeInfo[T] = (
|
|
27
24
|
InputType[T]
|
|
28
25
|
| Callable[..., T]
|
|
@@ -67,43 +64,3 @@ def get_yield_hint[T](
|
|
|
67
64
|
return (arg,)
|
|
68
65
|
|
|
69
66
|
return ()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def standardize_types(
|
|
73
|
-
*types: InputType[Any],
|
|
74
|
-
with_origin: bool = False,
|
|
75
|
-
ignore_none_type: bool = False,
|
|
76
|
-
) -> Iterator[TypeDef[Any]]:
|
|
77
|
-
for tp in types:
|
|
78
|
-
if tp is None or (ignore_none_type and tp is NoneType):
|
|
79
|
-
continue
|
|
80
|
-
|
|
81
|
-
origin = get_origin(tp)
|
|
82
|
-
|
|
83
|
-
if origin is Union or isinstance(tp, UnionType):
|
|
84
|
-
inner_types = get_args(tp)
|
|
85
|
-
|
|
86
|
-
elif origin is Annotated:
|
|
87
|
-
inner_types = get_args(tp)[:1]
|
|
88
|
-
|
|
89
|
-
else:
|
|
90
|
-
yield tp
|
|
91
|
-
|
|
92
|
-
if with_origin:
|
|
93
|
-
if origin is not None:
|
|
94
|
-
yield origin
|
|
95
|
-
|
|
96
|
-
for alias in (tp, origin):
|
|
97
|
-
if isinstance(alias, TypeAliasType):
|
|
98
|
-
yield from standardize_types(
|
|
99
|
-
alias.__value__,
|
|
100
|
-
with_origin=with_origin,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
continue
|
|
104
|
-
|
|
105
|
-
yield from standardize_types(
|
|
106
|
-
*inner_types,
|
|
107
|
-
with_origin=with_origin,
|
|
108
|
-
ignore_none_type=ignore_none_type,
|
|
109
|
-
)
|
|
@@ -53,11 +53,7 @@ class MappedScope:
|
|
|
53
53
|
if name == descriptor_name:
|
|
54
54
|
continue
|
|
55
55
|
|
|
56
|
-
key = self.__module.reserve_scoped_slot(
|
|
57
|
-
hint,
|
|
58
|
-
scope_name=self.__name,
|
|
59
|
-
ignore_none_type=True,
|
|
60
|
-
)
|
|
56
|
+
key = self.__module.reserve_scoped_slot(hint, scope_name=self.__name)
|
|
61
57
|
yield name, key
|
|
62
58
|
|
|
63
59
|
def __mapping_from(self, instance: object) -> dict[SlotKey[Any], Any]:
|
|
@@ -122,11 +122,12 @@ class SingletonInjectable[T](Injectable[T]):
|
|
|
122
122
|
class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
123
123
|
factory: Caller[..., R]
|
|
124
124
|
scope_name: str
|
|
125
|
+
key: SlotKey[T] = field(default_factory=SlotKey)
|
|
125
126
|
logic: CacheLogic[T] = field(default_factory=CacheLogic)
|
|
126
127
|
|
|
127
128
|
@property
|
|
128
129
|
def is_locked(self) -> bool:
|
|
129
|
-
return in_scope_cache(self, self.scope_name)
|
|
130
|
+
return in_scope_cache(self.key, self.scope_name)
|
|
130
131
|
|
|
131
132
|
@abstractmethod
|
|
132
133
|
async def abuild(self, scope: Scope) -> T:
|
|
@@ -139,12 +140,12 @@ class ScopedInjectable[R, T](Injectable[T], ABC):
|
|
|
139
140
|
async def aget_instance(self) -> T:
|
|
140
141
|
scope = self.__get_scope()
|
|
141
142
|
factory = partial(self.abuild, scope)
|
|
142
|
-
return await self.logic.aget_or_create(scope.cache, self, factory)
|
|
143
|
+
return await self.logic.aget_or_create(scope.cache, self.key, factory)
|
|
143
144
|
|
|
144
145
|
def get_instance(self) -> T:
|
|
145
146
|
scope = self.__get_scope()
|
|
146
147
|
factory = partial(self.build, scope)
|
|
147
|
-
return self.logic.get_or_create(scope.cache, self, factory)
|
|
148
|
+
return self.logic.get_or_create(scope.cache, self.key, factory)
|
|
148
149
|
|
|
149
150
|
def unlock(self) -> None:
|
|
150
151
|
if self.is_locked:
|
|
@@ -187,7 +188,7 @@ class SimpleScopedInjectable[T](ScopedInjectable[T, T]):
|
|
|
187
188
|
return self.factory.call()
|
|
188
189
|
|
|
189
190
|
def unlock(self) -> None:
|
|
190
|
-
remove_scoped_values(self, self.scope_name)
|
|
191
|
+
remove_scoped_values(self.key, self.scope_name)
|
|
191
192
|
|
|
192
193
|
|
|
193
194
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import itertools
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from collections import OrderedDict, deque
|
|
5
6
|
from collections.abc import (
|
|
@@ -42,6 +43,8 @@ from typing import (
|
|
|
42
43
|
runtime_checkable,
|
|
43
44
|
)
|
|
44
45
|
|
|
46
|
+
from type_analyzer import MatchingTypesConfig, iter_matching_types, matching_types
|
|
47
|
+
|
|
45
48
|
from injection._core.common.asynchronous import (
|
|
46
49
|
AsyncCaller,
|
|
47
50
|
Caller,
|
|
@@ -55,11 +58,9 @@ from injection._core.common.lazy import Lazy, alazy, lazy
|
|
|
55
58
|
from injection._core.common.threading import get_lock
|
|
56
59
|
from injection._core.common.type import (
|
|
57
60
|
InputType,
|
|
58
|
-
TypeDef,
|
|
59
61
|
TypeInfo,
|
|
60
62
|
get_return_types,
|
|
61
63
|
get_yield_hint,
|
|
62
|
-
standardize_types,
|
|
63
64
|
)
|
|
64
65
|
from injection._core.injectables import (
|
|
65
66
|
AsyncCMScopedInjectable,
|
|
@@ -118,15 +119,25 @@ class ModuleEventProxy(ModuleEvent):
|
|
|
118
119
|
return f"`{self.module}` has propagated an event: {self.origin}"
|
|
119
120
|
|
|
120
121
|
@property
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
def is_duplicate(self) -> bool:
|
|
123
|
+
module, origin = self.module, self.origin
|
|
124
|
+
return any(
|
|
125
|
+
module is event.module and origin is event.origin
|
|
126
|
+
for event in self.proxy_history
|
|
127
|
+
)
|
|
126
128
|
|
|
127
129
|
@property
|
|
128
130
|
def origin(self) -> Event:
|
|
129
|
-
|
|
131
|
+
reversed_proxy_history = reversed(tuple(self.proxy_history))
|
|
132
|
+
return next(reversed_proxy_history, self).event
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def proxy_history(self) -> Iterator[ModuleEventProxy]:
|
|
136
|
+
event = self.event
|
|
137
|
+
|
|
138
|
+
if isinstance(event, ModuleEventProxy):
|
|
139
|
+
yield event
|
|
140
|
+
yield from event.proxy_history
|
|
130
141
|
|
|
131
142
|
|
|
132
143
|
@dataclass(frozen=True, slots=True)
|
|
@@ -160,8 +171,10 @@ class ModulePriorityUpdated(ModuleEvent):
|
|
|
160
171
|
|
|
161
172
|
@dataclass(frozen=True, slots=True)
|
|
162
173
|
class UnlockCalled(Event):
|
|
174
|
+
module: Module
|
|
175
|
+
|
|
163
176
|
def __str__(self) -> str:
|
|
164
|
-
return "
|
|
177
|
+
return f"`{self.module}.unlock` has been called."
|
|
165
178
|
|
|
166
179
|
|
|
167
180
|
"""
|
|
@@ -229,27 +242,10 @@ class Updater[T]:
|
|
|
229
242
|
classes: Iterable[InputType[T]]
|
|
230
243
|
injectable: Injectable[T]
|
|
231
244
|
mode: Mode
|
|
232
|
-
ignore_none_type: bool
|
|
233
245
|
|
|
234
246
|
def make_record(self) -> Record[T]:
|
|
235
247
|
return Record(self.injectable, self.mode)
|
|
236
248
|
|
|
237
|
-
@classmethod
|
|
238
|
-
def with_basics(
|
|
239
|
-
cls,
|
|
240
|
-
on: TypeInfo[T],
|
|
241
|
-
/,
|
|
242
|
-
injectable: Injectable[T],
|
|
243
|
-
mode: Mode | ModeStr,
|
|
244
|
-
ignore_none_type: bool = False,
|
|
245
|
-
) -> Self:
|
|
246
|
-
return cls(
|
|
247
|
-
classes=get_return_types(on),
|
|
248
|
-
injectable=injectable,
|
|
249
|
-
mode=Mode(mode),
|
|
250
|
-
ignore_none_type=ignore_none_type,
|
|
251
|
-
)
|
|
252
|
-
|
|
253
249
|
|
|
254
250
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
255
251
|
class Locator(Broker):
|
|
@@ -263,21 +259,15 @@ class Locator(Broker):
|
|
|
263
259
|
)
|
|
264
260
|
|
|
265
261
|
def __getitem__[T](self, cls: InputType[T], /) -> Injectable[T]:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
262
|
+
try:
|
|
263
|
+
record = self.__records[cls]
|
|
264
|
+
except KeyError as exc:
|
|
265
|
+
raise NoInjectable(cls) from exc
|
|
266
|
+
else:
|
|
272
267
|
return record.injectable
|
|
273
268
|
|
|
274
|
-
raise NoInjectable(cls)
|
|
275
|
-
|
|
276
269
|
def __contains__(self, cls: InputType[Any], /) -> bool:
|
|
277
|
-
return
|
|
278
|
-
input_class in self.__records
|
|
279
|
-
for input_class in self.__standardize_inputs((cls,))
|
|
280
|
-
)
|
|
270
|
+
return cls in self.__records
|
|
281
271
|
|
|
282
272
|
@property
|
|
283
273
|
def is_locked(self) -> bool:
|
|
@@ -289,8 +279,7 @@ class Locator(Broker):
|
|
|
289
279
|
|
|
290
280
|
def update[T](self, updater: Updater[T]) -> Self:
|
|
291
281
|
record = updater.make_record()
|
|
292
|
-
|
|
293
|
-
records = dict(self.__prepare_for_updating(classes, record))
|
|
282
|
+
records = dict(self.__prepare_for_updating(updater.classes, record))
|
|
294
283
|
|
|
295
284
|
if records:
|
|
296
285
|
event = LocatorDependenciesUpdated(self, records.keys(), record.mode)
|
|
@@ -351,21 +340,6 @@ class Locator(Broker):
|
|
|
351
340
|
|
|
352
341
|
return new_mode.rank > existing_mode.rank
|
|
353
342
|
|
|
354
|
-
@staticmethod
|
|
355
|
-
def __reduce_classes[T](updater: Updater[T]) -> frozenset[TypeDef[T]]:
|
|
356
|
-
return frozenset(
|
|
357
|
-
standardize_types(
|
|
358
|
-
*updater.classes,
|
|
359
|
-
ignore_none_type=updater.ignore_none_type,
|
|
360
|
-
)
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
@staticmethod
|
|
364
|
-
def __standardize_inputs[T](
|
|
365
|
-
classes: Iterable[InputType[T]],
|
|
366
|
-
) -> Iterator[InputType[T]]:
|
|
367
|
-
return standardize_types(*classes, with_origin=True)
|
|
368
|
-
|
|
369
343
|
|
|
370
344
|
"""
|
|
371
345
|
Module
|
|
@@ -422,23 +396,26 @@ class Module(Broker, EventListener):
|
|
|
422
396
|
self.__locator.add_listener(self)
|
|
423
397
|
|
|
424
398
|
def __getitem__[T](self, cls: InputType[T], /) -> Injectable[T]:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
399
|
+
key_types = self.__matching_key_types(cls)
|
|
400
|
+
|
|
401
|
+
for broker in self._iter_brokers():
|
|
402
|
+
for key_type in key_types:
|
|
403
|
+
with suppress(KeyError):
|
|
404
|
+
return broker[key_type]
|
|
428
405
|
|
|
429
406
|
raise NoInjectable(cls)
|
|
430
407
|
|
|
431
408
|
def __contains__(self, cls: InputType[Any], /) -> bool:
|
|
432
|
-
|
|
409
|
+
key_types = self.__matching_key_types(cls)
|
|
410
|
+
return any(
|
|
411
|
+
key_type in broker
|
|
412
|
+
for broker in self._iter_brokers()
|
|
413
|
+
for key_type in key_types
|
|
414
|
+
)
|
|
433
415
|
|
|
434
416
|
@property
|
|
435
417
|
def is_locked(self) -> bool:
|
|
436
|
-
return any(broker.is_locked for broker in self.
|
|
437
|
-
|
|
438
|
-
@property
|
|
439
|
-
def __brokers(self) -> Iterator[Broker]:
|
|
440
|
-
yield from self.__modules
|
|
441
|
-
yield self.__locator
|
|
418
|
+
return any(broker.is_locked for broker in self._iter_brokers())
|
|
442
419
|
|
|
443
420
|
def injectable[**P, T](
|
|
444
421
|
self,
|
|
@@ -455,8 +432,7 @@ class Module(Broker, EventListener):
|
|
|
455
432
|
factory = extract_caller(self.make_injected_function(wp) if inject else wp)
|
|
456
433
|
injectable = cls(factory) # type: ignore[arg-type]
|
|
457
434
|
hints = on if ignore_type_hint else (wp, on)
|
|
458
|
-
|
|
459
|
-
self.update(updater)
|
|
435
|
+
self.update_from(hints, injectable, mode)
|
|
460
436
|
return wp
|
|
461
437
|
|
|
462
438
|
return decorator(wrapped) if wrapped else decorator
|
|
@@ -507,8 +483,7 @@ class Module(Broker, EventListener):
|
|
|
507
483
|
def should_be_injectable[T](self, wrapped: type[T] | None = None, /) -> Any:
|
|
508
484
|
def decorator(wp: type[T]) -> type[T]:
|
|
509
485
|
injectable = ShouldBeInjectable(wp)
|
|
510
|
-
|
|
511
|
-
self.update(updater)
|
|
486
|
+
self.update_from(wp, injectable, Mode.FALLBACK)
|
|
512
487
|
return wp
|
|
513
488
|
|
|
514
489
|
return decorator(wrapped) if wrapped else decorator
|
|
@@ -564,11 +539,9 @@ class Module(Broker, EventListener):
|
|
|
564
539
|
scope_name: str,
|
|
565
540
|
*,
|
|
566
541
|
mode: Mode | ModeStr = Mode.get_default(),
|
|
567
|
-
ignore_none_type: bool = False,
|
|
568
542
|
) -> SlotKey[T]:
|
|
569
543
|
injectable = ScopedSlotInjectable(cls, scope_name)
|
|
570
|
-
|
|
571
|
-
self.update(updater)
|
|
544
|
+
self.update_from(cls, injectable, mode)
|
|
572
545
|
return injectable.key
|
|
573
546
|
|
|
574
547
|
def inject[**P, T](
|
|
@@ -791,6 +764,21 @@ class Module(Broker, EventListener):
|
|
|
791
764
|
self.__locator.update(updater)
|
|
792
765
|
return self
|
|
793
766
|
|
|
767
|
+
def update_from[T](
|
|
768
|
+
self,
|
|
769
|
+
on: TypeInfo[T],
|
|
770
|
+
/,
|
|
771
|
+
injectable: Injectable[T],
|
|
772
|
+
mode: Mode | ModeStr,
|
|
773
|
+
) -> Self:
|
|
774
|
+
updater = Updater(
|
|
775
|
+
classes=self.__build_key_types(on),
|
|
776
|
+
injectable=injectable,
|
|
777
|
+
mode=Mode(mode),
|
|
778
|
+
)
|
|
779
|
+
self.update(updater)
|
|
780
|
+
return self
|
|
781
|
+
|
|
794
782
|
def init_modules(self, *modules: Module) -> Self:
|
|
795
783
|
for module in tuple(self.__modules):
|
|
796
784
|
self.stop_using(module)
|
|
@@ -860,7 +848,7 @@ class Module(Broker, EventListener):
|
|
|
860
848
|
return self
|
|
861
849
|
|
|
862
850
|
def unlock(self) -> Self:
|
|
863
|
-
event = UnlockCalled()
|
|
851
|
+
event = UnlockCalled(self)
|
|
864
852
|
|
|
865
853
|
with self.dispatch(event, lock_bypass=True):
|
|
866
854
|
self.unsafe_unlocking()
|
|
@@ -868,11 +856,11 @@ class Module(Broker, EventListener):
|
|
|
868
856
|
return self
|
|
869
857
|
|
|
870
858
|
def unsafe_unlocking(self) -> None:
|
|
871
|
-
for broker in self.
|
|
859
|
+
for broker in self._iter_brokers():
|
|
872
860
|
broker.unsafe_unlocking()
|
|
873
861
|
|
|
874
862
|
async def all_ready(self) -> None:
|
|
875
|
-
for broker in self.
|
|
863
|
+
for broker in self._iter_brokers():
|
|
876
864
|
await broker.all_ready()
|
|
877
865
|
|
|
878
866
|
def add_logger(self, logger: Logger) -> Self:
|
|
@@ -887,8 +875,12 @@ class Module(Broker, EventListener):
|
|
|
887
875
|
self.__channel.remove_listener(listener)
|
|
888
876
|
return self
|
|
889
877
|
|
|
890
|
-
def on_event(self, event: Event, /) -> ContextManager[None]:
|
|
878
|
+
def on_event(self, event: Event, /) -> ContextManager[None] | None:
|
|
891
879
|
self_event = ModuleEventProxy(self, event)
|
|
880
|
+
|
|
881
|
+
if self_event.is_duplicate:
|
|
882
|
+
return None
|
|
883
|
+
|
|
892
884
|
return self.dispatch(self_event)
|
|
893
885
|
|
|
894
886
|
@contextmanager
|
|
@@ -902,6 +894,20 @@ class Module(Broker, EventListener):
|
|
|
902
894
|
finally:
|
|
903
895
|
self.__debug(event)
|
|
904
896
|
|
|
897
|
+
def _iter_brokers(self, visited: set[Module] | None = None, /) -> Iterator[Broker]:
|
|
898
|
+
if visited is None:
|
|
899
|
+
visited = set()
|
|
900
|
+
|
|
901
|
+
if self in visited:
|
|
902
|
+
return
|
|
903
|
+
|
|
904
|
+
visited.add(self)
|
|
905
|
+
|
|
906
|
+
for module in self.__modules:
|
|
907
|
+
yield from module._iter_brokers(visited)
|
|
908
|
+
|
|
909
|
+
yield self.__locator
|
|
910
|
+
|
|
905
911
|
def __debug(self, message: object) -> None:
|
|
906
912
|
for logger in self.__loggers:
|
|
907
913
|
logger.debug(message)
|
|
@@ -933,6 +939,20 @@ class Module(Broker, EventListener):
|
|
|
933
939
|
def default(cls) -> Module:
|
|
934
940
|
return cls.from_name("__default__")
|
|
935
941
|
|
|
942
|
+
@staticmethod
|
|
943
|
+
def __build_key_types(input_cls: Any) -> frozenset[Any]:
|
|
944
|
+
config = MatchingTypesConfig(ignore_none=True)
|
|
945
|
+
return frozenset(
|
|
946
|
+
itertools.chain.from_iterable(
|
|
947
|
+
iter_matching_types(cls, config) for cls in get_return_types(input_cls)
|
|
948
|
+
)
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
@staticmethod
|
|
952
|
+
def __matching_key_types(input_cls: Any) -> tuple[Any, ...]:
|
|
953
|
+
config = MatchingTypesConfig(with_origin=True, with_type_alias_value=True)
|
|
954
|
+
return matching_types(input_cls, config)
|
|
955
|
+
|
|
936
956
|
|
|
937
957
|
def mod(name: str | None = None, /) -> Module:
|
|
938
958
|
if name is None:
|
|
@@ -182,11 +182,11 @@ def get_scope[T](name: str, default: T | EllipsisType = ...) -> Scope | T:
|
|
|
182
182
|
return default
|
|
183
183
|
|
|
184
184
|
|
|
185
|
-
def in_scope_cache(key: Any, scope_name: str) -> bool:
|
|
185
|
+
def in_scope_cache(key: SlotKey[Any], scope_name: str) -> bool:
|
|
186
186
|
return any(key in scope.cache for scope in get_active_scopes(scope_name))
|
|
187
187
|
|
|
188
188
|
|
|
189
|
-
def remove_scoped_values(key: Any, scope_name: str) -> None:
|
|
189
|
+
def remove_scoped_values(key: SlotKey[Any], scope_name: str) -> None:
|
|
190
190
|
for scope in get_active_scopes(scope_name):
|
|
191
191
|
scope.cache.pop(key, None)
|
|
192
192
|
|
|
@@ -233,7 +233,7 @@ def _bind_scope(
|
|
|
233
233
|
class Scope(Protocol):
|
|
234
234
|
__slots__ = ()
|
|
235
235
|
|
|
236
|
-
cache: MutableMapping[Any, Any]
|
|
236
|
+
cache: MutableMapping[SlotKey[Any], Any]
|
|
237
237
|
|
|
238
238
|
@abstractmethod
|
|
239
239
|
async def aenter[T](self, context_manager: AsyncContextManager[T]) -> T:
|
|
@@ -247,7 +247,7 @@ class Scope(Protocol):
|
|
|
247
247
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
248
248
|
class BaseScope[T](Scope, ABC):
|
|
249
249
|
delegate: T
|
|
250
|
-
cache: MutableMapping[Any, Any] = field(
|
|
250
|
+
cache: MutableMapping[SlotKey[Any], Any] = field(
|
|
251
251
|
default_factory=dict,
|
|
252
252
|
init=False,
|
|
253
253
|
hash=False,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
from injection import mod
|
|
4
|
+
from injection.loaders import LoadedProfile, ProfileLoader, load_profile
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"TEST_PROFILE_NAME",
|
|
8
|
+
"load_test_profile",
|
|
9
|
+
"reserve_scoped_test_slot",
|
|
10
|
+
"set_test_constant",
|
|
11
|
+
"should_be_test_injectable",
|
|
12
|
+
"test_constant",
|
|
13
|
+
"test_injectable",
|
|
14
|
+
"test_scoped",
|
|
15
|
+
"test_singleton",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TEST_PROFILE_NAME: Final[str] = "__testing__"
|
|
19
|
+
|
|
20
|
+
reserve_scoped_test_slot = mod(TEST_PROFILE_NAME).reserve_scoped_slot
|
|
21
|
+
set_test_constant = mod(TEST_PROFILE_NAME).set_constant
|
|
22
|
+
should_be_test_injectable = mod(TEST_PROFILE_NAME).should_be_injectable
|
|
23
|
+
test_constant = mod(TEST_PROFILE_NAME).constant
|
|
24
|
+
test_injectable = mod(TEST_PROFILE_NAME).injectable
|
|
25
|
+
test_scoped = mod(TEST_PROFILE_NAME).scoped
|
|
26
|
+
test_singleton = mod(TEST_PROFILE_NAME).singleton
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_test_profile(loader: ProfileLoader | None = None) -> LoadedProfile:
|
|
30
|
+
return load_profile(TEST_PROFILE_NAME, loader)
|
|
@@ -30,7 +30,7 @@ test = [
|
|
|
30
30
|
|
|
31
31
|
[project]
|
|
32
32
|
name = "python-injection"
|
|
33
|
-
version = "0.
|
|
33
|
+
version = "0.25.0"
|
|
34
34
|
description = "Fast and easy dependency injection framework."
|
|
35
35
|
license = "MIT"
|
|
36
36
|
license-files = ["LICENSE"]
|
|
@@ -55,7 +55,9 @@ classifiers = [
|
|
|
55
55
|
"Natural Language :: English",
|
|
56
56
|
"Typing :: Typed",
|
|
57
57
|
]
|
|
58
|
-
dependencies = [
|
|
58
|
+
dependencies = [
|
|
59
|
+
"type-analyzer",
|
|
60
|
+
]
|
|
59
61
|
|
|
60
62
|
[project.optional-dependencies]
|
|
61
63
|
async = [
|
|
@@ -106,12 +108,12 @@ init_forbid_extra = true
|
|
|
106
108
|
init_typed = true
|
|
107
109
|
warn_required_dynamic_aliases = true
|
|
108
110
|
|
|
109
|
-
[tool.pytest
|
|
110
|
-
python_files = "test_*.py"
|
|
111
|
-
addopts = "--tb short --cov ./ --cov-report term-missing:skip-covered"
|
|
111
|
+
[tool.pytest]
|
|
112
|
+
python_files = ["test_*.py"]
|
|
113
|
+
addopts = ["--tb", "short", "--cov", "./", "--cov-report", "term-missing:skip-covered"]
|
|
112
114
|
asyncio_default_fixture_loop_scope = "session"
|
|
113
115
|
asyncio_mode = "auto"
|
|
114
|
-
testpaths = "**/tests/"
|
|
116
|
+
testpaths = ["**/tests/"]
|
|
115
117
|
|
|
116
118
|
[tool.ruff]
|
|
117
119
|
line-length = 88
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from typing import Final
|
|
2
|
-
|
|
3
|
-
from injection import mod
|
|
4
|
-
from injection.loaders import LoadedProfile, ProfileLoader, load_profile
|
|
5
|
-
|
|
6
|
-
__all__ = (
|
|
7
|
-
"load_test_profile",
|
|
8
|
-
"reserve_scoped_test_slot",
|
|
9
|
-
"set_test_constant",
|
|
10
|
-
"should_be_test_injectable",
|
|
11
|
-
"test_constant",
|
|
12
|
-
"test_injectable",
|
|
13
|
-
"test_scoped",
|
|
14
|
-
"test_singleton",
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
_TEST_PROFILE_NAME: Final[str] = "__testing__"
|
|
18
|
-
|
|
19
|
-
reserve_scoped_test_slot = mod(_TEST_PROFILE_NAME).reserve_scoped_slot
|
|
20
|
-
set_test_constant = mod(_TEST_PROFILE_NAME).set_constant
|
|
21
|
-
should_be_test_injectable = mod(_TEST_PROFILE_NAME).should_be_injectable
|
|
22
|
-
test_constant = mod(_TEST_PROFILE_NAME).constant
|
|
23
|
-
test_injectable = mod(_TEST_PROFILE_NAME).injectable
|
|
24
|
-
test_scoped = mod(_TEST_PROFILE_NAME).scoped
|
|
25
|
-
test_singleton = mod(_TEST_PROFILE_NAME).singleton
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def load_test_profile(loader: ProfileLoader | None = None) -> LoadedProfile:
|
|
29
|
-
return load_profile(_TEST_PROFILE_NAME, loader)
|
|
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
|
|
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
|
|
@@ -176,12 +176,12 @@ class ProfileLoader:
|
|
|
176
176
|
|
|
177
177
|
def __init_subsets_for(self, module: Module) -> Module:
|
|
178
178
|
if not self.__is_empty and not self.__is_initialized(module):
|
|
179
|
+
self.__mark_initialized(module)
|
|
179
180
|
target_modules = tuple(
|
|
180
181
|
self.__init_subsets_for(mod(name))
|
|
181
182
|
for name in self.module_subsets.get(module.name, ())
|
|
182
183
|
)
|
|
183
184
|
module.init_modules(*target_modules)
|
|
184
|
-
self.__mark_initialized(module)
|
|
185
185
|
|
|
186
186
|
return module
|
|
187
187
|
|
|
File without changes
|