diff --git a/modern_di/types_parser.py b/modern_di/types_parser.py index 6926d404..f1c9871c 100644 --- a/modern_di/types_parser.py +++ b/modern_di/types_parser.py @@ -40,7 +40,10 @@ def from_type(cls, type_: type, default: object = UNSET) -> "SignatureItem": if isinstance(type_, types.UnionType) or origin is typing.Union: # A parameterized generic member degrades to its origin (list[str] -> list); see # test_union_member_degrades_to_bare_origin. - union_members = [typing.get_origin(x) or x for x in typing.get_args(type_)] + members = [ + typing.get_args(x)[0] if typing.get_origin(x) is typing.Annotated else x for x in typing.get_args(type_) + ] + union_members = [typing.get_origin(x) or x for x in members] non_none_members = [member for member in union_members if member is not types.NoneType] if len(non_none_members) != len(union_members): result["is_nullable"] = True diff --git a/tests/test_types_parser.py b/tests/test_types_parser.py index b022f43e..21cf4602 100644 --- a/tests/test_types_parser.py +++ b/tests/test_types_parser.py @@ -450,6 +450,52 @@ class Dependencies(Group): assert isinstance(Container(groups=[Dependencies]).resolve(_SignatureOnlyModel).dep, _Dep) +@pytest.mark.parametrize( + "annotation", + [ + typing.Annotated[_Dep, "meta"] | None, + typing.Optional[typing.Annotated[_Dep, "meta"]], # noqa: UP045 + typing.Annotated[typing.Annotated[_Dep, "inner"], "outer"] | None, + ], +) +def test_annotated_member_of_an_optional_on_signature_is_wired(annotation: object) -> None: + creator = _signature_only(_keyword("dep", annotation)) + + class Dependencies(Group): + dep = providers.Factory(_Dep, cache=True) + target = providers.Factory(creator, bound_type=None) + + container = Container(groups=[Dependencies]) + container.validate() + + assert container.resolve_provider(Dependencies.target)["dep"] is container.resolve(_Dep) + + +def test_annotated_member_of_an_optional_on_signature_falls_back_to_none() -> None: + creator = _signature_only(_keyword("dep", typing.Annotated[_Dep, "meta"] | None)) + + class Dependencies(Group): + target = providers.Factory(creator, bound_type=None) + + container = Container(groups=[Dependencies]) + container.validate() + + assert container.resolve_provider(Dependencies.target) == {"dep": None} + + +def test_annotated_members_of_a_union_on_signature_resolve_the_first_registered() -> None: + creator = _signature_only(_keyword("dep", typing.Annotated[_OtherDep, "a"] | typing.Annotated[_Dep, "b"])) + + class Dependencies(Group): + dep = providers.Factory(_Dep) + target = providers.Factory(creator, bound_type=None) + + container = Container(groups=[Dependencies]) + container.validate() + + assert isinstance(container.resolve_provider(Dependencies.target)["dep"], _Dep) + + def test_string_annotation_on_signature_is_not_wired() -> None: creator = _signature_only(_keyword("dep", "_Dep"))