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.
- 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
getdoes; 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.
- 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.
- 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 — useashutdown_resources()/async with containerfor those.- Return type:
None
- 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 theAnnotatedmetadata (Annotated[Repo, "postgres"]) is accepted too.- Parameters:
name (str)
- class inito.Factory[source]#
A callable that builds a fresh
Tper call, autowiring what it can.Inject it as a constructor parameter —
make: Factory[Widget]— and call it to build aWidgeton 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.
Factoryis the annotation/marker and the static type: type-checkers seemake(...)returningTwith no plugin. The container injects a bound implementation; callingFactorydirectly is not supported.
- inito.default_container#
The shared Container that
@Service/@Singletonregister 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.
- class inito.ServiceOptions(scope=Scope.SINGLETON, container=None, qualifier=None, primary=False)[source]#
Configuration surface for the @Service decorator.
- 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.
- 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/**kwargspacking and per-parameter loop; exotic signatures fall back to a generic wrapper with identical behavior.
- 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. Anasyncclose is awaited byashutdown_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
yieldruns at teardown. Sync generators build viaget(), async generators viaawait aget().
- class inito.ResourceOptions(close='close', container=None, scope=Scope.SINGLETON)[source]#
Configuration surface for the @Resource decorator.
- class inito.Injected(target, *, container=None)[source]#
A FastAPI dependency that resolves
Tfrom 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. Passcontainer=to the call form to resolve from a container other than the default.
@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.