Dependency injection#

API reference for the DI subsystem. For the full guide (resolution, scopes, containers, errors, and the thread-safety/performance guarantees) see Dependency injection.

Container and Scope#

class inito.Container[source]#

Registers services at decoration time; resolves and builds instances lazily on get().

Singleton construction is thread-safe: concurrent first-access to the same singleton from multiple threads is serialized via a per-class lock, so a service is never constructed more than once and every caller receives the same instance. Already-cached singletons cost nothing extra - no lock is touched once resolved. A Container is always process-local; it is not shared across separate OS processes.

register(cls, *, scope=Scope.SINGLETON, qualifier=None, primary=False)[source]#

Register cls under scope (and optional qualifier), without instantiating it.

Parameters:
Return type:

None

register_provider(spec, *, scope=Scope.SINGLETON)[source]#

Register a function-form @Resource provider, keyed by the type it yields.

Parameters:
  • spec (ProviderSpec)

  • scope (Scope)

Return type:

None

get(cls)[source]#

Resolve and return an instance of cls, building its dependency graph bottom-up.

Parameters:

cls (type[T])

Return type:

T

async aget(cls)[source]#

Async twin of get, awaiting async @Resource providers anywhere in the graph.

Sync services, singletons, scoped services, and cached instances resolve exactly as get does; an async-generator provider is awaited to its first yield. Use this whenever the graph contains an async resource.

Parameters:

cls (type[T])

Return type:

T

override(cls, instance)[source]#

Make get(cls) return instance until cleared. For tests/environment swaps.

Parameters:
  • cls (type[T])

  • instance (T)

Return type:

None

override_factory(cls, factory)[source]#

Make get(cls) return factory() on each resolution until cleared.

Parameters:
Return type:

None

clear_override(cls)[source]#

Remove any override for cls.

Parameters:

cls (type)

Return type:

None

clear_overrides()[source]#

Remove all overrides.

Return type:

None

overrides(mapping)[source]#

Temporarily override each type -> instance in mapping, restoring on exit.

Both the overrides and the singleton cache are snapshotted and restored, so any singleton built under the override is discarded on exit and the container returns to its prior state - the behavior a test expects.

Parameters:

mapping (Mapping[type, Any])

Return type:

Iterator[None]

is_registered(cls)[source]#

Whether cls has been registered in this container.

Parameters:

cls (type)

Return type:

bool

reset()[source]#

Clear the singleton/thread-local caches, overrides, and pending finalizers.

Registrations are kept. A test helper: it drops resource finalizers without running them, so tear resources down with shutdown_resources() first if their cleanup matters.

Return type:

None

shutdown_resources()[source]#

Tear down every built @Resource in reverse construction order (LIFO).

Best-effort: each is closed even if an earlier one raised, and the failures are aggregated into a single ResourceTeardownError. Raises immediately, tearing nothing down, if an async resource is pending — use ashutdown_resources() / async with container for those.

Return type:

None

async ashutdown_resources()[source]#

Async twin of shutdown_resources: awaits async teardowns, runs sync ones.

Return type:

None

scope()[source]#

Open a scope for Scope.SCOPED services; usable as with or async with.

Within the block, a scoped service is built once and cached, and a scoped @Resource is torn down (LIFO) when the block exits. Scopes nest and are task/thread-local.

Return type:

_ScopeHandle

class inito.Scope(*values)[source]#

How a registered service’s lifetime is managed.

class inito.Qualifier(name)[source]#

Names the desired implementation of an Annotated dependency.

Use as Annotated[Repo, Qualifier("postgres")] to autowire the service registered with @Service(qualifier="postgres") when several implement the same base type. A bare string in the Annotated metadata (Annotated[Repo, "postgres"]) is accepted too.

Parameters:

name (str)

class inito.Factory[source]#

A callable that builds a fresh T per call, autowiring what it can.

Inject it as a constructor parameter — make: Factory[Widget] — and call it to build a Widget on demand:

report = self.make_report(title="Sales")   # title supplied now, deps autowired

Every keyword argument you pass wins; every other constructor parameter of the target whose type is a registered service is autowired from the container; and anything left falls to the target’s own default (or raises a natural missing-argument error). A fresh instance is built on every call — the result is never cached, and the target need not itself be registered.

Factory is the annotation/marker and the static type: type-checkers see make(...) returning T with no plugin. The container injects a bound implementation; calling Factory directly is not supported.

