anydi 0.67.1__py3-none-any.whl → 0.68.0__py3-none-any.whl
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.
- anydi/_cli.py +80 -0
- anydi/_container.py +702 -273
- anydi/_context.py +14 -14
- anydi/_decorators.py +115 -8
- anydi/_graph.py +217 -0
- anydi/_injector.py +12 -10
- anydi/_marker.py +11 -13
- anydi/_provider.py +46 -8
- anydi/_resolver.py +205 -159
- anydi/_scanner.py +46 -7
- anydi/ext/fastapi.py +1 -1
- anydi/ext/faststream.py +1 -1
- anydi/ext/pydantic_settings.py +3 -3
- anydi/ext/pytest_plugin.py +6 -2
- anydi/ext/typer.py +7 -7
- {anydi-0.67.1.dist-info → anydi-0.68.0.dist-info}/METADATA +1 -1
- anydi-0.68.0.dist-info/RECORD +29 -0
- {anydi-0.67.1.dist-info → anydi-0.68.0.dist-info}/entry_points.txt +3 -0
- anydi-0.67.1.dist-info/RECORD +0 -27
- {anydi-0.67.1.dist-info → anydi-0.68.0.dist-info}/WHEEL +0 -0
anydi/_scanner.py
CHANGED
|
@@ -33,18 +33,24 @@ class Scanner:
|
|
|
33
33
|
self._container = container
|
|
34
34
|
|
|
35
35
|
def scan(
|
|
36
|
-
self,
|
|
36
|
+
self,
|
|
37
|
+
/,
|
|
38
|
+
packages: PackageOrIterable,
|
|
39
|
+
*,
|
|
40
|
+
tags: Iterable[str] | None = None,
|
|
41
|
+
ignore: PackageOrIterable | None = None,
|
|
37
42
|
) -> None:
|
|
38
43
|
"""Scan packages or modules for decorated members and inject dependencies."""
|
|
39
44
|
if isinstance(packages, (ModuleType, str)):
|
|
40
45
|
packages = [packages]
|
|
41
46
|
|
|
42
47
|
tags_list = list(tags) if tags else []
|
|
48
|
+
ignore_prefixes = self._normalize_ignore(ignore)
|
|
43
49
|
provided_classes: list[type[Provided]] = []
|
|
44
50
|
injectable_dependencies: list[ScannedDependency] = []
|
|
45
51
|
|
|
46
52
|
# Single pass: collect both @provided classes and @injectable functions
|
|
47
|
-
for module in self._iter_modules(packages):
|
|
53
|
+
for module in self._iter_modules(packages, ignore_prefixes=ignore_prefixes):
|
|
48
54
|
provided_classes.extend(self._scan_module_for_provided(module))
|
|
49
55
|
injectable_dependencies.extend(
|
|
50
56
|
self._scan_module_for_injectable(module, tags=tags_list)
|
|
@@ -52,16 +58,47 @@ class Scanner:
|
|
|
52
58
|
|
|
53
59
|
# First: register @provided classes
|
|
54
60
|
for cls in provided_classes:
|
|
55
|
-
|
|
61
|
+
dependency_type = cls.__provided__.get("dependency_type", cls)
|
|
62
|
+
if not self._container.is_registered(dependency_type):
|
|
56
63
|
scope = cls.__provided__["scope"]
|
|
57
|
-
|
|
64
|
+
from_context = cls.__provided__.get("from_context", False)
|
|
65
|
+
self._container.register(
|
|
66
|
+
dependency_type, cls, scope=scope, from_context=from_context
|
|
67
|
+
)
|
|
58
68
|
|
|
59
69
|
# Second: inject @injectable functions
|
|
60
70
|
for dependency in injectable_dependencies:
|
|
61
71
|
decorated = self._container.inject()(dependency.member)
|
|
62
72
|
setattr(dependency.module, dependency.member.__name__, decorated)
|
|
63
73
|
|
|
64
|
-
def
|
|
74
|
+
def _normalize_ignore(self, ignore: PackageOrIterable | None) -> list[str]:
|
|
75
|
+
"""Normalize ignore parameter to a list of module name prefixes."""
|
|
76
|
+
if ignore is None:
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
if isinstance(ignore, (ModuleType, str)):
|
|
80
|
+
ignore = [ignore]
|
|
81
|
+
|
|
82
|
+
prefixes: list[str] = []
|
|
83
|
+
for item in ignore:
|
|
84
|
+
if isinstance(item, ModuleType):
|
|
85
|
+
prefixes.append(item.__name__)
|
|
86
|
+
else:
|
|
87
|
+
prefixes.append(item)
|
|
88
|
+
return prefixes
|
|
89
|
+
|
|
90
|
+
def _should_ignore_module(
|
|
91
|
+
self, module_name: str, ignore_prefixes: list[str]
|
|
92
|
+
) -> bool:
|
|
93
|
+
"""Check if a module should be ignored based on ignore prefixes."""
|
|
94
|
+
for prefix in ignore_prefixes:
|
|
95
|
+
if module_name == prefix or module_name.startswith(prefix + "."):
|
|
96
|
+
return True
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def _iter_modules(
|
|
100
|
+
self, packages: Iterable[Package], *, ignore_prefixes: list[str]
|
|
101
|
+
) -> Iterator[ModuleType]:
|
|
65
102
|
"""Iterate over all modules in the given packages."""
|
|
66
103
|
for package in packages:
|
|
67
104
|
if isinstance(package, str):
|
|
@@ -69,14 +106,16 @@ class Scanner:
|
|
|
69
106
|
|
|
70
107
|
# Single module (not a package)
|
|
71
108
|
if not hasattr(package, "__path__"):
|
|
72
|
-
|
|
109
|
+
if not self._should_ignore_module(package.__name__, ignore_prefixes):
|
|
110
|
+
yield package
|
|
73
111
|
continue
|
|
74
112
|
|
|
75
113
|
# Package - walk all submodules
|
|
76
114
|
for module_info in pkgutil.walk_packages(
|
|
77
115
|
package.__path__, prefix=package.__name__ + "."
|
|
78
116
|
):
|
|
79
|
-
|
|
117
|
+
if not self._should_ignore_module(module_info.name, ignore_prefixes):
|
|
118
|
+
yield importlib.import_module(module_info.name)
|
|
80
119
|
|
|
81
120
|
def _scan_module_for_provided(self, module: ModuleType) -> list[type[Provided]]:
|
|
82
121
|
"""Scan a module for @provided classes."""
|
anydi/ext/fastapi.py
CHANGED
|
@@ -35,7 +35,7 @@ class FastAPIMarker(params.Depends, Marker):
|
|
|
35
35
|
async def _fastapi_dependency(
|
|
36
36
|
self, container: Annotated[Container, Depends(get_container)]
|
|
37
37
|
) -> Any:
|
|
38
|
-
return await container.aresolve(self.
|
|
38
|
+
return await container.aresolve(self.dependency_type)
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
# Configure Inject() and Provide[T] to use FastAPI-specific marker at import time
|
anydi/ext/faststream.py
CHANGED
|
@@ -62,7 +62,7 @@ class FastStreamMarker(Dependant, Marker):
|
|
|
62
62
|
|
|
63
63
|
async def _faststream_dependency(self, context: ContextRepo) -> Any:
|
|
64
64
|
container = get_container_from_context(context)
|
|
65
|
-
return await container.aresolve(self.
|
|
65
|
+
return await container.aresolve(self.dependency_type)
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
# Configure Inject() and Provide[T] to use FastStream-specific marker
|
anydi/ext/pydantic_settings.py
CHANGED
|
@@ -26,14 +26,14 @@ def install(
|
|
|
26
26
|
all_fields = {**settings_cls.model_fields, **settings_cls.model_computed_fields}
|
|
27
27
|
for setting_name, field_info in all_fields.items():
|
|
28
28
|
if isinstance(field_info, ComputedFieldInfo):
|
|
29
|
-
|
|
29
|
+
origin = field_info.return_type
|
|
30
30
|
elif isinstance(field_info, FieldInfo):
|
|
31
|
-
|
|
31
|
+
origin = field_info.annotation
|
|
32
32
|
else:
|
|
33
33
|
continue
|
|
34
34
|
|
|
35
35
|
container.register(
|
|
36
|
-
Annotated[
|
|
36
|
+
Annotated[origin, f"{prefix}{setting_name}"],
|
|
37
37
|
_get_setting_value(getattr(_settings, setting_name)),
|
|
38
38
|
scope="singleton",
|
|
39
39
|
)
|
anydi/ext/pytest_plugin.py
CHANGED
|
@@ -183,8 +183,10 @@ def pytest_fixture_setup( # noqa: C901
|
|
|
183
183
|
|
|
184
184
|
@pytest.fixture(scope="session")
|
|
185
185
|
def container(request: pytest.FixtureRequest) -> Container:
|
|
186
|
-
"""Container fixture."""
|
|
187
|
-
|
|
186
|
+
"""Container fixture with testing mode enabled."""
|
|
187
|
+
container = _find_container(request)
|
|
188
|
+
container.enable_test_mode()
|
|
189
|
+
return container
|
|
188
190
|
|
|
189
191
|
|
|
190
192
|
@pytest.fixture
|
|
@@ -441,6 +443,7 @@ def _resolve_dependencies_sync(
|
|
|
441
443
|
*,
|
|
442
444
|
target: str,
|
|
443
445
|
) -> dict[str, Any]:
|
|
446
|
+
container.enable_test_mode()
|
|
444
447
|
resolved: dict[str, Any] = {}
|
|
445
448
|
for param_name, annotation in parameters:
|
|
446
449
|
try:
|
|
@@ -462,6 +465,7 @@ async def _resolve_dependencies_async(
|
|
|
462
465
|
*,
|
|
463
466
|
target: str,
|
|
464
467
|
) -> dict[str, Any]:
|
|
468
|
+
container.enable_test_mode()
|
|
465
469
|
resolved: dict[str, Any] = {}
|
|
466
470
|
for param_name, annotation in parameters:
|
|
467
471
|
try:
|
anydi/ext/typer.py
CHANGED
|
@@ -42,7 +42,7 @@ def _wrap_async_callback_with_injection(
|
|
|
42
42
|
callback: Callable[..., Awaitable[Any]],
|
|
43
43
|
container: Container,
|
|
44
44
|
sig: inspect.Signature,
|
|
45
|
-
non_injected_params:
|
|
45
|
+
non_injected_params: list[inspect.Parameter],
|
|
46
46
|
scopes: set[Scope],
|
|
47
47
|
) -> Any:
|
|
48
48
|
"""Wrap async callback with injection in anyio.run()."""
|
|
@@ -86,24 +86,24 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
|
|
|
86
86
|
"""Validate and wrap a callback for dependency injection."""
|
|
87
87
|
sig = inspect.signature(callback, eval_str=True)
|
|
88
88
|
injected_param_names: set[str] = set()
|
|
89
|
-
non_injected_params:
|
|
89
|
+
non_injected_params: list[inspect.Parameter] = []
|
|
90
90
|
scopes: set[Scope] = set()
|
|
91
91
|
|
|
92
92
|
# Validate parameters and collect which ones need injection
|
|
93
93
|
for parameter in sig.parameters.values():
|
|
94
|
-
|
|
94
|
+
dependency_type, should_inject, _ = container.validate_injected_parameter(
|
|
95
95
|
parameter, call=callback
|
|
96
96
|
)
|
|
97
97
|
processed_parameter = container._injector.unwrap_parameter(parameter)
|
|
98
98
|
if should_inject:
|
|
99
99
|
injected_param_names.add(parameter.name)
|
|
100
100
|
try:
|
|
101
|
-
scopes.add(container.providers[
|
|
101
|
+
scopes.add(container.providers[dependency_type].scope)
|
|
102
102
|
except KeyError:
|
|
103
|
-
if inspect.isclass(
|
|
104
|
-
scopes.add(
|
|
103
|
+
if inspect.isclass(dependency_type) and is_provided(dependency_type):
|
|
104
|
+
scopes.add(dependency_type.__provided__["scope"])
|
|
105
105
|
else:
|
|
106
|
-
non_injected_params.
|
|
106
|
+
non_injected_params.append(processed_parameter)
|
|
107
107
|
|
|
108
108
|
# If no parameters need injection and callback is not async, return original
|
|
109
109
|
if not injected_param_names and not inspect.iscoroutinefunction(callback):
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
anydi/__init__.py,sha256=KFX8OthKXwBuYDPCV61t-044DpJ88tAOzIxeUWRC5OA,633
|
|
2
|
+
anydi/_async_lock.py,sha256=3dwZr0KthXFYha0XKMyXf8jMmGb1lYoNC0O5w29V9ic,1104
|
|
3
|
+
anydi/_cli.py,sha256=0BhNvWPyuIGzUkDELIBm_nsEMWk7MtLi3oTvgXj5oko,2072
|
|
4
|
+
anydi/_container.py,sha256=lxUkRYU35qSiwTZYXfydElD28n2imzS81qRAAIhiHZU,46677
|
|
5
|
+
anydi/_context.py,sha256=ZQWxtBXWkrMsCk_L7K_A7-e09v5Mv9HApPH3LZ6ZF9k,3648
|
|
6
|
+
anydi/_decorators.py,sha256=7FvgmBDruv1w_iVqwloPzZwQQgyzQCklM6r9f6UVn-E,5728
|
|
7
|
+
anydi/_graph.py,sha256=X7AYsTMcGZtnIUJD5CVc1t8-t54fSI8CY_xh6ijODWk,7945
|
|
8
|
+
anydi/_injector.py,sha256=RvnPEYOgkg-WOIW1ItvVsoAZaSC9wmCnWQrfXad_86A,4507
|
|
9
|
+
anydi/_marker.py,sha256=yXSPbIVU-X-jMSawtCHWFMKke5VpWMiBRZlEH8PlUqE,3373
|
|
10
|
+
anydi/_module.py,sha256=2kN5uEXLd2Dsc58gz5IWK43wJewr_QgIVGSO3iWp798,2609
|
|
11
|
+
anydi/_provider.py,sha256=5pMXyiGwBo2j6OHaoOktLQfiX2rkCf5aYXFlFi65JlQ,2898
|
|
12
|
+
anydi/_resolver.py,sha256=CTYCjYkUXX35o2VMwvVwXyF8Kqp7h2tXituCNq6oydM,32746
|
|
13
|
+
anydi/_scanner.py,sha256=rCPbpW_5OV9MwYQdwTfceV_MlwSMPfpTTKuxdcMmV98,5804
|
|
14
|
+
anydi/_types.py,sha256=lsShY_-_CM2EFajeknAYXvLl-rHfopBT8udnK5_BtS4,1161
|
|
15
|
+
anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
anydi/ext/django/__init__.py,sha256=Ve8lncLU9dPY_Vjt4zihPgsSxwAtFHACn0XvBM5JG8k,367
|
|
17
|
+
anydi/ext/fastapi.py,sha256=NowHc-z_Sw069YDv9vP98Mehum5vHsIIcqvDkRmicmc,2964
|
|
18
|
+
anydi/ext/faststream.py,sha256=_3FZ8vlgl1GVizVv_6ippvwSWu_Zor4qm6HaiAp24o4,2744
|
|
19
|
+
anydi/ext/pydantic_settings.py,sha256=y6uz0MiLtqPvRO186bIdRZVQfezLvRUfT3KvvB5fCWk,1524
|
|
20
|
+
anydi/ext/pytest_plugin.py,sha256=XoMy5nLY8jM-KyK7VuwDuF_3rE3ouxaR3jZ0S6A6mdw,16322
|
|
21
|
+
anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
anydi/ext/starlette/middleware.py,sha256=n_JJ7BcG2Mg2M5HwM_SBboxZ-mnnD6WWJn4khq7Bgbs,1860
|
|
23
|
+
anydi/ext/typer.py,sha256=c7HapXQfKhnLJQcHNncJAGd8jZ3crX5it6-MRCJjyPM,6268
|
|
24
|
+
anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
anydi/testing.py,sha256=cHg3mMScZbEep9smRqSNQ81BZMQOkyugHe8TvKdPnEg,1347
|
|
26
|
+
anydi-0.68.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
27
|
+
anydi-0.68.0.dist-info/entry_points.txt,sha256=oDl_yEX12KlWcDzsZBTg85GG1Jl1rpiYOG4C7EJvebs,87
|
|
28
|
+
anydi-0.68.0.dist-info/METADATA,sha256=3gYwUfTF6a368Qhb8H-_-85cIze1H_054Wlt-ExFLz0,8061
|
|
29
|
+
anydi-0.68.0.dist-info/RECORD,,
|
anydi-0.67.1.dist-info/RECORD
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
anydi/__init__.py,sha256=KFX8OthKXwBuYDPCV61t-044DpJ88tAOzIxeUWRC5OA,633
|
|
2
|
-
anydi/_async_lock.py,sha256=3dwZr0KthXFYha0XKMyXf8jMmGb1lYoNC0O5w29V9ic,1104
|
|
3
|
-
anydi/_container.py,sha256=4bv3_fiDw6aPZ-bKjU5jmqeT66CvRnteHbosqMk5R0U,29408
|
|
4
|
-
anydi/_context.py,sha256=-9QqeMWo9OpZVXZxZCQgIsswggl3Ch7lgx1KiFX_ezc,3752
|
|
5
|
-
anydi/_decorators.py,sha256=J3W261ZAG7q4XKm4tbAv1wsWr9ysx9_5MUbUvSJB_MQ,2809
|
|
6
|
-
anydi/_injector.py,sha256=1Ux71DhGxu3dLwPJP8gU73olI0pcZ3_tVaVzwKH7100,4411
|
|
7
|
-
anydi/_marker.py,sha256=xVydjGdkxd_DqqwttnJZRkQbhpCTE9OnrhFmFJMlgvI,3415
|
|
8
|
-
anydi/_module.py,sha256=2kN5uEXLd2Dsc58gz5IWK43wJewr_QgIVGSO3iWp798,2609
|
|
9
|
-
anydi/_provider.py,sha256=OV1WFHTYv7W2U0XDk_Kql1r551Vhq8o-pUV5ep1HQcU,1574
|
|
10
|
-
anydi/_resolver.py,sha256=6pJS-9F0epCj7goVbc7GeGdpqe0woI3Blu-3dA3jeNs,31464
|
|
11
|
-
anydi/_scanner.py,sha256=rbRkHzyd2zMu7AFLffN6_tZJcMaW9gy7E-lVdHLHYrs,4294
|
|
12
|
-
anydi/_types.py,sha256=lsShY_-_CM2EFajeknAYXvLl-rHfopBT8udnK5_BtS4,1161
|
|
13
|
-
anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
anydi/ext/django/__init__.py,sha256=Ve8lncLU9dPY_Vjt4zihPgsSxwAtFHACn0XvBM5JG8k,367
|
|
15
|
-
anydi/ext/fastapi.py,sha256=px6gdRVIE8-thDy93Zcsd_xJF3H09n1nn7FKKemdiig,2958
|
|
16
|
-
anydi/ext/faststream.py,sha256=yszUfSbo3vJ2tr9PNC2GR-uX1XgRSXGH2lvLNoAXkc4,2738
|
|
17
|
-
anydi/ext/pydantic_settings.py,sha256=jVJZ1wPaPpsxdNPlJj9yq282ebqLZ9tckWpZ0eIwWLg,1533
|
|
18
|
-
anydi/ext/pytest_plugin.py,sha256=M54DkA-KxD9GqLnXdoCyn-Qur2c44MB6d0AgJuYCZ5w,16171
|
|
19
|
-
anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
anydi/ext/starlette/middleware.py,sha256=n_JJ7BcG2Mg2M5HwM_SBboxZ-mnnD6WWJn4khq7Bgbs,1860
|
|
21
|
-
anydi/ext/typer.py,sha256=z-sDd3jZMPTE2CyEuJ0f9uIJB43FjoLWbjpnkOvqSKA,6236
|
|
22
|
-
anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
anydi/testing.py,sha256=cHg3mMScZbEep9smRqSNQ81BZMQOkyugHe8TvKdPnEg,1347
|
|
24
|
-
anydi-0.67.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
25
|
-
anydi-0.67.1.dist-info/entry_points.txt,sha256=AgOcQYM5KyS4D37QcYb00tiid0QA-pD1VrjHHq4QAps,44
|
|
26
|
-
anydi-0.67.1.dist-info/METADATA,sha256=lqtZFxSRc7ohAAkAAdjcgxQDy7q5FTNP2MtBPZo3c4o,8061
|
|
27
|
-
anydi-0.67.1.dist-info/RECORD,,
|
|
File without changes
|