From b8919a08ea4f8f61cabec0b81038e41c77ce938c Mon Sep 17 00:00:00 2001 From: uttam12331 Date: Sat, 4 Jul 2026 00:25:31 +0530 Subject: [PATCH] Do not type DeclarativeContainer.__getattr__ as returning a Provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `__getattr__(self, name: str) -> Provider[Any]` stub lived on the base `Container`, so `DeclarativeContainer` inherited a catch-all that made type checkers accept access to any attribute — silencing typos and references to providers that were never declared. Move the stub to `DynamicContainer`, whose providers really are added by name at runtime. `DeclarativeContainer` now only exposes its statically declared providers, so accessing an undeclared one is flagged by the type checker while dynamic containers keep resolving attributes to `Provider[Any]`. Closes #910 --- src/dependency_injector/containers.pyi | 7 +++++-- tests/typing/dynamic_container.py | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dependency_injector/containers.pyi b/src/dependency_injector/containers.pyi index 3a928646..022aff20 100644 --- a/src/dependency_injector/containers.pyi +++ b/src/dependency_injector/containers.pyi @@ -57,7 +57,6 @@ class Container: def __init__(self) -> None: ... def __deepcopy__(self, memo: Optional[Dict[str, Any]]) -> _Self: ... def __setattr__(self, name: str, value: Union[Provider[Any], Any]) -> None: ... - def __getattr__(self, name: str) -> Provider[Any]: ... def __delattr__(self, name: str) -> None: ... def set_providers(self, **providers: Provider[Any]) -> None: ... def set_provider(self, name: str, provider: Provider[Any]) -> None: ... @@ -104,7 +103,11 @@ class Container: @overload def traverse(cls, types: Optional[Iterable[Type[TT]]] = None) -> Iterator[TT]: ... -class DynamicContainer(Container): ... +class DynamicContainer(Container): + # Providers are added dynamically by name, so attribute access must resolve + # to a Provider. This is intentionally absent from DeclarativeContainer, + # whose providers are declared statically (see issue #910). + def __getattr__(self, name: str) -> Provider[Any]: ... class DeclarativeContainer(Container): cls_providers: ClassVar[Dict[str, Provider[Any]]] diff --git a/tests/typing/dynamic_container.py b/tests/typing/dynamic_container.py index 9febded3..91831983 100644 --- a/tests/typing/dynamic_container.py +++ b/tests/typing/dynamic_container.py @@ -29,3 +29,7 @@ # Test 6: to check base class # NOTE: Using assignment to check base class instead of exact type container6: containers.Container = containers.DynamicContainer() + +# Test 7: dynamic attribute access resolves to a Provider (see issue #910) +container7 = containers.DynamicContainer() +assert_type(container7.some_provider, providers.Provider[Any])