anydi 0.43.0__tar.gz → 0.44.1__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.
- {anydi-0.43.0 → anydi-0.44.1}/PKG-INFO +1 -1
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_container.py +12 -31
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/_utils.py +1 -11
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/_settings.py +0 -2
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/_utils.py +3 -2
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/apps.py +1 -3
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/pytest_plugin.py +4 -4
- {anydi-0.43.0 → anydi-0.44.1}/anydi/testing.py +1 -4
- {anydi-0.43.0 → anydi-0.44.1}/docs/extensions/django.md +1 -2
- {anydi-0.43.0 → anydi-0.44.1}/docs/usage.md +5 -17
- {anydi-0.43.0 → anydi-0.44.1}/pyproject.toml +2 -2
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/fastapi/app.py +1 -1
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/fastapi/test_ext.py +2 -2
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/faststream/test_ext.py +2 -2
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/faststream/test_subscribers.py +1 -1
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/test_pytest_plugin.py +1 -1
- {anydi-0.43.0 → anydi-0.44.1}/tests/test_container.py +9 -32
- {anydi-0.43.0 → anydi-0.44.1}/tests/test_testing.py +5 -6
- {anydi-0.43.0 → anydi-0.44.1}/uv.lock +1 -1
- anydi-0.43.0/override.py +0 -0
- anydi-0.43.0/tests/ext/fastapi/test_auto_register.py +0 -34
- {anydi-0.43.0 → anydi-0.44.1}/.editorconfig +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/.github/workflows/ci.yml +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/.gitignore +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/.readthedocs.yaml +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/LICENSE +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/Makefile +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/README.md +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_async.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_context.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_decorators.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_module.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_provider.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_scan.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_scope.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/_typing.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/_container.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/middleware.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/ninja/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/ninja/_operation.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/django/ninja/_signature.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/fastapi.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/faststream.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/pydantic_settings.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/anydi/py.typed +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/docs/examples/basic.md +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/docs/extensions/fastapi.md +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/docs/extensions/faststream.md +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/docs/extensions/pydantic_settings.md +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/docs/index.md +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/mkdocs.yml +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/conftest.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/api/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/api/router.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/api/test_router.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/api/urls.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/conftest.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/container.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/scan/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/services.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/settings.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/test_views.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/urls.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/django/views.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/fastapi/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/fastapi/conftest.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/fastapi/test_routes.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/faststream/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/fixtures.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/starlette/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/starlette/app.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/starlette/conftest.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/starlette/test_routes.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/ext/test_pydantic.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/fixtures.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a1/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a1/handlers.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a2/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a2/a21/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a2/a21/handlers.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a3/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/a/a3/handlers.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/b/__init__.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/scan_app/b/handlers.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/test_decorators.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/test_module.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/test_scan.py +0 -0
- {anydi-0.43.0 → anydi-0.44.1}/tests/test_utils.py +0 -0
|
@@ -19,11 +19,7 @@ from ._async import run_sync
|
|
|
19
19
|
from ._context import InstanceContext
|
|
20
20
|
from ._decorators import is_provided
|
|
21
21
|
from ._module import ModuleDef, ModuleRegistrar
|
|
22
|
-
from ._provider import
|
|
23
|
-
Provider,
|
|
24
|
-
ProviderDef,
|
|
25
|
-
ProviderKind,
|
|
26
|
-
)
|
|
22
|
+
from ._provider import Provider, ProviderDef, ProviderKind
|
|
27
23
|
from ._scan import PackageOrIterable, Scanner
|
|
28
24
|
from ._scope import ALLOWED_SCOPES, Scope
|
|
29
25
|
from ._typing import (
|
|
@@ -53,12 +49,10 @@ class Container:
|
|
|
53
49
|
*,
|
|
54
50
|
providers: Iterable[ProviderDef] | None = None,
|
|
55
51
|
modules: Iterable[ModuleDef] | None = None,
|
|
56
|
-
strict: bool = False,
|
|
57
52
|
default_scope: Scope = "transient",
|
|
58
53
|
logger: logging.Logger | None = None,
|
|
59
54
|
) -> None:
|
|
60
55
|
self._providers: dict[Any, Provider] = {}
|
|
61
|
-
self._strict = strict
|
|
62
56
|
self._default_scope: Scope = default_scope
|
|
63
57
|
self._logger = logger or logging.getLogger(__name__)
|
|
64
58
|
self._resources: dict[str, list[Any]] = defaultdict(list)
|
|
@@ -91,11 +85,6 @@ class Container:
|
|
|
91
85
|
# Properties
|
|
92
86
|
############################
|
|
93
87
|
|
|
94
|
-
@property
|
|
95
|
-
def strict(self) -> bool:
|
|
96
|
-
"""Check if strict mode is enabled."""
|
|
97
|
-
return self._strict
|
|
98
|
-
|
|
99
88
|
@property
|
|
100
89
|
def default_scope(self) -> Scope:
|
|
101
90
|
"""Get the default scope."""
|
|
@@ -231,6 +220,10 @@ class Container:
|
|
|
231
220
|
"""Check if a provider is registered for the specified interface."""
|
|
232
221
|
return interface in self._providers
|
|
233
222
|
|
|
223
|
+
def has_provider_for(self, interface: Any) -> bool:
|
|
224
|
+
"""Check if a provider exists for the specified interface."""
|
|
225
|
+
return self.is_registered(interface) or is_provided(interface)
|
|
226
|
+
|
|
234
227
|
def unregister(self, interface: Any) -> None:
|
|
235
228
|
"""Unregister a provider by interface."""
|
|
236
229
|
if not self.is_registered(interface):
|
|
@@ -440,7 +433,7 @@ class Container:
|
|
|
440
433
|
try:
|
|
441
434
|
return self._get_provider(interface)
|
|
442
435
|
except LookupError:
|
|
443
|
-
if
|
|
436
|
+
if interface is inspect.Parameter.empty:
|
|
444
437
|
raise
|
|
445
438
|
if inspect.isclass(interface) and not is_builtin_type(interface):
|
|
446
439
|
# Try to get defined scope
|
|
@@ -464,14 +457,13 @@ class Container:
|
|
|
464
457
|
if provider.is_resource:
|
|
465
458
|
self._resources[provider.scope].remove(provider.interface)
|
|
466
459
|
|
|
460
|
+
@staticmethod
|
|
467
461
|
def _parameter_has_default(
|
|
468
|
-
|
|
462
|
+
parameter: inspect.Parameter, /, **defaults: Any
|
|
469
463
|
) -> bool:
|
|
470
464
|
has_default_in_kwargs = parameter.name in defaults if defaults else False
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
)
|
|
474
|
-
return has_default_in_kwargs or has_non_strict_default
|
|
465
|
+
has_default = parameter.default is not inspect.Parameter.empty
|
|
466
|
+
return has_default_in_kwargs or has_default
|
|
475
467
|
|
|
476
468
|
############################
|
|
477
469
|
# Instance Methods
|
|
@@ -822,18 +814,7 @@ class Container:
|
|
|
822
814
|
for parameter in get_typed_parameters(call):
|
|
823
815
|
if not is_marker(parameter.default):
|
|
824
816
|
continue
|
|
825
|
-
|
|
826
|
-
self._validate_injected_parameter(call, parameter)
|
|
827
|
-
except LookupError as exc:
|
|
828
|
-
if not self.strict:
|
|
829
|
-
self.logger.debug(
|
|
830
|
-
f"Cannot validate the `{type_repr(call)}` parameter "
|
|
831
|
-
f"`{parameter.name}` with an annotation of "
|
|
832
|
-
f"`{type_repr(parameter.annotation)} due to being "
|
|
833
|
-
"in non-strict mode. It will be validated at the first call."
|
|
834
|
-
)
|
|
835
|
-
else:
|
|
836
|
-
raise exc
|
|
817
|
+
self._validate_injected_parameter(call, parameter)
|
|
837
818
|
injected_params[parameter.name] = parameter.annotation
|
|
838
819
|
return injected_params
|
|
839
820
|
|
|
@@ -846,7 +827,7 @@ class Container:
|
|
|
846
827
|
f"Missing `{type_repr(call)}` parameter `{parameter.name}` annotation."
|
|
847
828
|
)
|
|
848
829
|
|
|
849
|
-
if not self.
|
|
830
|
+
if not self.has_provider_for(parameter.annotation):
|
|
850
831
|
raise LookupError(
|
|
851
832
|
f"`{type_repr(call)}` has an unknown dependency parameter "
|
|
852
833
|
f"`{parameter.name}` with an annotation of "
|
|
@@ -9,7 +9,6 @@ from typing import Annotated, Any, Callable
|
|
|
9
9
|
from typing_extensions import get_args, get_origin
|
|
10
10
|
|
|
11
11
|
from anydi._container import Container
|
|
12
|
-
from anydi._typing import type_repr
|
|
13
12
|
|
|
14
13
|
logger = logging.getLogger(__name__)
|
|
15
14
|
|
|
@@ -69,15 +68,6 @@ def patch_call_parameter(
|
|
|
69
68
|
if not isinstance(parameter.default, HasInterface):
|
|
70
69
|
return None
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
logger.debug(
|
|
74
|
-
f"Callable `{type_repr(call)}` injected parameter "
|
|
75
|
-
f"`{parameter.name}` with an annotation of "
|
|
76
|
-
f"`{type_repr(parameter.annotation)}` "
|
|
77
|
-
"is not registered. It will be registered at runtime with the "
|
|
78
|
-
"first call because it is running in non-strict mode."
|
|
79
|
-
)
|
|
80
|
-
else:
|
|
81
|
-
container._validate_injected_parameter(call, parameter) # noqa
|
|
71
|
+
container._validate_injected_parameter(call, parameter) # noqa
|
|
82
72
|
|
|
83
73
|
parameter.default.interface = parameter.annotation
|
|
@@ -8,7 +8,6 @@ from typing_extensions import TypedDict
|
|
|
8
8
|
|
|
9
9
|
class Settings(TypedDict):
|
|
10
10
|
CONTAINER_FACTORY: str | None
|
|
11
|
-
STRICT_MODE: bool
|
|
12
11
|
REGISTER_SETTINGS: bool
|
|
13
12
|
REGISTER_COMPONENTS: bool
|
|
14
13
|
INJECT_URLCONF: str | Sequence[str] | None
|
|
@@ -19,7 +18,6 @@ class Settings(TypedDict):
|
|
|
19
18
|
|
|
20
19
|
DEFAULTS = Settings(
|
|
21
20
|
CONTAINER_FACTORY=None,
|
|
22
|
-
STRICT_MODE=False,
|
|
23
21
|
REGISTER_SETTINGS=False,
|
|
24
22
|
REGISTER_COMPONENTS=False,
|
|
25
23
|
MODULES=[],
|
|
@@ -62,15 +62,16 @@ def register_components(container: Container) -> None:
|
|
|
62
62
|
def inject_urlpatterns(container: Container, *, urlconf: str) -> None:
|
|
63
63
|
"""Auto-inject the container into views."""
|
|
64
64
|
resolver = get_resolver(urlconf)
|
|
65
|
+
injected_urlpatterns = []
|
|
65
66
|
for pattern in iter_urlpatterns(resolver.url_patterns):
|
|
66
67
|
# Skip already injected views
|
|
67
|
-
if
|
|
68
|
+
if pattern.lookup_str in injected_urlpatterns:
|
|
68
69
|
continue
|
|
69
70
|
# Skip django-ninja views
|
|
70
71
|
if pattern.lookup_str.startswith("ninja."):
|
|
71
72
|
continue # pragma: no cover
|
|
72
73
|
pattern.callback = container.inject(pattern.callback)
|
|
73
|
-
pattern.
|
|
74
|
+
injected_urlpatterns.append(pattern.lookup_str)
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
def iter_urlpatterns(
|
|
@@ -34,9 +34,7 @@ class ContainerConfig(AppConfig):
|
|
|
34
34
|
) from exc
|
|
35
35
|
self.container = container_factory()
|
|
36
36
|
else:
|
|
37
|
-
self.container = anydi.Container(
|
|
38
|
-
strict=self.settings["STRICT_MODE"],
|
|
39
|
-
)
|
|
37
|
+
self.container = anydi.Container()
|
|
40
38
|
|
|
41
39
|
def ready(self) -> None: # noqa: C901
|
|
42
40
|
# Register Django settings
|
|
@@ -88,8 +88,8 @@ def _anydi_inject(
|
|
|
88
88
|
container = cast(Container, request.getfixturevalue("anydi_setup_container"))
|
|
89
89
|
|
|
90
90
|
for argname, interface in _anydi_injected_parameter_iterator():
|
|
91
|
-
# Skip if the interface
|
|
92
|
-
if
|
|
91
|
+
# Skip if the interface has no provider
|
|
92
|
+
if not container.has_provider_for(interface):
|
|
93
93
|
continue
|
|
94
94
|
|
|
95
95
|
try:
|
|
@@ -128,8 +128,8 @@ def _anydi_ainject(
|
|
|
128
128
|
container = cast(Container, request.getfixturevalue("anydi_setup_container"))
|
|
129
129
|
|
|
130
130
|
for argname, interface in _anydi_injected_parameter_iterator():
|
|
131
|
-
# Skip if the interface
|
|
132
|
-
if
|
|
131
|
+
# Skip if the interface has no provider
|
|
132
|
+
if not container.has_provider_for(interface):
|
|
133
133
|
continue
|
|
134
134
|
|
|
135
135
|
try:
|
|
@@ -23,14 +23,12 @@ class TestContainer(Container):
|
|
|
23
23
|
*,
|
|
24
24
|
providers: Sequence[ProviderDef] | None = None,
|
|
25
25
|
modules: Iterable[ModuleDef] | None = None,
|
|
26
|
-
strict: bool = False,
|
|
27
26
|
default_scope: Scope = "transient",
|
|
28
27
|
logger: logging.Logger | None = None,
|
|
29
28
|
) -> None:
|
|
30
29
|
super().__init__(
|
|
31
30
|
providers=providers,
|
|
32
31
|
modules=modules,
|
|
33
|
-
strict=strict,
|
|
34
32
|
default_scope=default_scope,
|
|
35
33
|
logger=logger,
|
|
36
34
|
)
|
|
@@ -47,7 +45,6 @@ class TestContainer(Container):
|
|
|
47
45
|
)
|
|
48
46
|
for provider in container.providers.values()
|
|
49
47
|
],
|
|
50
|
-
strict=container.strict,
|
|
51
48
|
default_scope=container.default_scope,
|
|
52
49
|
logger=container.logger,
|
|
53
50
|
)
|
|
@@ -57,7 +54,7 @@ class TestContainer(Container):
|
|
|
57
54
|
"""
|
|
58
55
|
Override the provider for the specified interface with a specific instance.
|
|
59
56
|
"""
|
|
60
|
-
if not self.
|
|
57
|
+
if not self.has_provider_for(interface):
|
|
61
58
|
raise LookupError(
|
|
62
59
|
f"The provider interface `{type_repr(interface)}` not registered."
|
|
63
60
|
)
|
|
@@ -59,7 +59,6 @@ The `HelloService` will be automatically injected into the hello view through th
|
|
|
59
59
|
`ANYDI` supports the following settings:
|
|
60
60
|
|
|
61
61
|
* `CONTAINER_FACTORY: str | None` - Specifies the factory function used to create the container. If not provided, the default container factory will be utilized.
|
|
62
|
-
* `STRICT_MODE: bool` - Determines the container's behavior when a dependency cannot be resolved. If set to `True`, the container will raise an exception. If `False`, it will attempt to automatically create the dependency.
|
|
63
62
|
* `REGISTER_SETTINGS: bool` - If `True`, the container will register the Django settings within it.
|
|
64
63
|
* `REGISTER_COMPONENTS: bool` - If `True`, the container will register Django components such as the database and cache.
|
|
65
64
|
* `INJECT_URLCONF: str | Sequence[str] | None` - Specifies the URL configuration where dependencies should be injected.
|
|
@@ -148,7 +147,7 @@ from anydi import Container
|
|
|
148
147
|
|
|
149
148
|
|
|
150
149
|
def get_container() -> Container:
|
|
151
|
-
container = Container(
|
|
150
|
+
container = Container()
|
|
152
151
|
# Add custom container configuration here
|
|
153
152
|
return container
|
|
154
153
|
```
|
|
@@ -145,10 +145,11 @@ assert not container.is_resolved(int)
|
|
|
145
145
|
|
|
146
146
|
This pattern can be used while writing unit tests to ensure that each test case has a clean dependency graph.
|
|
147
147
|
|
|
148
|
-
##
|
|
148
|
+
## Auto-Registration
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
`AnyDI`
|
|
151
|
+
`AnyDI` doesn't require explicit registration for every type. It can dynamically resolve and auto-register dependencies,
|
|
152
|
+
simplifying setups where manual registration for each type is impractical.
|
|
152
153
|
|
|
153
154
|
Consider a scenario with class dependencies:
|
|
154
155
|
|
|
@@ -177,7 +178,7 @@ from typing import Iterator
|
|
|
177
178
|
|
|
178
179
|
from anydi import Container
|
|
179
180
|
|
|
180
|
-
container = Container()
|
|
181
|
+
container = Container()
|
|
181
182
|
|
|
182
183
|
@container.provider(scope="singleton")
|
|
183
184
|
def db() -> Iterator[Database]:
|
|
@@ -194,19 +195,6 @@ assert container.is_resolved(Repository)
|
|
|
194
195
|
assert container.is_resolved(Database)
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
### Enabling Strict Mode
|
|
198
|
-
|
|
199
|
-
For strict checking, enable strict mode by setting `strict=True` when creating the `Container`. In strict mode, all types must be explicitly registered or have a definable provider before instantiation.
|
|
200
|
-
|
|
201
|
-
```python
|
|
202
|
-
container = Container(strict=True)
|
|
203
|
-
|
|
204
|
-
# Raises LookupError if `Service` or dependencies aren't registered.
|
|
205
|
-
_ = container.resolve(Service)
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Here's an improved version of the documentation with some enhancements for clarity, completeness, and formatting:
|
|
209
|
-
|
|
210
198
|
### Automatic Resource Management
|
|
211
199
|
|
|
212
200
|
When your class dependencies implement the context manager protocol by defining the `__enter__/__aenter__` and `__exit__/__aexit__` methods, these resources are automatically managed by the container for `singleton` and `request` scoped providers.
|
|
@@ -229,7 +217,7 @@ class Connection:
|
|
|
229
217
|
self.disconnected = True
|
|
230
218
|
|
|
231
219
|
|
|
232
|
-
container = Container(
|
|
220
|
+
container = Container()
|
|
233
221
|
connection = container.resolve(Connection)
|
|
234
222
|
|
|
235
223
|
assert container.is_resolved(Connection)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.44.1"
|
|
4
4
|
description = "Dependency Injection library"
|
|
5
5
|
authors = [{ name = "Anton Ruhlov", email = "antonruhlov@gmail.com" }]
|
|
6
6
|
requires-python = "~=3.9"
|
|
@@ -137,7 +137,7 @@ omit = [
|
|
|
137
137
|
]
|
|
138
138
|
|
|
139
139
|
[tool.bumpversion]
|
|
140
|
-
current_version = "0.
|
|
140
|
+
current_version = "0.44.1"
|
|
141
141
|
parse = """(?x)
|
|
142
142
|
(?P<major>0|[1-9]\\d*)\\.
|
|
143
143
|
(?P<minor>0|[1-9]\\d*)\\.
|
|
@@ -15,7 +15,7 @@ def test_inject_param_missing_interface() -> None:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_install_without_annotation() -> None:
|
|
18
|
-
container = Container(
|
|
18
|
+
container = Container()
|
|
19
19
|
|
|
20
20
|
@container.provider(scope="singleton")
|
|
21
21
|
def message() -> str:
|
|
@@ -34,7 +34,7 @@ def test_install_without_annotation() -> None:
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_install_unknown_annotation() -> None:
|
|
37
|
-
container = Container(
|
|
37
|
+
container = Container()
|
|
38
38
|
|
|
39
39
|
app = FastAPI()
|
|
40
40
|
|
|
@@ -15,7 +15,7 @@ def test_inject_param_missing_interface() -> None:
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_install_without_annotation() -> None:
|
|
18
|
-
container = Container(
|
|
18
|
+
container = Container()
|
|
19
19
|
|
|
20
20
|
@container.provider(scope="singleton")
|
|
21
21
|
def message() -> str:
|
|
@@ -34,7 +34,7 @@ def test_install_without_annotation() -> None:
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def test_install_unknown_annotation() -> None:
|
|
37
|
-
container = Container(
|
|
37
|
+
container = Container()
|
|
38
38
|
|
|
39
39
|
broker = RedisBroker()
|
|
40
40
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import logging
|
|
3
2
|
import sys
|
|
4
3
|
import threading
|
|
5
4
|
import uuid
|
|
@@ -35,12 +34,6 @@ def container() -> Container:
|
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
class TestContainer:
|
|
38
|
-
def test_default_properties(self) -> None:
|
|
39
|
-
container = Container()
|
|
40
|
-
|
|
41
|
-
assert not container.strict
|
|
42
|
-
assert container.default_scope == "transient"
|
|
43
|
-
|
|
44
37
|
def test_register_provider(self, container: Container) -> None:
|
|
45
38
|
def provider_call() -> str:
|
|
46
39
|
return "test"
|
|
@@ -1319,7 +1312,7 @@ class TestContainer:
|
|
|
1319
1312
|
def __init__(self, name: str) -> None:
|
|
1320
1313
|
self.name = name
|
|
1321
1314
|
|
|
1322
|
-
container = Container(
|
|
1315
|
+
container = Container()
|
|
1323
1316
|
|
|
1324
1317
|
instance = container.create(Component, name="test")
|
|
1325
1318
|
|
|
@@ -1331,7 +1324,7 @@ class TestContainer:
|
|
|
1331
1324
|
def __init__(self, name: str) -> None:
|
|
1332
1325
|
self.name = name
|
|
1333
1326
|
|
|
1334
|
-
container = Container(
|
|
1327
|
+
container = Container()
|
|
1335
1328
|
|
|
1336
1329
|
instance = container.create(Component, name="test")
|
|
1337
1330
|
|
|
@@ -1343,7 +1336,7 @@ class TestContainer:
|
|
|
1343
1336
|
def __init__(self, name: str) -> None:
|
|
1344
1337
|
self.name = name
|
|
1345
1338
|
|
|
1346
|
-
container = Container(
|
|
1339
|
+
container = Container()
|
|
1347
1340
|
|
|
1348
1341
|
with container.request_context():
|
|
1349
1342
|
instance = container.create(Component, name="test")
|
|
@@ -1355,7 +1348,7 @@ class TestContainer:
|
|
|
1355
1348
|
class Component:
|
|
1356
1349
|
pass
|
|
1357
1350
|
|
|
1358
|
-
container = Container(
|
|
1351
|
+
container = Container()
|
|
1359
1352
|
|
|
1360
1353
|
with pytest.raises(TypeError, match="takes no arguments"):
|
|
1361
1354
|
container.create(Component, param="test")
|
|
@@ -1366,7 +1359,7 @@ class TestContainer:
|
|
|
1366
1359
|
def __init__(self, name: str) -> None:
|
|
1367
1360
|
self.name = name
|
|
1368
1361
|
|
|
1369
|
-
container = Container(
|
|
1362
|
+
container = Container()
|
|
1370
1363
|
|
|
1371
1364
|
instance = await container.acreate(Component, name="test")
|
|
1372
1365
|
|
|
@@ -1378,7 +1371,7 @@ class TestContainer:
|
|
|
1378
1371
|
def __init__(self, name: str) -> None:
|
|
1379
1372
|
self.name = name
|
|
1380
1373
|
|
|
1381
|
-
container = Container(
|
|
1374
|
+
container = Container()
|
|
1382
1375
|
|
|
1383
1376
|
instance = await container.acreate(Component, name="test")
|
|
1384
1377
|
|
|
@@ -1390,7 +1383,7 @@ class TestContainer:
|
|
|
1390
1383
|
def __init__(self, name: str) -> None:
|
|
1391
1384
|
self.name = name
|
|
1392
1385
|
|
|
1393
|
-
container = Container(
|
|
1386
|
+
container = Container()
|
|
1394
1387
|
|
|
1395
1388
|
with container.request_context():
|
|
1396
1389
|
instance = await container.acreate(Component, name="test")
|
|
@@ -1402,7 +1395,7 @@ class TestContainer:
|
|
|
1402
1395
|
class Component:
|
|
1403
1396
|
pass
|
|
1404
1397
|
|
|
1405
|
-
container = Container(
|
|
1398
|
+
container = Container()
|
|
1406
1399
|
|
|
1407
1400
|
with pytest.raises(TypeError, match="takes no arguments"):
|
|
1408
1401
|
await container.acreate(Component, param="test")
|
|
@@ -1426,22 +1419,6 @@ class TestContainerInjector:
|
|
|
1426
1419
|
|
|
1427
1420
|
assert result == "service ident = 1000"
|
|
1428
1421
|
|
|
1429
|
-
def test_inject_auto_registered_log_message(
|
|
1430
|
-
self, container: Container, caplog: pytest.LogCaptureFixture
|
|
1431
|
-
) -> None:
|
|
1432
|
-
with caplog.at_level(logging.DEBUG, logger="anydi"):
|
|
1433
|
-
|
|
1434
|
-
@container.inject
|
|
1435
|
-
def handler(service: Service = auto) -> None:
|
|
1436
|
-
pass
|
|
1437
|
-
|
|
1438
|
-
assert caplog.messages == [
|
|
1439
|
-
"Cannot validate the `tests.test_container.TestContainerInjector"
|
|
1440
|
-
".test_inject_auto_registered_log_message.<locals>.handler` parameter "
|
|
1441
|
-
"`service` with an annotation of `tests.fixtures.Service due to "
|
|
1442
|
-
"being in non-strict mode. It will be validated at the first call."
|
|
1443
|
-
]
|
|
1444
|
-
|
|
1445
1422
|
def test_inject_missing_annotation(self, container: Container) -> None:
|
|
1446
1423
|
def handler(name=auto) -> str: # type: ignore[no-untyped-def]
|
|
1447
1424
|
return name # type: ignore[no-any-return]
|
|
@@ -1452,7 +1429,7 @@ class TestContainerInjector:
|
|
|
1452
1429
|
container.inject(handler)
|
|
1453
1430
|
|
|
1454
1431
|
def test_inject_unknown_dependency_using_strict_mode(self) -> None:
|
|
1455
|
-
container = Container(
|
|
1432
|
+
container = Container()
|
|
1456
1433
|
|
|
1457
1434
|
def handler(message: str = auto) -> None:
|
|
1458
1435
|
pass
|
|
@@ -20,7 +20,6 @@ class TestTestContainer:
|
|
|
20
20
|
test_container = TestContainer.from_container(container)
|
|
21
21
|
|
|
22
22
|
assert test_container.providers == container.providers
|
|
23
|
-
assert test_container.strict == container.strict
|
|
24
23
|
assert test_container.default_scope == container.default_scope
|
|
25
24
|
|
|
26
25
|
assert test_container.resolve(str) == "Hello, world!"
|
|
@@ -41,7 +40,7 @@ class TestTestContainer:
|
|
|
41
40
|
assert container.resolve(str) == origin_name
|
|
42
41
|
|
|
43
42
|
def test_override_instance_provider_not_registered_using_strict_mode(self) -> None:
|
|
44
|
-
container = TestContainer(
|
|
43
|
+
container = TestContainer()
|
|
45
44
|
|
|
46
45
|
with pytest.raises(
|
|
47
46
|
LookupError, match="The provider interface `str` not registered."
|
|
@@ -92,7 +91,7 @@ class TestTestContainer:
|
|
|
92
91
|
assert (await container.aresolve(str)) == overridden
|
|
93
92
|
|
|
94
93
|
def test_override_registered_instance(self) -> None:
|
|
95
|
-
container = TestContainer(
|
|
94
|
+
container = TestContainer()
|
|
96
95
|
container.register(Annotated[str, "param"], lambda: "param", scope="singleton")
|
|
97
96
|
|
|
98
97
|
class UserRepo:
|
|
@@ -126,7 +125,7 @@ class TestTestContainer:
|
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
async def test_override_instance_async_resolved(self) -> None:
|
|
129
|
-
container = TestContainer(
|
|
128
|
+
container = TestContainer()
|
|
130
129
|
container.register(Annotated[str, "param"], lambda: "param", scope="singleton")
|
|
131
130
|
|
|
132
131
|
@singleton
|
|
@@ -147,7 +146,7 @@ class TestTestContainer:
|
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
def test_override_instance_in_strict_mode(self) -> None:
|
|
150
|
-
container = TestContainer(
|
|
149
|
+
container = TestContainer()
|
|
151
150
|
|
|
152
151
|
class Settings:
|
|
153
152
|
def __init__(self, name: str) -> None:
|
|
@@ -166,7 +165,7 @@ class TestTestContainer:
|
|
|
166
165
|
assert service.ident == "test"
|
|
167
166
|
|
|
168
167
|
def test_override_instance_first(self) -> None:
|
|
169
|
-
container = TestContainer(
|
|
168
|
+
container = TestContainer()
|
|
170
169
|
|
|
171
170
|
@dataclass
|
|
172
171
|
class Item:
|
anydi-0.43.0/override.py
DELETED
|
File without changes
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
from fastapi import FastAPI
|
|
6
|
-
|
|
7
|
-
import anydi.ext.fastapi
|
|
8
|
-
from anydi import Container
|
|
9
|
-
from anydi.ext.fastapi import Inject
|
|
10
|
-
|
|
11
|
-
from tests.ext.fixtures import Mail, MailService
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_auto_register(caplog: pytest.LogCaptureFixture) -> None:
|
|
15
|
-
container = Container(strict=False)
|
|
16
|
-
|
|
17
|
-
app = FastAPI()
|
|
18
|
-
|
|
19
|
-
@app.post("/send-mail", response_model=Mail)
|
|
20
|
-
async def send_email(
|
|
21
|
-
mail_service: MailService = Inject(),
|
|
22
|
-
) -> Any:
|
|
23
|
-
return await mail_service.send_mail(email="test@mail.com", message="test")
|
|
24
|
-
|
|
25
|
-
with caplog.at_level(logging.DEBUG, logger="anydi"):
|
|
26
|
-
anydi.ext.fastapi.install(app, container)
|
|
27
|
-
|
|
28
|
-
assert caplog.messages == [
|
|
29
|
-
"Callable `tests.ext.fastapi.test_auto_register.test_auto_register.<locals>"
|
|
30
|
-
".send_email` injected parameter `mail_service` with an annotation of "
|
|
31
|
-
"`tests.ext.fixtures.MailService` is not registered. It will be "
|
|
32
|
-
"registered at runtime with the first call because it is running in "
|
|
33
|
-
"non-strict mode."
|
|
34
|
-
]
|
|
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
|
|
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
|
|
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
|
|
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
|