Scope context values

What you’ll learn

  • Pass per-scope values via enter_scope(..., context={...}).

  • Resolve context values via FromContext[T] in providers and injected callables.

Provider from context

Run locally

uv run python examples/ex_17_scope_context_values/01_provider_from_context.py
"""Focused example: provider dependencies can read ``FromContext[T]`` values."""

from __future__ import annotations

from diwire import Container, FromContext, Lifetime, Scope


class RequestValue:
    def __init__(self, value: int) -> None:
        self.value = value


def build_request_value(value: FromContext[int]) -> RequestValue:
    return RequestValue(value=value)


def main() -> None:
    container = Container()
    container.add_factory(
        build_request_value,
        provides=RequestValue,
        scope=Scope.REQUEST,
        lifetime=Lifetime.TRANSIENT,
    )

    with container.enter_scope(Scope.REQUEST, context={int: 7}) as request_scope:
        resolved = request_scope.resolve(RequestValue)

    print(f"provider_from_context={resolved.value}")  # => provider_from_context=7


if __name__ == "__main__":
    main()

Nested scope inheritance

Run locally

uv run python examples/ex_17_scope_context_values/02_nested_scope_inheritance.py
"""Focused example: nested scopes inherit context and child scopes can override keys."""

from __future__ import annotations

from diwire import Container, FromContext, Scope


def main() -> None:
    container = Container()

    with (
        container.enter_scope(Scope.REQUEST, context={int: 1, str: "parent"}) as request_scope,
        request_scope.enter_scope(Scope.ACTION) as action_scope,
        action_scope.enter_scope(Scope.STEP, context={int: 2}) as step_scope,
    ):
        inherited_value = action_scope.resolve(FromContext[int])
        overridden_value = step_scope.resolve(FromContext[int])
        inherited_parent_key = step_scope.resolve(FromContext[str])

    print(f"action_inherits_parent={inherited_value}")  # => action_inherits_parent=1
    print(f"step_overrides_parent={overridden_value}")  # => step_overrides_parent=2
    print(
        f"step_inherits_other_parent_key={inherited_parent_key}"
    )  # => step_inherits_other_parent_key=parent


if __name__ == "__main__":
    main()

Injected callable context

Run locally

uv run python examples/ex_17_scope_context_values/03_injected_callable_context.py
"""Injected callables can consume FromContext values via diwire_context."""

from __future__ import annotations

from diwire import Container, FromContext, Scope, resolver_context


def main() -> None:
    Container()

    @resolver_context.inject(scope=Scope.REQUEST)
    def handler(value: FromContext[int]) -> int:
        return value

    from_context = handler(diwire_context={int: 7})
    overridden = handler(value=8)

    print(f"from_context={from_context}")  # => from_context=7
    print(f"overridden={overridden}")  # => overridden=8


if __name__ == "__main__":
    main()

Annotated context keys

Run locally

uv run python examples/ex_17_scope_context_values/04_annotated_context_keys.py
"""Annotated tokens can be used as scope context keys."""

from __future__ import annotations

from typing import Annotated, TypeAlias

from diwire import Component, Container, FromContext, Lifetime, Scope

ReplicaNumber: TypeAlias = Annotated[int, Component("replica")]


class ReplicaConsumer:
    def __init__(self, value: int) -> None:
        self.value = value


def build_consumer(value: FromContext[ReplicaNumber]) -> ReplicaConsumer:
    return ReplicaConsumer(value=value)


def main() -> None:
    container = Container()
    container.add_factory(
        build_consumer,
        provides=ReplicaConsumer,
        scope=Scope.REQUEST,
        lifetime=Lifetime.TRANSIENT,
    )

    with container.enter_scope(Scope.REQUEST, context={ReplicaNumber: 42}) as request_scope:
        resolved = request_scope.resolve(ReplicaConsumer)
        direct = request_scope.resolve(FromContext[ReplicaNumber])

    print(f"consumer_value={resolved.value}")  # => consumer_value=42
    print(f"direct_value={direct}")  # => direct_value=42


if __name__ == "__main__":
    main()

Context without scope open

Run locally

uv run python examples/ex_17_scope_context_values/05_context_without_scope_open.py
"""Passing diwire_context without opening a scope raises a clear error."""

from __future__ import annotations

from diwire import Container, FromContext, resolver_context
from diwire.exceptions import DIWireInvalidRegistrationError


def main() -> None:
    Container()

    @resolver_context.inject(auto_open_scope=False)
    def handler(value: FromContext[int]) -> int:
        return value

    try:
        handler(diwire_context={int: 7})
    except DIWireInvalidRegistrationError as error:
        error_name = type(error).__name__

    print(
        f"context_without_scope_error={error_name}"
    )  # => context_without_scope_error=DIWireInvalidRegistrationError


if __name__ == "__main__":
    main()