Open generics

What you’ll learn

  • Register open-generic providers and resolve closed generic keys.

  • Understand precedence and validation behavior.

Factory type argument

Run locally

uv run python examples/ex_08_open_generics/01_factory_type_argument.py
"""Focused example: open generic factory with ``type[T]`` injection."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Generic, TypeVar, cast

from diwire import Container

T = TypeVar("T")


class IBox(Generic[T]):
    pass


@dataclass(slots=True)
class Box(IBox[T]):
    type_arg: type[T]


def build_box(type_arg: type[T]) -> IBox[T]:
    return Box(type_arg=type_arg)


def main() -> None:
    container = Container()
    container.add_factory(build_box, provides=IBox)

    resolved = cast("Box[int]", container.resolve(IBox[int]))
    print(f"box_int={resolved.type_arg.__name__}")  # => box_int=int


if __name__ == "__main__":
    main()

Closed override

Run locally

uv run python examples/ex_08_open_generics/02_closed_override.py
"""Focused example: closed generic override beats open template."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Generic, TypeVar

from diwire import Container

T = TypeVar("T")


class IBox(Generic[T]):
    pass


@dataclass(slots=True)
class Box(IBox[T]):
    type_arg: type[T]


class _SpecialIntBox(IBox[int]):
    pass


def main() -> None:
    container = Container()
    container.add(Box, provides=IBox)
    container.add(_SpecialIntBox, provides=IBox[int])

    resolved = container.resolve(IBox[int])
    print(f"override={type(resolved).__name__}")  # => override=_SpecialIntBox


if __name__ == "__main__":
    main()

Specificity winner

Run locally

uv run python examples/ex_08_open_generics/03_specificity_winner.py
"""Focused example: most-specific open template selection."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Generic, TypeVar, cast

from diwire import Container

T = TypeVar("T")
U = TypeVar("U")


class Repo(Generic[T]):
    pass


@dataclass(slots=True)
class GenericRepo(Repo[T]):
    dependency_type: type[T]


@dataclass(slots=True)
class ListRepo(Repo[list[U]]):
    item_type: type[U]


def main() -> None:
    container = Container()
    container.add(GenericRepo, provides=Repo)
    container.add(ListRepo, provides=Repo[list[U]])

    resolved = cast("ListRepo[int]", container.resolve(Repo[list[int]]))
    print(f"specificity_item={resolved.item_type.__name__}")  # => specificity_item=int


if __name__ == "__main__":
    main()

Scoped open generics

Run locally

uv run python examples/ex_08_open_generics/04_scoped_open_generics.py
"""Focused example: scoped open generics require an opened scope."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Generic, TypeVar

from diwire import Container, Lifetime, Scope
from diwire.exceptions import DIWireScopeMismatchError

T = TypeVar("T")


class IBox(Generic[T]):
    pass


@dataclass(slots=True)
class Box(IBox[T]):
    type_arg: type[T]


def build_box(type_arg: type[T]) -> IBox[T]:
    return Box(type_arg=type_arg)


def main() -> None:
    container = Container()
    container.add_factory(
        build_box,
        provides=IBox,
        scope=Scope.REQUEST,
        lifetime=Lifetime.SCOPED,
    )

    try:
        container.resolve(IBox[int])
    except DIWireScopeMismatchError:
        requires_scope = True
    else:
        requires_scope = False

    print(f"scoped_requires_scope={requires_scope}")  # => scoped_requires_scope=True


if __name__ == "__main__":
    main()

Constraint details

Run locally

uv run python examples/ex_08_open_generics/05_open_generics_constraints_details.py
"""Open-generics constraints deep dive.

This focused script shows constrained ``TypeVar`` behavior with both valid and
invalid resolutions.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Generic, TypeVar, cast

from diwire import Container
from diwire.exceptions import DIWireInvalidGenericTypeArgumentError

Allowed = TypeVar("Allowed", int, str)


class ConstrainedBox(Generic[Allowed]):
    pass


@dataclass(slots=True)
class ConstrainedBoxImpl(ConstrainedBox[Allowed]):
    type_arg: type[Allowed]


def main() -> None:
    container = Container()
    container.add(ConstrainedBoxImpl, provides=ConstrainedBox)

    valid_int = container.resolve(ConstrainedBox[int])
    valid_str = container.resolve(ConstrainedBox[str])

    if cast("ConstrainedBoxImpl[int]", valid_int).type_arg is not int:
        msg = "Expected int constrained type argument"
        raise TypeError(msg)
    if cast("ConstrainedBoxImpl[str]", valid_str).type_arg is not str:
        msg = "Expected str constrained type argument"
        raise TypeError(msg)

    invalid_key = cast("Any", ConstrainedBox)[float]
    try:
        container.resolve(invalid_key)
    except DIWireInvalidGenericTypeArgumentError:
        return

    msg = "Expected DIWireInvalidGenericTypeArgumentError for constrained TypeVar"
    raise TypeError(msg)


if __name__ == "__main__":
    main()