Registration

diwire can auto-wire many graphs with zero registrations, but real applications typically need explicit providers for:

  • configuration objects (instances / singletons)

  • interfaces / protocols (bind to a concrete implementation)

  • resources (sessions/clients with cleanup)

  • multiple implementations (named components)

Direct registration APIs

Add (concrete types)

Use diwire.Container.add() when you want to resolve provides but construct concrete_type:

from typing import Protocol

from diwire import Container


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


class SystemClock:
    def now(self) -> str:
        return "now"


container = Container()
container.add(SystemClock, provides=Clock)

Instances

Use diwire.Container.add_instance() to bind an already-created object:

from diwire import Container

class Config: ...

container = Container()
container.add_instance(Config())

Factories

Use diwire.Container.add_factory() for custom construction logic (sync or async factories are supported):

from diwire import Container

class Client: ...

def build_client() -> Client:
    return Client()

container = Container()
container.add_factory(build_client, provides=Client)

Cleanup providers

For deterministic cleanup, use:

add_generator() validates registrations by default and requires every yield / yield from in the provider body to be inside a try block with a non-empty finally. If you intentionally want to skip this validation for a specific registration, pass require_generator_finally=False.

See Scopes & cleanup for a runnable cleanup example.

Re-registering (overrides)

Registrations are replaceable. Registering a provider again for the same key replaces the previous provider. This is useful for tests and environment-based swapping.

Next

Continue with Lifetimes and Scopes & cleanup to control caching and cleanup.