Skip to content

Cannot use qualifiers on factory with Optional Types #138

Description

@ranjanprasad96

Qualifier is ignored on a factory returning Optional[T]

Summary

When a factory is registered with a qualifier and returns an Optional[T] type, the qualifier appears to be dropped during registration. Instead of registering the qualified factory, the container tries to resolve T as a plain (unqualified) dependency, which fails validation with a self-dependency error.

Could you please help me with this?
This is blocking integration of wireup into our codebase.

Environment

  • wireup: 2.10.0
  • Python: 3.9.x

Minimal reproduction

import asyncio
from typing import Optional

import wireup
from wireup import injectable


class AuthContext:
    pass


@injectable
async def require_authentication() -> AuthContext:
    return AuthContext()


# Factory with a qualifier that returns Optional[AuthContext] — this combination breaks.
@injectable(qualifier="optional")
async def maybe_get_authentication() -> Optional[AuthContext]:
    return None


async def main() -> None:
    container = wireup.create_async_container(
        injectables=[require_authentication, maybe_get_authentication],
    )


if __name__ == "__main__":
    asyncio.run(main())

Actual behavior

Container creation fails at registry validation:

WireupError: Parameter 'raw_type_instance' of <class '__main__.AuthContext'>
has an unknown dependency on <class '__main__.AuthContext'>.

Traceback (most recent call last):
  check_wireup.py:46  in <module>          asyncio.run(main())
  check_wireup.py:40  in main              container = wireup.create_async_container(...)
  wireup/ioc/container/__init__.py:241     in create_async_container → _create_container(...)
  wireup/ioc/container/__init__.py:114     in _create_container → ContainerRegistry(...)
  wireup/ioc/registry.py:97                in __init__ → self.extend(...)
  wireup/ioc/registry.py:137               in extend → validate_registry(self)
  wireup/ioc/registry_validation.py:30     in validate_registry → assert_dependency_exists(...)
  wireup/ioc/registry_validation.py:118    in assert_dependency_exists → raise WireupError(msg)

Expected behavior

The factory should be registered under the "optional" qualifier and resolve as Optional[AuthContext], without the qualifier being discarded or a spurious self-dependency being created.

Investigation

While debugging, it looks like the compatibility/Optional-unwrapping path does not carry the qualifier through when the return type is Optional[T]. Because the qualifier is lost, the factory ends up registered against the bare type T, which then appears to depend on itself — producing the raw_type_instance self-dependency error above. A non-Optional return type with the same qualifier registers fine.

Suggested fixes

File wireup 2.10.0: https://github.com/maldoinc/wireup/blob/v2.10.0/wireup/ioc/registry.py#L258
File wireup 2.11.0: https://github.com/maldoinc/wireup/blob/v2.11.0/wireup/ioc/registry.py#L396

               compat_fn.__signature__ = inspect.Signature(  # type: ignore[attr-defined]
                   parameters=[
                       inspect.Parameter(
                           "raw_type_instance",
                           kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
  -                        annotation=klass,
  +                        annotation=klass if qualifier is None else Annotated[klass, 
  InjectableQualifier(qualifier)], 
                       )
                   ],
               )

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions