Providers

What you’ll learn

  • Break dependency cycles with Provider[T].

  • Defer expensive object construction.

  • Observe scoped vs transient behavior through provider calls.

Break cycle provider

Run locally

uv run python examples/ex_20_providers/01_break_cycle_provider.py
"""Focused example: break a cycle with ``Provider[T]``."""

from __future__ import annotations

from diwire import Container, Provider


class A:
    def __init__(self, b_provider: Provider[B]) -> None:
        self._b_provider = b_provider

    def get_b(self) -> B:
        return self._b_provider()


class B:
    def __init__(self, a: A) -> None:
        self.a = a


def main() -> None:
    container = Container()
    container.add(A)
    container.add(B)

    resolved_a = container.resolve(A)
    resolved_b = resolved_a.get_b()

    print(f"cycle_resolves={isinstance(resolved_b, B)}")  # => cycle_resolves=True
    print(f"cycle_same_a={resolved_b.a is resolved_a}")  # => cycle_same_a=True


if __name__ == "__main__":
    main()

Lazy construction provider

Run locally

uv run python examples/ex_20_providers/02_lazy_construction_provider.py
"""Focused example: ``Provider[T]`` defers expensive construction until called."""

from __future__ import annotations

from diwire import Container, Provider


class Expensive:
    build_count = 0

    def __init__(self) -> None:
        type(self).build_count += 1


class UsesExpensiveProvider:
    def __init__(self, expensive_provider: Provider[Expensive]) -> None:
        self._expensive_provider = expensive_provider

    def get_expensive(self) -> Expensive:
        return self._expensive_provider()


def main() -> None:
    Expensive.build_count = 0
    container = Container()
    container.add(Expensive)
    container.add(UsesExpensiveProvider)

    consumer = container.resolve(UsesExpensiveProvider)
    before_call = Expensive.build_count
    _ = consumer.get_expensive()
    after_call = Expensive.build_count

    print(f"lazy_before_call={before_call}")  # => lazy_before_call=0
    print(f"lazy_after_call={after_call}")  # => lazy_after_call=1


if __name__ == "__main__":
    main()

Provider lifetime semantics

Run locally

uv run python examples/ex_20_providers/03_provider_lifetime_semantics.py
"""Focused example: provider calls follow scoped vs transient lifetime semantics."""

from __future__ import annotations

from diwire import Container, Lifetime, Provider, Scope


class Expensive:
    build_count = 0

    def __init__(self) -> None:
        type(self).build_count += 1


class UsesExpensiveProvider:
    def __init__(self, expensive_provider: Provider[Expensive]) -> None:
        self._expensive_provider = expensive_provider

    def get_expensive(self) -> Expensive:
        return self._expensive_provider()


def _run_scenario(*, lifetime: Lifetime) -> tuple[int, bool]:
    Expensive.build_count = 0
    container = Container()
    container.add(
        Expensive,
        provides=Expensive,
        scope=Scope.REQUEST,
        lifetime=lifetime,
    )
    container.add(
        UsesExpensiveProvider,
        provides=UsesExpensiveProvider,
        scope=Scope.REQUEST,
        lifetime=Lifetime.TRANSIENT,
    )

    with container.enter_scope() as request_scope:
        consumer = request_scope.resolve(UsesExpensiveProvider)
        first = consumer.get_expensive()
        second = consumer.get_expensive()

    return Expensive.build_count, first is second


def main() -> None:
    scoped_calls, scoped_same = _run_scenario(lifetime=Lifetime.SCOPED)
    transient_calls, transient_same = _run_scenario(lifetime=Lifetime.TRANSIENT)

    print(f"scoped_after_calls={scoped_calls}")  # => scoped_after_calls=1
    print(f"scoped_same_identity={scoped_same}")  # => scoped_same_identity=True
    print(f"transient_after_calls={transient_calls}")  # => transient_after_calls=2
    print(f"transient_same_identity={transient_same}")  # => transient_same_identity=False


if __name__ == "__main__":
    main()