anydi 0.67.2__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/_scanner.py CHANGED
@@ -33,18 +33,24 @@ class Scanner:
33
33
  self._container = container
34
34
 
35
35
  def scan(
36
- self, /, packages: PackageOrIterable, *, tags: Iterable[str] | None = None
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
- if not self._container.is_registered(cls):
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
- self._container.register(cls, scope=scope)
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 _iter_modules(self, packages: Iterable[Package]) -> Iterator[ModuleType]:
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
- yield package
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
- yield importlib.import_module(module_info.name)
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.interface)
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.interface)
65
+ return await container.aresolve(self.dependency_type)
66
66
 
67
67
 
68
68
  # Configure Inject() and Provide[T] to use FastStream-specific marker
@@ -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
- interface = field_info.return_type
29
+ origin = field_info.return_type
30
30
  elif isinstance(field_info, FieldInfo):
31
- interface = field_info.annotation
31
+ origin = field_info.annotation
32
32
  else:
33
33
  continue
34
34
 
35
35
  container.register(
36
- Annotated[interface, f"{prefix}{setting_name}"],
36
+ Annotated[origin, f"{prefix}{setting_name}"],
37
37
  _get_setting_value(getattr(_settings, setting_name)),
38
38
  scope="singleton",
39
39
  )
@@ -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
- return _find_container(request)
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
@@ -91,17 +91,17 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
91
91
 
92
92
  # Validate parameters and collect which ones need injection
93
93
  for parameter in sig.parameters.values():
94
- interface, should_inject, _ = container.validate_injected_parameter(
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[interface].scope)
101
+ scopes.add(container.providers[dependency_type].scope)
102
102
  except KeyError:
103
- if inspect.isclass(interface) and is_provided(interface):
104
- scopes.add(interface.__provided__["scope"])
103
+ if inspect.isclass(dependency_type) and is_provided(dependency_type):
104
+ scopes.add(dependency_type.__provided__["scope"])
105
105
  else:
106
106
  non_injected_params.append(processed_parameter)
107
107
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.67.2
3
+ Version: 0.68.0
4
4
  Summary: Dependency Injection library
5
5
  Keywords: dependency injection,dependencies,di,async,asyncio,application
6
6
  Author: Anton Ruhlov
@@ -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,,
@@ -1,3 +1,6 @@
1
+ [console_scripts]
2
+ anydi = anydi._cli:main
3
+
1
4
  [pytest11]
2
5
  anydi = anydi.ext.pytest_plugin
3
6
 
@@ -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=e73sTDIkE9Cn0DWgYc-Y-xnAUC7kCWCpc28jCiozqz0,29356
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=cCkERh70RYWGuESE4nFwjDrzLAMztVLGgGeUBWLAjEQ,6238
22
- anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- anydi/testing.py,sha256=cHg3mMScZbEep9smRqSNQ81BZMQOkyugHe8TvKdPnEg,1347
24
- anydi-0.67.2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
25
- anydi-0.67.2.dist-info/entry_points.txt,sha256=AgOcQYM5KyS4D37QcYb00tiid0QA-pD1VrjHHq4QAps,44
26
- anydi-0.67.2.dist-info/METADATA,sha256=-RMwk-7ZbR7MJLY11pKe40CzhIF9w1NznfHj6GK6mug,8061
27
- anydi-0.67.2.dist-info/RECORD,,
File without changes