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()