inito.default_container#

The shared Container that @Service/@Singleton register into by default.

Container exposes get/aget/register/register_provider/is_registered/ reset, scope() (a with/async with scope for Scope.SCOPED services), the resource lifecycle shutdown_resources/ashutdown_resources (and the with/async with context-manager protocol), and the test overrides override/override_factory/overrides/clear_override/clear_overrides. Scope adds SCOPED (one instance per scope()). Scope is SINGLETON, TRANSIENT, or THREAD_LOCAL. Qualifier names an implementation for Annotated[Base, Qualifier("name")]. Factory[T] injects a callable that builds a fresh T per call, autowiring its registered dependencies and taking the rest as call-time keyword arguments.

Decorators#

inito.Service#

Register cls’s constructor dependency types into a Container (default_container unless container= is given), at decoration time - never instantiates cls. The class remains a perfectly ordinary, directly-constructible Python class; container.get(cls) is the DI-aware path that autowires and builds it.

Parameters:
Return type:

Any

class inito.ServiceOptions(scope=Scope.SINGLETON, container=None, qualifier=None, primary=False)[source]#

Configuration surface for the @Service decorator.

Parameters:
inito.Singleton[source]#

Register cls into a Container with Scope.SINGLETON, always.

A standalone decorator, not a stacking requirement on top of @Service - @Service(scope=…) already covers every scope, including singleton. Rejects an explicit scope= kwarg rather than silently honoring it, since a caller passing scope=Scope.TRANSIENT to @Singleton is almost certainly a mistake, not an intentional override.

Parameters:
Return type:

Any

inito.Inject[source]#

Wrap fn so its type-annotated, unfilled parameters are resolved from a Container per call.

Explicit args/kwargs supplied by the caller are never overridden. Unlike every class decorator in this library, resolution here is a real per-call cost (a container.get() per unfilled, container-registered parameter) - @Inject targets composition-root entry points (e.g. a main()/handler function), not generated hot-path methods, so this cost is intentional and documented rather than hidden. All signature/type-hint inspection is still done exactly once, at decoration time; the per-call path only checks which parameters the caller already supplied and resolves the rest.

For an ordinary signature (no *args/**kwargs/positional-only), the wrapper is generated with the function’s own parameters, so a call skips the generic *args/**kwargs packing and per-parameter loop; exotic signatures fall back to a generic wrapper with identical behavior.

Parameters:
Return type:

Any

inito.Resource(*args, **kwargs)[source]#

Mark a resource whose lifetime the container manages, then closes at shutdown.

On a class (paired with @Service/@Singleton): the instance is torn down by its close() method — rename it with @Resource(close="aclose") — or, if it has none, the __enter__/__exit__ protocol. An async close is awaited by ashutdown_resources() / async with container.

On a generator function: it self-registers a provider keyed by the yielded type; its parameters are autowired; the code after yield runs at teardown. Sync generators build via get(), async generators via await aget().

Parameters:
Return type:

Any

class inito.ResourceOptions(close='close', container=None, scope=Scope.SINGLETON)[source]#

Configuration surface for the @Resource decorator.

Parameters:
class inito.Injected(target, *, container=None)[source]#

A FastAPI dependency that resolves T from an inito Container per request.

Two equivalent forms:

def handler(svc: Injected[Service]) -> ...:            # Annotated form
def handler(svc: Service = Injected(Service)) -> ...:  # default-value form

Each resolution runs inside a fresh container.scope() (opened and torn down per request), so a scoped @Resource — e.g. a request-lifetime DB session — is built on entry and closed when the request ends. Pass container= to the call form to resolve from a container other than the default.

Parameters:
Return type:

Any

@Resource marks a class (torn down by close()/aclose() or the __enter__/__exit__ protocol) or a generator-provider function (yield the resource, then clean up) whose lifetime the Container manages. Tear resources down with container.shutdown_resources() / with container, or await container.ashutdown_resources() / async with container for async ones; an async generator provider is built with await container.aget(cls).

Also exported as inito.component/inito.Component (a literal alias for @Service), inito.singleton, and inito.inject. @Service/@Singleton register a class’s constructor dependency types at decoration time — they never mutate the class, so it stays an ordinary, directly-constructible Python class; container.get(cls) is the DI-aware path that autowires and lazily builds it.