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])