From 9d3a43271e4cb3802a8fdf504656e126b99e0af8 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Fri, 10 Oct 2025 14:06:24 +0700 Subject: [PATCH 1/8] Fix providers.Resource missing overloads for AbstractContextManager and AbstractAsyncContextManager (#927) --- src/dependency_injector/providers.pyi | 15 +++++++ tests/typing/resource.py | 61 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index e4d62506..8f9b525a 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -1,5 +1,6 @@ from __future__ import annotations +from contextlib import AbstractContextManager, AbstractAsyncContextManager from pathlib import Path from typing import ( Awaitable, @@ -465,6 +466,20 @@ class Resource(Provider[T]): **kwargs: Injection, ) -> None: ... @overload + def __init__( + self, + provides: Optional[_Callable[..., AbstractContextManager[T]]] = None, + *args: Injection, + **kwargs: Injection, + ) -> None: ... + @overload + def __init__( + self, + provides: Optional[_Callable[..., AbstractAsyncContextManager[T]]] = None, + *args: Injection, + **kwargs: Injection, + ) -> None: ... + @overload def __init__( self, provides: Optional[_Callable[..., _Iterator[T]]] = None, diff --git a/tests/typing/resource.py b/tests/typing/resource.py index d01a5106..0d3bf254 100644 --- a/tests/typing/resource.py +++ b/tests/typing/resource.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager, asynccontextmanager from typing import ( Any, AsyncGenerator, @@ -7,6 +8,7 @@ from typing import ( Iterator, List, Optional, + Self, ) from dependency_injector import providers, resources @@ -109,3 +111,62 @@ async def _provide8() -> None: # Test 9: to check string imports provider9: providers.Resource[Dict[Any, Any]] = providers.Resource("builtins.dict") provider9.set_provides("builtins.dict") + + +# Test 10: to check the return type with classes implementing AbstractContextManager protocol +class MyResource10: + def __init__(self) -> None: + pass + + def __enter__(self) -> Self: + return self + + def __exit__(self, *args: Any, **kwargs: Any) -> None: + return None + + +provider10 = providers.Resource(MyResource10) +var10: MyResource10 = provider10() + + +# Test 11: to check the return type with functions decorated with contextlib.contextmanager +@contextmanager +def init11() -> Iterator[int]: + yield 1 + + +provider11 = providers.Resource(init11) +var11: int = provider11() + + +# Test 12: to check the return type with classes implementing AbstractAsyncContextManager protocol +class MyResource12: + def __init__(self) -> None: + pass + + async def __aenter__(self) -> Self: + return self + + async def __aexit__(self, *args: Any, **kwargs: Any) -> None: + return None + + +provider12 = providers.Resource(MyResource12) + + +async def _provide12() -> None: + var1: MyResource12 = await provider12() # type: ignore + var2: MyResource12 = await provider12.async_() + + +# Test 13: to check the return type with functions decorated with contextlib.asynccontextmanager +@asynccontextmanager +async def init13() -> AsyncIterator[int]: + yield 1 + + +provider13 = providers.Resource(init13) + +async def _provide13() -> None: + var1: int = await provider13() # type: ignore + var2: int = await provider13.async_() From 059f78b27ccecabc5f8cf68f181e3253002c8f34 Mon Sep 17 00:00:00 2001 From: AndrianEquestrian <88708533+AndrianEquestrian@users.noreply.github.com> Date: Thu, 23 Oct 2025 16:33:53 +0300 Subject: [PATCH 2/8] Fix FastDepends v3 compatibility (#933) --- src/dependency_injector/wiring.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dependency_injector/wiring.py b/src/dependency_injector/wiring.py index 211fdcde..ceeee74e 100644 --- a/src/dependency_injector/wiring.py +++ b/src/dependency_injector/wiring.py @@ -76,8 +76,18 @@ with suppress(ImportError): MARKER_EXTRACTORS.append(extract_marker_from_fastapi) -with suppress(ImportError): - from fast_depends.dependencies import Depends as FastDepends +with suppress(ImportError): # fast_depends >=3.0.0 + from fast_depends.dependencies.model import Dependant as FastDependant # type: ignore[attr-defined] + + def extract_marker_from_dependant_fast_depends(param: Any) -> Any: + if isinstance(param, FastDependant): + return param.dependency + return None + + MARKER_EXTRACTORS.append(extract_marker_from_dependant_fast_depends) + +with suppress(ImportError): # fast_depends <3.0.0 + from fast_depends.dependencies import Depends as FastDepends # type: ignore[attr-defined] def extract_marker_from_fast_depends(param: Any) -> Any: if isinstance(param, FastDepends): From 18e32521a00e54113fa442b2b0b506d5ca109609 Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Fri, 24 Oct 2025 19:37:08 +0700 Subject: [PATCH 3/8] Allow explicit typing on Selector using TypeVar with default Any (#932) --- pyproject.toml | 3 ++- src/dependency_injector/providers.pyi | 14 ++++++----- tests/typing/selector.py | 35 ++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fe89efaa..fc8b36c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,8 @@ dynamic = ["version"] dependencies = [ # typing.Annotated since v3.9 # typing.Self and typing.assert_never since v3.11 - "typing-extensions; python_version<'3.11'", + # typing.TypeVar default since v3.13 + "typing-extensions; python_version<'3.13'", ] [project.optional-dependencies] diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index 8f9b525a..a4443107 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -4,7 +4,6 @@ from contextlib import AbstractContextManager, AbstractAsyncContextManager from pathlib import Path from typing import ( Awaitable, - TypeVar, Generic, Type, Callable as _Callable, @@ -22,6 +21,8 @@ from typing import ( overload, ) +from typing_extensions import Self as _Self, TypeVar + try: import yaml except ImportError: @@ -38,6 +39,7 @@ Injection = Any ProviderParent = Union["Provider", Any] T = TypeVar("T") TT = TypeVar("TT") +T_Any = TypeVar("T_Any", default=Any) P = TypeVar("P", bound="Provider") BS = TypeVar("BS", bound="BaseSingleton") @@ -542,17 +544,17 @@ class Container(Provider[T]): def parent_name(self) -> Optional[str]: ... def assign_parent(self, parent: ProviderParent) -> None: ... -class Selector(Provider[Any]): +class Selector(Provider[T_Any]): def __init__( self, selector: Optional[_Callable[..., Any]] = None, **providers: Provider ): ... - def __getattr__(self, name: str) -> Provider: ... + def __getattr__(self, name: str) -> Provider[T_Any]: ... @property def selector(self) -> Optional[_Callable[..., Any]]: ... - def set_selector(self, selector: Optional[_Callable[..., Any]]) -> Selector: ... + def set_selector(self, selector: Optional[_Callable[..., Any]]) -> _Self: ... @property - def providers(self) -> _Dict[str, Provider]: ... - def set_providers(self, **providers: Provider) -> Selector: ... + def providers(self) -> _Dict[str, Provider[T_Any]]: ... + def set_providers(self, **providers: Provider) -> _Self: ... class ProvidedInstanceFluentInterface: def __getattr__(self, item: Any) -> AttributeGetter: ... diff --git a/tests/typing/selector.py b/tests/typing/selector.py index 5d89ec66..6297baf1 100644 --- a/tests/typing/selector.py +++ b/tests/typing/selector.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Callable, Optional, Dict from dependency_injector import providers @@ -40,3 +40,36 @@ provider4 = providers.Selector( async def _async4() -> None: var1: Any = await provider4() var2: Any = await provider4.async_() + + +# Test 5: to check selector getter and setter +provider5 = providers.Selector( + lambda: "a", + a=providers.Factory(object), + b=providers.Factory(object), +) +selector5: Optional[Callable[..., Any]] = provider5.selector +provider5_after_set_selector: providers.Selector[Any] = provider5.set_selector(lambda: "a") + +# Test 6: to check providers getter and setter +provider6 = providers.Selector( + lambda: "a", + a=providers.Factory(object), + b=providers.Factory(object), +) +providers6: Dict[str, providers.Provider[Any]] = provider6.providers +provider6_after_set_providers: providers.Selector[Any] = provider6.set_providers(c=providers.Factory(object)) + + +# Test 7: to check explicit typing: return type, getattr, getter/setter of providers and selectors +provider7 = providers.Selector[bool](lambda: "a", a=providers.Factory(bool), b=providers.Factory(int)) +var7: bool = provider7() +attr7: providers.Provider[bool] = provider7.a + +selector7: Optional[Callable[..., Any]] = provider7.selector +provider7_after_set_selector: providers.Selector[bool] = provider7.set_selector(lambda: "a") + +providers7: Dict[str, providers.Provider[bool]] = provider7.providers +provider7_after_set_providers: providers.Selector[bool] = provider7.set_providers( + c=providers.Factory(str) +) # We don't require Provider of subclass of bool yet since Provider is invariant From 009a86de2c4a44f747e4499b1ccdf475dd449ab9 Mon Sep 17 00:00:00 2001 From: ZipFile Date: Sat, 1 Nov 2025 13:04:53 +0000 Subject: [PATCH 4/8] Imporve dict typings --- src/dependency_injector/providers.pyi | 47 ++++++++++++++------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index a4443107..f7539881 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -1,23 +1,24 @@ from __future__ import annotations -from contextlib import AbstractContextManager, AbstractAsyncContextManager +from contextlib import AbstractAsyncContextManager, AbstractContextManager from pathlib import Path from typing import ( - Awaitable, - Generic, - Type, - Callable as _Callable, Any, - Tuple, - List as _List, - Dict as _Dict, - Optional, - Union, + AsyncIterator as _AsyncIterator, + Awaitable, + Callable as _Callable, Coroutine as _Coroutine, + Dict as _Dict, + Generator as _Generator, + Generic, Iterable as _Iterable, Iterator as _Iterator, - AsyncIterator as _AsyncIterator, - Generator as _Generator, + List as _List, + Mapping, + Optional, + Tuple, + Type, + Union, overload, ) @@ -68,7 +69,7 @@ class Provider(Generic[T]): @property def provider(self) -> Provider[T]: ... @property - def provided(self) -> ProvidedInstance[T]: ... + def provided(self) -> ProvidedInstance: ... def enable_async_mode(self) -> None: ... def disable_async_mode(self) -> None: ... def reset_async_mode(self) -> None: ... @@ -106,7 +107,7 @@ class Delegate(Provider[Provider]): class Aggregate(Provider[T]): def __init__( self, - provider_dict: Optional[_Dict[Any, Provider[T]]] = None, + provider_dict: Optional[Mapping[Any, Provider[T]]] = None, **provider_kwargs: Provider[T], ): ... def __getattr__(self, provider_name: Any) -> Provider[T]: ... @@ -125,7 +126,7 @@ class Aggregate(Provider[T]): def providers(self) -> _Dict[Any, Provider[T]]: ... def set_providers( self, - provider_dict: Optional[_Dict[Any, Provider[T]]] = None, + provider_dict: Optional[Mapping[Any, Provider[T]]] = None, **provider_kwargs: Provider[T], ) -> Aggregate[T]: ... @@ -183,7 +184,7 @@ class Callable(Provider[T]): def set_args(self, *args: Injection) -> Callable[T]: ... def clear_args(self) -> Callable[T]: ... @property - def kwargs(self) -> _Dict[Any, Injection]: ... + def kwargs(self) -> _Dict[str, Injection]: ... def add_kwargs(self, **kwargs: Injection) -> Callable[T]: ... def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ... def clear_kwargs(self) -> Callable[T]: ... @@ -355,7 +356,7 @@ class Factory(Provider[T]): def set_args(self, *args: Injection) -> Factory[T]: ... def clear_args(self) -> Factory[T]: ... @property - def kwargs(self) -> _Dict[Any, Injection]: ... + def kwargs(self) -> _Dict[str, Injection]: ... def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ... def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ... def clear_kwargs(self) -> Factory[T]: ... @@ -379,7 +380,7 @@ class FactoryAggregate(Aggregate[T]): def factories(self) -> _Dict[Any, Factory[T]]: ... def set_factories( self, - provider_dict: Optional[_Dict[Any, Factory[T]]] = None, + provider_dict: Optional[Mapping[Any, Factory[T]]] = None, **provider_kwargs: Factory[T], ) -> FactoryAggregate[T]: ... @@ -404,7 +405,7 @@ class BaseSingleton(Provider[T]): def set_args(self, *args: Injection) -> BaseSingleton[T]: ... def clear_args(self) -> BaseSingleton[T]: ... @property - def kwargs(self) -> _Dict[Any, Injection]: ... + def kwargs(self) -> _Dict[str, Injection]: ... def add_kwargs(self, **kwargs: Injection) -> BaseSingleton[T]: ... def set_kwargs(self, **kwargs: Injection) -> BaseSingleton[T]: ... def clear_kwargs(self) -> BaseSingleton[T]: ... @@ -440,15 +441,15 @@ class List(Provider[_List]): class Dict(Provider[_Dict]): def __init__( - self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection + self, dict_: Optional[Mapping[Any, Injection]] = None, **kwargs: Injection ): ... @property def kwargs(self) -> _Dict[Any, Injection]: ... def add_kwargs( - self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection + self, dict_: Optional[Mapping[Any, Injection]] = None, **kwargs: Injection ) -> Dict: ... def set_kwargs( - self, dict_: Optional[_Dict[Any, Injection]] = None, **kwargs: Injection + self, dict_: Optional[Mapping[Any, Injection]] = None, **kwargs: Injection ) -> Dict: ... def clear_kwargs(self) -> Dict: ... @@ -518,7 +519,7 @@ class Resource(Provider[T]): def set_args(self, *args: Injection) -> Resource[T]: ... def clear_args(self) -> Resource[T]: ... @property - def kwargs(self) -> _Dict[Any, Injection]: ... + def kwargs(self) -> _Dict[str, Injection]: ... def add_kwargs(self, **kwargs: Injection) -> Resource[T]: ... def set_kwargs(self, **kwargs: Injection) -> Resource[T]: ... def clear_kwargs(self) -> Resource[T]: ... From d72d07caf751669d6d40e7fc12a2aa97816e6069 Mon Sep 17 00:00:00 2001 From: ZipFile Date: Sat, 1 Nov 2025 13:45:08 +0000 Subject: [PATCH 5/8] Improve type annotations in providers --- src/dependency_injector/providers.pyi | 134 ++++++++++++-------------- 1 file changed, 64 insertions(+), 70 deletions(-) diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index f7539881..8ba2e98d 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -89,7 +89,7 @@ class Object(Provider[T]): def __init__(self, provides: Optional[T] = None) -> None: ... @property def provides(self) -> Optional[T]: ... - def set_provides(self, provides: Optional[T]) -> Object: ... + def set_provides(self, provides: Optional[T]) -> _Self: ... class Self(Provider[T]): def __init__(self, container: Optional[T] = None) -> None: ... @@ -102,7 +102,7 @@ class Delegate(Provider[Provider]): def __init__(self, provides: Optional[Provider] = None) -> None: ... @property def provides(self) -> Optional[Provider]: ... - def set_provides(self, provides: Optional[Provider]) -> Delegate: ... + def set_provides(self, provides: Optional[Provider]) -> _Self: ... class Aggregate(Provider[T]): def __init__( @@ -128,7 +128,7 @@ class Aggregate(Provider[T]): self, provider_dict: Optional[Mapping[Any, Provider[T]]] = None, **provider_kwargs: Provider[T], - ) -> Aggregate[T]: ... + ) -> _Self: ... class Dependency(Provider[T]): def __init__( @@ -139,10 +139,10 @@ class Dependency(Provider[T]): def __getattr__(self, name: str) -> Any: ... @property def instance_of(self) -> Type[T]: ... - def set_instance_of(self, instance_of: Type[T]) -> Dependency[T]: ... + def set_instance_of(self, instance_of: Type[T]) -> _Self: ... @property def default(self) -> Provider[T]: ... - def set_default(self, default: Optional[Union[Provider, Any]]) -> Dependency[T]: ... + def set_default(self, default: Optional[Union[Provider, Any]]) -> _Self: ... @property def is_defined(self) -> bool: ... def provided_by(self, provider: Provider) -> OverridingContext[P]: ... @@ -166,28 +166,28 @@ class DependenciesContainer(Object): def parent_name(self) -> Optional[str]: ... def assign_parent(self, parent: ProviderParent) -> None: ... -class Callable(Provider[T]): +class Callable(Provider[T_Any]): def __init__( self, - provides: Optional[Union[_Callable[..., T], str]] = None, + provides: Optional[Union[_Callable[..., T_Any], str]] = None, *args: Injection, **kwargs: Injection, ) -> None: ... @property - def provides(self) -> Optional[_Callable[..., T]]: ... + def provides(self) -> Optional[_Callable[..., T_Any]]: ... def set_provides( - self, provides: Optional[Union[_Callable[..., T], str]] - ) -> Callable[T]: ... + self, provides: Optional[Union[_Callable[..., T_Any], str]] + ) -> _Self: ... @property def args(self) -> Tuple[Injection]: ... - def add_args(self, *args: Injection) -> Callable[T]: ... - def set_args(self, *args: Injection) -> Callable[T]: ... - def clear_args(self) -> Callable[T]: ... + def add_args(self, *args: Injection) -> _Self: ... + def set_args(self, *args: Injection) -> _Self: ... + def clear_args(self) -> _Self: ... @property def kwargs(self) -> _Dict[str, Injection]: ... - def add_kwargs(self, **kwargs: Injection) -> Callable[T]: ... - def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ... - def clear_kwargs(self) -> Callable[T]: ... + def add_kwargs(self, **kwargs: Injection) -> _Self: ... + def set_kwargs(self, **kwargs: Injection) -> _Self: ... + def clear_kwargs(self) -> _Self: ... class DelegatedCallable(Callable[T]): ... @@ -209,7 +209,7 @@ class CoroutineDelegate(Delegate): class ConfigurationOption(Provider[Any]): UNDEFINED: object def __init__(self, name: Tuple[str], root: Configuration) -> None: ... - def __enter__(self) -> ConfigurationOption: ... + def __enter__(self) -> _Self: ... def __exit__(self, *exc_info: Any) -> None: ... def __getattr__(self, item: str) -> ConfigurationOption: ... def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ... @@ -274,30 +274,26 @@ class Configuration(Object[Any]): json_files: Optional[_Iterable[Union[Path, str]]] = None, pydantic_settings: Optional[_Iterable[PydanticSettings]] = None, ) -> None: ... - def __enter__(self) -> Configuration: ... + def __enter__(self) -> _Self: ... def __exit__(self, *exc_info: Any) -> None: ... def __getattr__(self, item: str) -> ConfigurationOption: ... def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ... def get_name(self) -> str: ... - def set_name(self, name: str) -> Configuration: ... + def set_name(self, name: str) -> _Self: ... def get_default(self) -> _Dict[Any, Any]: ... - def set_default(self, default: _Dict[Any, Any]): ... + def set_default(self, default: _Dict[Any, Any]) -> _Self: ... def get_strict(self) -> bool: ... - def set_strict(self, strict: bool) -> Configuration: ... + def set_strict(self, strict: bool) -> _Self: ... def get_children(self) -> _Dict[str, ConfigurationOption]: ... - def set_children( - self, children: _Dict[str, ConfigurationOption] - ) -> Configuration: ... + def set_children(self, children: _Dict[str, ConfigurationOption]) -> _Self: ... def get_ini_files(self) -> _List[Union[Path, str]]: ... - def set_ini_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ... + def set_ini_files(self, files: _Iterable[Union[Path, str]]) -> _Self: ... def get_yaml_files(self) -> _List[Union[Path, str]]: ... - def set_yaml_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ... + def set_yaml_files(self, files: _Iterable[Union[Path, str]]) -> _Self: ... def get_json_files(self) -> _List[Union[Path, str]]: ... - def set_json_files(self, files: _Iterable[Union[Path, str]]) -> Configuration: ... + def set_json_files(self, files: _Iterable[Union[Path, str]]) -> _Self: ... def get_pydantic_settings(self) -> _List[PydanticSettings]: ... - def set_pydantic_settings( - self, settings: _Iterable[PydanticSettings] - ) -> Configuration: ... + def set_pydantic_settings(self, settings: _Iterable[PydanticSettings]) -> _Self: ... def load(self, required: bool = False, envs_required: bool = False) -> None: ... def get(self, selector: str) -> Any: ... def set(self, selector: str, value: Any) -> OverridingContext[P]: ... @@ -349,22 +345,22 @@ class Factory(Provider[T]): def provides(self) -> Optional[_Callable[..., T]]: ... def set_provides( self, provides: Optional[Union[_Callable[..., T], str]] - ) -> Factory[T]: ... + ) -> _Self: ... @property def args(self) -> Tuple[Injection]: ... - def add_args(self, *args: Injection) -> Factory[T]: ... - def set_args(self, *args: Injection) -> Factory[T]: ... - def clear_args(self) -> Factory[T]: ... + def add_args(self, *args: Injection) -> _Self: ... + def set_args(self, *args: Injection) -> _Self: ... + def clear_args(self) -> _Self: ... @property def kwargs(self) -> _Dict[str, Injection]: ... - def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ... - def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ... - def clear_kwargs(self) -> Factory[T]: ... + def add_kwargs(self, **kwargs: Injection) -> _Self: ... + def set_kwargs(self, **kwargs: Injection) -> _Self: ... + def clear_kwargs(self) -> _Self: ... @property - def attributes(self) -> _Dict[Any, Injection]: ... - def add_attributes(self, **kwargs: Injection) -> Factory[T]: ... - def set_attributes(self, **kwargs: Injection) -> Factory[T]: ... - def clear_attributes(self) -> Factory[T]: ... + def attributes(self) -> _Dict[str, Injection]: ... + def add_attributes(self, **kwargs: Injection) -> _Self: ... + def set_attributes(self, **kwargs: Injection) -> _Self: ... + def clear_attributes(self) -> _Self: ... class DelegatedFactory(Factory[T]): ... @@ -398,22 +394,22 @@ class BaseSingleton(Provider[T]): def provides(self) -> Optional[_Callable[..., T]]: ... def set_provides( self, provides: Optional[Union[_Callable[..., T], str]] - ) -> BaseSingleton[T]: ... + ) -> _Self: ... @property def args(self) -> Tuple[Injection]: ... - def add_args(self, *args: Injection) -> BaseSingleton[T]: ... - def set_args(self, *args: Injection) -> BaseSingleton[T]: ... - def clear_args(self) -> BaseSingleton[T]: ... + def add_args(self, *args: Injection) -> _Self: ... + def set_args(self, *args: Injection) -> _Self: ... + def clear_args(self) -> _Self: ... @property def kwargs(self) -> _Dict[str, Injection]: ... - def add_kwargs(self, **kwargs: Injection) -> BaseSingleton[T]: ... - def set_kwargs(self, **kwargs: Injection) -> BaseSingleton[T]: ... - def clear_kwargs(self) -> BaseSingleton[T]: ... + def add_kwargs(self, **kwargs: Injection) -> _Self: ... + def set_kwargs(self, **kwargs: Injection) -> _Self: ... + def clear_kwargs(self) -> _Self: ... @property - def attributes(self) -> _Dict[Any, Injection]: ... - def add_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ... - def set_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ... - def clear_attributes(self) -> BaseSingleton[T]: ... + def attributes(self) -> _Dict[str, Injection]: ... + def add_attributes(self, **kwargs: Injection) -> _Self: ... + def set_attributes(self, **kwargs: Injection) -> _Self: ... + def clear_attributes(self) -> _Self: ... def reset(self) -> SingletonResetContext[BS]: ... def full_reset(self) -> SingletonFullResetContext[BS]: ... @@ -435,9 +431,9 @@ class List(Provider[_List]): def __init__(self, *args: Injection): ... @property def args(self) -> Tuple[Injection]: ... - def add_args(self, *args: Injection) -> List[T]: ... - def set_args(self, *args: Injection) -> List[T]: ... - def clear_args(self) -> List[T]: ... + def add_args(self, *args: Injection) -> _Self: ... + def set_args(self, *args: Injection) -> _Self: ... + def clear_args(self) -> _Self: ... class Dict(Provider[_Dict]): def __init__( @@ -447,11 +443,11 @@ class Dict(Provider[_Dict]): def kwargs(self) -> _Dict[Any, Injection]: ... def add_kwargs( self, dict_: Optional[Mapping[Any, Injection]] = None, **kwargs: Injection - ) -> Dict: ... + ) -> _Self: ... def set_kwargs( self, dict_: Optional[Mapping[Any, Injection]] = None, **kwargs: Injection - ) -> Dict: ... - def clear_kwargs(self) -> Dict: ... + ) -> _Self: ... + def clear_kwargs(self) -> _Self: ... class Resource(Provider[T]): @overload @@ -512,17 +508,17 @@ class Resource(Provider[T]): ) -> None: ... @property def provides(self) -> Optional[_Callable[..., Any]]: ... - def set_provides(self, provides: Optional[Any]) -> Resource[T]: ... + def set_provides(self, provides: Optional[Any]) -> _Self: ... @property def args(self) -> Tuple[Injection]: ... - def add_args(self, *args: Injection) -> Resource[T]: ... - def set_args(self, *args: Injection) -> Resource[T]: ... - def clear_args(self) -> Resource[T]: ... + def add_args(self, *args: Injection) -> _Self: ... + def set_args(self, *args: Injection) -> _Self: ... + def clear_args(self) -> _Self: ... @property def kwargs(self) -> _Dict[str, Injection]: ... - def add_kwargs(self, **kwargs: Injection) -> Resource[T]: ... - def set_kwargs(self, **kwargs: Injection) -> Resource[T]: ... - def clear_kwargs(self) -> Resource[T]: ... + def add_kwargs(self, **kwargs: Injection) -> _Self: ... + def set_kwargs(self, **kwargs: Injection) -> _Self: ... + def clear_kwargs(self) -> _Self: ... @property def initialized(self) -> bool: ... def init(self) -> Optional[Awaitable[T]]: ... @@ -563,9 +559,7 @@ class ProvidedInstanceFluentInterface: def call(self, *args: Injection, **kwargs: Injection) -> MethodCaller: ... @property def provides(self) -> Optional[Provider]: ... - def set_provides( - self, provides: Optional[Provider] - ) -> ProvidedInstanceFluentInterface: ... + def set_provides(self, provides: Optional[Provider]) -> _Self: ... class ProvidedInstance(Provider, ProvidedInstanceFluentInterface): def __init__(self, provides: Optional[Provider] = None) -> None: ... @@ -576,7 +570,7 @@ class AttributeGetter(Provider, ProvidedInstanceFluentInterface): ) -> None: ... @property def name(self) -> Optional[str]: ... - def set_name(self, name: Optional[str]) -> ProvidedInstanceFluentInterface: ... + def set_name(self, name: Optional[str]) -> _Self: ... class ItemGetter(Provider, ProvidedInstanceFluentInterface): def __init__( @@ -584,7 +578,7 @@ class ItemGetter(Provider, ProvidedInstanceFluentInterface): ) -> None: ... @property def name(self) -> Optional[str]: ... - def set_name(self, name: Optional[str]) -> ProvidedInstanceFluentInterface: ... + def set_name(self, name: Optional[str]) -> _Self: ... class MethodCaller(Provider, ProvidedInstanceFluentInterface): def __init__( From 244deee75ff87c43e92b81ccbe0a062ad8f44c3d Mon Sep 17 00:00:00 2001 From: Leonardus Chen Date: Sat, 15 Nov 2025 17:00:23 +0800 Subject: [PATCH 6/8] Retrofit assert_type for Aggregate and Callable (#935) --- tests/typing/aggregate.py | 37 +++++++++++++++++------------ tests/typing/callable.py | 50 ++++++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/tests/typing/aggregate.py b/tests/typing/aggregate.py index 1f134cb0..6518257a 100644 --- a/tests/typing/aggregate.py +++ b/tests/typing/aggregate.py @@ -1,4 +1,5 @@ from dependency_injector import providers +from typing_extensions import assert_type, Any class Animal: ... @@ -8,29 +9,35 @@ class Cat(Animal): ... # Test 1: to check Aggregate provider -provider1: providers.Aggregate[str] = providers.Aggregate( +provider1 = providers.Aggregate( a=providers.Object("str1"), b=providers.Object("str2"), ) -provider_a_1: providers.Provider[str] = provider1.a +provider_a_1 = provider1.a provider_b_1: providers.Provider[str] = provider1.b -val1: str = provider1("a") +val1 = provider1("a") +assert_type(provider1, providers.Aggregate[str]) +assert_type(provider_a_1, providers.Provider[str]) +assert_type(provider_b_1, providers.Provider[str]) +assert_type(val1, str) -provider1_set_non_string_keys: providers.Aggregate[str] = providers.Aggregate() +provider1_set_non_string_keys = providers.Aggregate[str]() provider1_set_non_string_keys.set_providers({Cat: providers.Object("str")}) -provider_set_non_string_1: providers.Provider[str] = ( - provider1_set_non_string_keys.providers[Cat] -) +provider_set_non_string_1 = provider1_set_non_string_keys.providers[Cat] +assert_type(provider_set_non_string_1, providers.Provider[str]) -provider1_new_non_string_keys: providers.Aggregate[str] = providers.Aggregate( + +provider1_new_non_string_keys = providers.Aggregate( {Cat: providers.Object("str")}, ) -factory_new_non_string_1: providers.Provider[str] = ( - provider1_new_non_string_keys.providers[Cat] -) +factory_new_non_string_1 = provider1_new_non_string_keys.providers[Cat] +assert_type(provider1_new_non_string_keys, providers.Aggregate[str]) +assert_type(factory_new_non_string_1, providers.Provider[str]) + provider1_no_explicit_typing = providers.Aggregate(a=providers.Object("str")) -provider1_no_explicit_typing_factory: providers.Provider[str] = ( - provider1_no_explicit_typing.providers["a"] -) -provider1_no_explicit_typing_object: str = provider1_no_explicit_typing("a") +provider1_no_explicit_typing_factory = provider1_no_explicit_typing.providers["a"] +provider1_no_explicit_typing_object = provider1_no_explicit_typing("a") + +assert_type(provider1_no_explicit_typing_factory, providers.Provider[str]) +assert_type(provider1_no_explicit_typing_object, str) diff --git a/tests/typing/callable.py b/tests/typing/callable.py index 8d345d3b..3ee02ac4 100644 --- a/tests/typing/callable.py +++ b/tests/typing/callable.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Optional, Tuple, Type +from typing import Any, Callable, Dict, Optional, Tuple +from typing_extensions import assert_type from dependency_injector import providers @@ -7,7 +8,6 @@ class Animal: ... class Cat(Animal): - @classmethod def create(cls) -> Animal: return cls() @@ -15,11 +15,13 @@ class Cat(Animal): # Test 1: to check the return type (class) provider1 = providers.Callable(Cat) -animal1: Animal = provider1(1, 2, 3, b="1", c=2, e=0.0) +cat1 = provider1(1, 2, 3, b="1", c=2, e=0.0) +assert_type(cat1, Cat) # Test 2: to check the return type (class factory method) provider2 = providers.Callable(Cat.create) -animal2: Animal = provider2() +animal2 = provider2() +assert_type(animal2, Animal) # Test 3: to check the .override() method provider3 = providers.Callable(Animal) @@ -28,24 +30,32 @@ with provider3.override(providers.Callable(Cat)): # Test 4: to check the .args & .kwargs attributes provider4 = providers.Callable(Animal) -args4: Tuple[Any] = provider4.args -kwargs4: Dict[str, Any] = provider4.kwargs +args4 = provider4.args +kwargs4 = provider4.kwargs +assert_type(args4, Tuple[Any]) +assert_type(kwargs4, Dict[str, Any]) # Test 5: to check the provided instance interface provider5 = providers.Callable(Animal) -provided5: Animal = provider5.provided() -attr_getter5: providers.AttributeGetter = provider5.provided.attr -item_getter5: providers.ItemGetter = provider5.provided["item"] -method_caller: providers.MethodCaller = provider5.provided.method.call(123, arg=324) +provided_val5 = provider5.provided() +attr_getter5 = provider5.provided.attr +item_getter5 = provider5.provided["item"] +method_caller5 = provider5.provided.method.call(123, arg=324) +assert_type(provided_val5, Any) +assert_type(attr_getter5, providers.AttributeGetter) +assert_type(item_getter5, providers.ItemGetter) +assert_type(method_caller5, providers.MethodCaller) # Test 6: to check the DelegatedCallable provider6 = providers.DelegatedCallable(Cat) -animal6: Animal = provider6(1, 2, 3, b="1", c=2, e=0.0) +cat6 = provider6(1, 2, 3, b="1", c=2, e=0.0) +assert_type(cat6, Cat) # Test 7: to check the AbstractCallable provider7 = providers.AbstractCallable(Animal) provider7.override(providers.Callable(Cat)) -animal7: Animal = provider7(1, 2, 3, b="1", c=2, e=0.0) +animal7 = provider7(1, 2, 3, b="1", c=2, e=0.0) +assert_type(animal7, Animal) # Test 8: to check the CallableDelegate __init__ provider8 = providers.CallableDelegate(providers.Callable(lambda: None)) @@ -55,20 +65,22 @@ provider9 = providers.Callable(Cat) async def _async9() -> None: - animal1: Animal = await provider9(1, 2, 3, b="1", c=2, e=0.0) # type: ignore - animal2: Animal = await provider9.async_(1, 2, 3, b="1", c=2, e=0.0) + await provider9(1, 2, 3, b="1", c=2, e=0.0) # type: ignore[misc] + cat9 = await provider9.async_(1, 2, 3, b="1", c=2, e=0.0) + assert_type(cat9, Cat) # Test 10: to check the .provides provider10 = providers.Callable(Cat) -provides10: Optional[Callable[..., Cat]] = provider10.provides -assert provides10 is Cat +provides10 = provider10.provides +assert_type(provides10, Optional[Callable[..., Cat]]) # Test 11: to check the .provides for explicit typevar provider11 = providers.Callable[Animal](Cat) -provides11: Optional[Callable[..., Animal]] = provider11.provides -assert provides11 is Cat +provides11 = provider11.provides +assert_type(provides11, Optional[Callable[..., Animal]]) + # Test 12: to check string imports -provider12: providers.Callable[Dict[Any, Any]] = providers.Callable("builtins.dict") +provider12 = providers.Callable("builtins.dict") provider12.set_provides("builtins.dict") From 51f818720211f7257d0372bda37372f067a3347d Mon Sep 17 00:00:00 2001 From: Brian Pugh Date: Thu, 4 Dec 2025 13:12:43 -0500 Subject: [PATCH 7/8] Allow annotated marker to be anywhere in the annotation list (#939) --- src/dependency_injector/wiring.py | 33 +++++++++---------- tests/unit/samples/wiring/module_annotated.py | 7 ++++ .../provider_ids/test_main_annotated_py36.py | 11 +++++++ 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/dependency_injector/wiring.py b/src/dependency_injector/wiring.py index ceeee74e..6b13e05c 100644 --- a/src/dependency_injector/wiring.py +++ b/src/dependency_injector/wiring.py @@ -682,23 +682,18 @@ def _unpatch_attribute(patched: PatchedAttribute) -> None: def _extract_marker(parameter: inspect.Parameter) -> Optional["_Marker"]: if get_origin(parameter.annotation) is Annotated: - args = get_args(parameter.annotation) - if len(args) > 1: - marker = args[1] - else: - marker = None + candidates = get_args(parameter.annotation)[1:] else: - marker = parameter.default + candidates = (parameter.default,) - for marker_extractor in MARKER_EXTRACTORS: - if _marker := marker_extractor(marker): - marker = _marker - break - - if not isinstance(marker, _Marker): - return None - - return marker + for marker in candidates: + for marker_extractor in MARKER_EXTRACTORS: + if _marker := marker_extractor(marker): + marker = _marker + break + if _is_marker(marker): + return marker + return None @cache @@ -1223,9 +1218,11 @@ def _get_members_and_annotated(obj: Any) -> Iterable[Tuple[str, Any]]: for annotation_name, annotation in annotations.items(): if get_origin(annotation) is Annotated: args = get_args(annotation) - if len(args) > 1: - member = args[1] - members.append((annotation_name, member)) + # Search through all metadata items (args[1:]) for a DI marker + for arg in args[1:]: + if _is_marker(arg): + members.append((annotation_name, arg)) + break return members diff --git a/tests/unit/samples/wiring/module_annotated.py b/tests/unit/samples/wiring/module_annotated.py index f954d0cb..3af049f2 100644 --- a/tests/unit/samples/wiring/module_annotated.py +++ b/tests/unit/samples/wiring/module_annotated.py @@ -124,3 +124,10 @@ def test_class_decorator(service: Annotated[Service, Provide[Container.service]] def test_container(container: Annotated[Container, Provide[Container]]): return container.service() + + +@inject +def test_annotated_with_non_di_metadata_first( + service: Annotated[Service, "some other annotated value", Provide[Container.service]], +): + return service diff --git a/tests/unit/wiring/provider_ids/test_main_annotated_py36.py b/tests/unit/wiring/provider_ids/test_main_annotated_py36.py index 34d1d747..5e289cd0 100644 --- a/tests/unit/wiring/provider_ids/test_main_annotated_py36.py +++ b/tests/unit/wiring/provider_ids/test_main_annotated_py36.py @@ -174,3 +174,14 @@ def test_class_decorator(): def test_container(): service = module.test_container() assert isinstance(service, Service) + + +def test_annotated_with_non_di_metadata_first(): + """Test that Annotated works when DI marker is not the first metadata item. + + This tests the case where Annotated has other metadata (like docstrings or + other annotations) before the Provide marker, e.g.: + Annotated[Service, "some doc", Provide[Container.service]] + """ + service = module.test_annotated_with_non_di_metadata_first() + assert isinstance(service, Service) From 1510a8479732229bd851f74b9cf28520f59760d7 Mon Sep 17 00:00:00 2001 From: ZipFile Date: Thu, 4 Dec 2025 18:27:35 +0000 Subject: [PATCH 8/8] Bump version --- docs/main/changelog.rst | 7 +++++++ src/dependency_injector/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index a9ec5880..247c2b74 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -7,6 +7,13 @@ that were made in every particular version. From version 0.7.6 *Dependency Injector* framework strictly follows `Semantic versioning`_ +4.48.3 +------ + +- Allow annotated marker to be anywhere in the annotation list. Thanks to `@BrianPugh `_ for `#939 `_. +- Fix FastDepends v3 compatibility. Thanks to `@AndrianEquestrian `_ for `#933 `_. +- Various type annotation improvements for providers. Thanks to `@leonarduschen `_ for `#927 `_, `#932 `_ and `#935 `_. + 4.48.2 ------ diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py index 404510f4..3c45728b 100644 --- a/src/dependency_injector/__init__.py +++ b/src/dependency_injector/__init__.py @@ -1,6 +1,6 @@ """Top-level package.""" -__version__ = "4.48.2" +__version__ = "4.48.3" """Version number. :type: str