Patterns

HTTP request handler pattern (per-request scope)

Demonstrates a common web pattern:

  • each request gets its own scope

  • multiple services share a request-scoped context

  • the handler is resolved via container.resolve(..., scope="request")

from enum import Enum
from typing import Annotated

from diwire import Container, Injected, Lifetime


class Scope(str, Enum):
    REQUEST = "request"


class RequestContext:
    _counter = 0

    def __init__(self) -> None:
        type(self)._counter += 1
        self.request_id = f"req-{type(self)._counter}"
        self.user_id: int | None = None


class AuthService:
    def __init__(self, ctx: RequestContext) -> None:
        self.ctx = ctx

    def authenticate(self, token: str) -> None:
        if token.startswith("valid-"):
            self.ctx.user_id = int(token.removeprefix("valid-"))


class AuditLogger:
    def __init__(self, ctx: RequestContext) -> None:
        self.ctx = ctx

    def log(self, action: str) -> None:
        print(f"[{self.ctx.request_id}] user={self.ctx.user_id}: {action}")


def handle_request(
    token: str,
    auth: Annotated[AuthService, Injected()],
    audit: Annotated[AuditLogger, Injected()],
    ctx: Annotated[RequestContext, Injected()],
) -> dict[str, str | int | None]:
    auth.authenticate(token)
    audit.log("handle_request")
    return {"request_id": ctx.request_id, "user_id": ctx.user_id}


def main() -> None:
    container = Container()
    container.register(RequestContext, lifetime=Lifetime.SCOPED, scope=Scope.REQUEST)
    container.register(AuthService)
    container.register(AuditLogger)

    handler = container.resolve(handle_request, scope=Scope.REQUEST)

    print(handler(token="valid-42"))
    print(handler(token="invalid"))


if __name__ == "__main__":
    main()

Repository / unit-of-work pattern

Demonstrates a unit-of-work pattern where the database session is shared within a scope.

Per-call unit of work (ScopedInjected)

from dataclasses import dataclass
from enum import Enum
from typing import Annotated

from diwire import Container, Injected, Lifetime


class Scope(str, Enum):
    UNIT_OF_WORK = "unit_of_work"


class Session:
    _counter = 0

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

    def commit(self) -> None:
        print(f"[Session {self.session_id}] COMMIT")


@dataclass
class UserRepository:
    session: Session

    def create(self, username: str) -> str:
        print(f"[Session {self.session.session_id}] INSERT user {username!r}")
        return username


def create_user(
    username: str,
    session: Annotated[Session, Injected()],
    user_repo: Annotated[UserRepository, Injected()],
) -> dict[str, str | int]:
    user = user_repo.create(username)
    session.commit()
    return {"user": user, "session_id": session.session_id}


def main() -> None:
    container = Container()
    container.register(Session, lifetime=Lifetime.SCOPED, scope=Scope.UNIT_OF_WORK)
    container.register(UserRepository)

    handler = container.resolve(create_user, scope=Scope.UNIT_OF_WORK)
    print(handler(username="alice"))
    print(handler(username="bob"))


if __name__ == "__main__":
    main()

Manual unit of work scope (enter_scope)

Use enter_scope() when you want to manage the scope explicitly.

from dataclasses import dataclass
from enum import Enum

from diwire import Container, Lifetime


class Scope(str, Enum):
    UNIT_OF_WORK = "unit_of_work"


class Session:
    _counter = 0

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


@dataclass
class UserRepository:
    session: Session


def main() -> None:
    container = Container()
    container.register(Session, lifetime=Lifetime.SCOPED, scope=Scope.UNIT_OF_WORK)
    container.register(UserRepository)

    with container.enter_scope(Scope.UNIT_OF_WORK) as scope:
        repo1 = scope.resolve(UserRepository)
        repo2 = scope.resolve(UserRepository)
        session = scope.resolve(Session)

        print(f"Same session: {repo1.session is repo2.session is session}")
        print(f"session_id={session.session_id}")


if __name__ == "__main__":
    main()

Class methods with container_context

container_context.resolve() works on instance methods. This is useful for controller/handler classes where you want dependency injection without passing the container around.

Instance method injection

from typing import Annotated

from diwire import Container, Injected, Lifetime, container_context


class Logger:
    def info(self, message: str) -> None:
        print(f"[INFO] {message}")


class Controller:
    def __init__(self, prefix: str) -> None:
        self.prefix = prefix

    @container_context.resolve()
    def ping(self, logger: Annotated[Logger, Injected()]) -> str:
        logger.info(f"{self.prefix}/ping")  # noqa: G004
        return "pong"


def main() -> None:
    container = Container()
    container.register(Logger, lifetime=Lifetime.SINGLETON)

    token = container_context.set_current(container)
    try:
        controller = Controller(prefix="/v1")
        print(controller.ping())
    finally:
        container_context.reset(token)


if __name__ == "__main__":
    main()

Instance method with caller args

Injected params and caller-provided params can be mixed.

from dataclasses import dataclass
from typing import Annotated

from diwire import Container, Injected, container_context


@dataclass
class UserRepository:
    def get_username(self, user_id: int) -> str:
        return f"user-{user_id}"


class Controller:
    @container_context.resolve()
    def get_user(
        self,
        user_id: int,
        repo: Annotated[UserRepository, Injected()],
    ) -> str:
        return repo.get_username(user_id)


def main() -> None:
    container = Container()
    container.register(UserRepository)

    token = container_context.set_current(container)
    try:
        controller = Controller()
        print(controller.get_user(123))
    finally:
        container_context.reset(token)


if __name__ == "__main__":
    main()

Interface registration (Protocol/ABC -> concrete_class)

Use concrete_class=... to bind a Protocol/ABC to an implementation.

from typing import Protocol

from diwire import Container, Lifetime


class Clock(Protocol):
    def now(self) -> str: ...


class SystemClock:
    def now(self) -> str:
        return "2026-02-01T00:00:00Z"


class Greeter:
    def __init__(self, clock: Clock) -> None:
        self.clock = clock

    def greet(self) -> str:
        return f"Hello at {self.clock.now()}"


def main() -> None:
    container = Container()
    container.register(Clock, concrete_class=SystemClock, lifetime=Lifetime.SINGLETON)

    greeter = container.resolve(Greeter)
    print(greeter.greet())


if __name__ == "__main__":
    main()

Read more