Registration

You can use diwire with almost no registrations (auto-wiring), but real applications still need a few explicit ones:

  • configuration objects (singletons)

  • interfaces/protocols (bind to a concrete implementation)

  • resources (database sessions/clients with cleanup)

  • multiple implementations (named components)

Class / factory / instance

The three direct registration forms:

  • Class: container.register(Logger)

  • Factory: container.register(Database, factory=create_database)

  • Instance: container.register(Config, instance=Config(...))

Full runnable example:

See Basics (Registration methods section).

Decorator registration

@container.register is a convenience API that works for:

  • classes (optionally with an explicit lifetime/scope)

  • factory functions (return type inferred from annotations)

  • staticmethod factories (same as regular functions)

  • interface bindings (@container.register(Protocol, lifetime=...))

Selected patterns (see the full example for more):

See Basics (Decorator registration section).

Interface/protocol binding

You can bind a protocol/ABC to a concrete implementation in two main ways:

  1. Decorator:

    from typing import Protocol
    from diwire import Container, Lifetime
    
    
    class Clock(Protocol):
        def now(self) -> str: ...
    
    
    container = Container()
    
    
    @container.register(Clock, lifetime=Lifetime.SINGLETON)
    class SystemClock:
        ...
    
  2. Direct registration:

    container.register(Clock, concrete_class=SystemClock, lifetime=Lifetime.SINGLETON)
    

Re-registering (overrides)

Registrations are replaceable. Calling register() again with the same key replaces the previous registration. This is intentionally useful for testing and for swapping implementations by environment.

Next

Continue with Lifetimes and Scopes & cleanup - these determine how long objects live, and when cleanup happens.