API reference¶
Every public symbol is exported from the top-level fastapi_standalone_di
package:
from fastapi_standalone_di import (
AppState,
AutoBindingError,
Binding,
ConflictSolver,
CyclicDependencyError,
DependantCache,
DependencyOverrides,
DependencyScope,
FastAPIContainer,
MissingParameterError,
ParamSource,
ParameterError,
ParameterValidationError,
RegistrableDependency,
ResolutionScope,
ResolvedDependencies,
ScopeError,
auto_bindings,
get_app_state,
get_container,
patch_for_registrable_dependency_support,
provides,
register_bindings,
set_app_state_value,
singleton,
)
Container¶
FastAPIContainer¶
Dependency container that resolves FastAPI dependencies outside ASGI. It encapsulates the configuration for a whole dependency tree and owns the lifetime of the instances it builds.
FastAPIContainer(
app_state: AppState | None = None,
dependency_overrides: DependencyOverrides | None = None,
dependant_cache: DependantCache | bool = True,
default_scope: DependencyScope | dict[str | None, DependencyScope] = DependencyScope.CONTAINER,
scopes: dict[Callable[..., Any], DependencyScope] | None = None,
app: Any | None = None,
query: ParamSource | Mapping[str, str] | None = None,
path: ParamSource | Mapping[str, str] | None = None,
headers: ParamSource | Mapping[str, str] | None = None,
cookies: ParamSource | Mapping[str, str] | None = None,
security_scopes: Sequence[str] | None = None,
)
Constructor arguments:
app_state— theAppStatebackingget_app_stateand the stub request'sapp.state. Defaults to the standalone singleton.dependency_overrides— a{callable: replacement}map, like FastAPI'sapp.dependency_overrides.dependant_cache— a sharedDependantCache, orTrue(default) to create one, orFalseto disable introspection caching.default_scope— the globalDependencyScope, or a dict mapping FastAPI'sDepends(scope=...)literals ("request"/"function"/None) to a scope.scopes— per-dependency scope overrides; keyed by the declared callable or its resolved implementation. Wins overdefault_scope.app— a real FastAPI/Starlette application to expose asrequest.appon the stub request. When unset, a minimal stub app backed byapp_stateis used.query/path/headers/cookies— connection-parameter sources; each aParamSourceor a bare{name: value}mapping. See Parameters & connection.security_scopes— the scopes exposed to anySecurityScopesparameter.
Resolution methods (all coroutines):
await get(dependency)— resolve a single dependency and return its instance.await optional(dependency)— resolve a single dependency, orNoneif not resolved.await resolve(*dependencies)— resolve one or more dependencies at container scope; returnsResolvedDependencies. Resolving aSCOPEDdependency here raisesScopeError.await invoke(call)— resolvecall'sDepends()parameters and invoke it, inside an implicit resolution scope. The entry point is not cached.await invoke_resolved(call)— likeinvoke, but returns theResolvedDependencies;get(call)is the invocation result.
Scope and lifetime:
scope()— return aResolutionScopeto use asasync with container.scope() as scope: ....clear_cache()— drop all cached container-scoped instances without running teardown (theget_app_stateseed is restored).await aclose()— close the container, running teardown forCONTAINERyielddependencies and any queuedBackgroundTasks.
The container is an async context manager: async with FastAPIContainer() as
container: calls aclose() on exit.
ResolutionScope¶
A short-lived resolution scope owning SCOPED dependency lifetimes. Obtained from
FastAPIContainer.scope and used as an async context
manager; SCOPED dependencies resolved through it are torn down when the block
exits, while CONTAINER dependencies are delegated to the parent container and
outlive the scope.
It offers the same resolution surface as the container: await get(dependency),
await optional(dependency), await resolve(*dependencies),
await invoke(call), await invoke_resolved(call).
ResolvedDependencies¶
The bag returned by resolve / invoke_resolved.
get(dependency, *, transitive=False)— retrieve a resolved dependency by its type or callable. By default only the top-level dependencies (those explicitly passed toresolve) are addressable; passtransitive=Trueto reach a sub-dependency resolved along the way. RaisesKeyErrorif not resolved.optional(dependency, *, transitive=False)— asget, but returnsNoneinstead of raising.all_instances()— a read-onlyMappingof every instance resolved in the operation, keyed by resolved callable and ordered sub-dependencies first.
Dependency scopes¶
DependencyScope¶
Enum deciding a dependency's lifetime and teardown boundary:
DependencyScope.CONTAINER— one instance per container, torn down ataclose().DependencyScope.SCOPED— one instance per active scope, torn down when that scope closes.
ScopeError¶
RuntimeError subclass raised on scope misuse: resolving a SCOPED dependency
without an active scope, or a CONTAINER-scoped dependency depending on a
SCOPED one (a captive dependency).
CyclicDependencyError¶
RuntimeError subclass raised when a dependency re-enters its own in-flight
build — a cycle. Without detection the resolver, which serialises concurrent
builds of a shared dependency on a per-callable lock, would wait on that lock
forever. A common cause is a lazy singleton whose factory is the
implementation class registered for the very interface it subclasses: resolving
it dereferences back to the singleton. Make such a singleton eager, or register a
lazy singleton factory function instead of the class.
Application state¶
AppState¶
Abstraction over Starlette's State that works with and without a request. In
FastAPI mode reads/writes go to request.app.state; in standalone mode
they go to a module-level singleton dict.
get(key)/set(key, value)/delete(key)— read/write/remove a value.as_state()— return a StarletteStatesharing thisAppState's storage.AppState.from_request(request)— back it by an ASGI application's state.AppState.from_app(app)— back it by a Starlette/FastAPI application.AppState.standalone()— return the module-level singleton.AppState.reset_standalone()— reset that singleton (useful in tests).
get_app_state¶
FastAPI dependency returning an AppState. When injected by FastAPI, request is
provided and the AppState delegates to request.app.state; resolved standalone
(e.g. via FastAPIContainer) request is None and the standalone singleton is
used.
set_app_state_value¶
Set a value in the standalone AppState store. Call it at startup so the value is
available to both FastAPI and standalone contexts.
get_container¶
FastAPI dependency returning the active FastAPIContainer. Register the container
in app_state at startup, e.g. via
set_app_state_value("container", FastAPIContainer(...)). Raises RuntimeError
if no container is registered. When resolved through a container, it yields that
container itself, so Depends(get_container) works standalone without registering
anything.
container_lifespan¶
ASGI lifespan that installs a FastAPIContainer backed by the application state
into app.state.container and closes it at shutdown (running the
CONTAINER-scoped yield teardown of any lazy singleton). Pass it
directly as FastAPI(lifespan=container_lifespan), or call it from a wrapping
lifespan (async with container_lifespan(app): ...) to compose with your own
startup/shutdown. It yields nothing into the lifespan state, so it is compatible
across the whole supported Starlette range. Requires a FastAPI version that
honours the lifespan argument (FastAPI ≥ 0.93); on older releases register the
container manually at startup instead.
singleton¶
def singleton(
factory: Callable[..., T] | None = None,
*,
key: str | None = None,
lazy: bool = False,
) -> Callable[..., T] | Callable[[Callable[..., T]], Callable[..., T]]
Turn a dependency factory into an application-lifetime singleton: its instance is
built lazily on first access, cached in AppState, and reused
thereafter — identically under FastAPI (shared across requests via
request.app.state) and standalone (shared across containers sharing the same
AppState). Usable functionally (get_db = singleton(build_db, key="db")), as a
bare decorator (@singleton), or a parametrised one (@singleton(key="db")). The
result is a drop-in dependency for Depends(...) or container.get(...).
key— theAppStatekey under which the instance is cached. Defaults to a namespaced id derived from the factory. Sharing a key withset_app_state_valuepresets/overrides the instance (a preset short-circuits construction).lazy— resolution semantics:False(default, eager) — the factory body runs at most once, but itsDepends(...)sub-tree is re-resolved on each access; no container is required. Generator (yield) factories are rejected (TypeError).True(lazy) — construction is delegated to a container reachable throughapp_state(app.state.container); the sub-tree is resolved exactly once and the container owns anyyieldteardown (run ataclose()== application shutdown). In ASGI, install one withcontainer_lifespan; standalone, the resolving container provides itself. No container is needed when the value is preset underkey— the preset short-circuits construction.
Registrable dependencies¶
RegistrableDependency¶
Base class for a dependency interface with a swappable implementation. Declare an
abstract interface inheriting RegistrableDependency, bind a concrete class with
Interface.register(Impl), and depend on the interface via Depends(Interface)
— the container dereferences it to the registered implementation.
Interface.register(impl)— register (or clear, withNone) the implementation.Interface.dependency()— return the registered implementation; raisesRuntimeErrorwhen none is registered.Interface.impl— class property returning the registered implementation.
provides¶
@overload
def provides(fn: Callable[..., T]) -> Callable[..., T]
@overload
def provides(*, primary: bool = False) -> Callable[[Callable[..., T]], Callable[..., T]]
Mark a factory function as the implementation of the interface it returns, so
auto_bindings wires it. An implementation class is matched by
its hierarchy, but a function has no bases; @provides names the interface it
implements through the function's return annotation — the interface itself, a
concrete implementation of it (matched by the returned type's direct interface
bases, as for a class), or a generator's element type (Iterator[X] /
AsyncIterator[X], and the Generator forms) for a factory with yield
teardown.
- Mandatory on a factory function: a plain function returning an interface is left alone.
- Optional on a class for plain wiring (classes are wired by hierarchy), but the marker still applies to a class for ranking.
- Ranks candidates when several match one interface: a marked candidate (class or
factory) beats an unmarked class, and
primary=Truebeats every other candidate — settling the tie before anyconflict_solver. - Composes with
singletonin either decorator order; the tag survives the wrapper, so the singleton wrapper is registered and its cache survives. Used alone, the function is rebuilt on every resolution. - A return type carrying no interface —
Any, missing, or unrelated toRegistrableDependency— is reported as anAutoBindingError.
patch_for_registrable_dependency_support¶
Patch fastapi.params.Depends so it resolves a RegistrableDependency eagerly.
Only needed when FastAPI itself must see the concrete implementation at
introspection time (e.g. for OpenAPI); FastAPIContainer does not require it. The
patch mutates the class in place, so it affects every Depends() object
regardless of when it was created — order relative to this call no longer
matters. Returns True if applied, False if already patched.
register_bindings¶
def register_bindings(
*packages: str | ModuleType,
module: str = "di",
attr: str = "register",
recursive: bool = False,
warn_missing: bool = True,
) -> None
Discover per-feature binding modules and run them, so every
RegistrableDependency is bound before the routers are mounted. For each
subpackage of every packages entry, it imports <subpackage>.<module> and
calls its attr callable.
packages— the packages to scan, each an imported module or a dotted name.module— the submodule to look for under each subpackage; may be a dotted path (e.g."api.di").attr— the callable to invoke on that module.recursive— also walk nested subpackages, not just the direct ones.warn_missing—logging.warningwhen a matching module exposes no callableattr, instead of failing silently at request time.
Subpackages with no such module are skipped silently. An import error raised by
a binding module propagates. Raises ValueError if a packages entry is not a
package.
auto_bindings¶
def auto_bindings(
*packages: str | ModuleType,
interfaces: Sequence[str | ModuleType] = (),
implementations: Sequence[str | ModuleType] = (),
recursive: bool = True,
conflict_solver: ConflictSolver | None = None,
ast: bool = True,
) -> list[Binding]
Wire RegistrableDependency interfaces to their implementations by convention,
deriving the bindings from the class hierarchy instead of hand-written
register() calls. Scans the packages for interface classes (those carrying
RegistrableDependency as a direct base) and implementation classes, then
binds each interface to the implementation that declares it as a direct base.
packages— packages holding both interfaces and implementations, scanned once for both roles. Each is a dotted name or an imported module; a leading.is anchored to the caller's package.interfaces— extra packages scanned for interface classes only.implementations— extra packages scanned for implementation classes only.recursive— also descend into nested subpackages. Defaults toTrue(unlikeregister_bindings); implementations are typically spread across a subtree. Scanning imports every module in the scanned packages, so call it once at bootstrap.conflict_solver— optional tie-breaker called once per interface still ambiguous after the ranking below, with(interface, contenders); returns the chosen candidate (one ofcontenders, each an implementation class or a@providesfunction) orNoneto leave the ambiguity unresolved.ast— pre-filter the scanned tree by static analysis so only modules that can hold an interface or implementation are imported (defaultTrue); setFalseto import every module (running their import-time side effects) when implementations are synthesised at import time and so invisible to the analysis.
When several implementations match one interface, they are ranked before any
conflict_solver: (1) a single @provides(primary=True) candidate —
a class or a factory alike — wins outright (two or more primaries is an error);
(2) otherwise @provides-marked candidates beat unmarked ones (a factory function
is always marked, an implementation class only when decorated), so a lone marked
candidate wins over any number of bare classes. Only candidates tied at the
winning rank reach the conflict_solver. An interface that already carries its
own implementation is left untouched and reported with already_bound=True.
Resolution and registration are two phases: if any interface has zero matches, an
unresolved ambiguity, several primaries, or a @provides carries no interface
return type, nothing is registered and an AutoBindingError
aggregating every problem is raised. Returns every resolved interface as a
Binding,
freshly bound and pre-existing alike, ordered by the interface's module and
qualified name.
An implementation class decorated with singleton (eager or lazy)
is discovered through the class it wraps and registered as the wrapper, so its
application-lifetime cache is preserved. A factory function is wired only when
marked with provides, matched by its return annotation (a function
has no bases); a @provides carrying no interface return type is reported as an
error.
Binding¶
class Binding(NamedTuple):
interface: type[RegistrableDependency]
implementation: Callable[..., Any]
already_bound: bool
One resolved interface→implementation link returned by
auto_bindings. already_bound is True for an interface that
carried an implementation before the call (left untouched, reported for
completeness) and False for one bound by the call itself.
ConflictSolver¶
ConflictSolver = Callable[
[type[RegistrableDependency], list[Callable[..., Any]]],
Callable[..., Any] | None,
]
Type alias for the auto_bindings conflict_solver callback:
given an interface and its candidate implementations (each an implementation class
or a @provides function), return the chosen candidate or None to
leave the ambiguity unresolved.
AutoBindingError¶
ValueError subclass raised by auto_bindings when it cannot
wire every discovered interface, aggregating all wiring gaps found in one scan:
interfaces with no matching implementation, ambiguous interfaces that neither a
primary marker nor a conflict_solver resolved, interfaces with two or more
candidates marked @provides(primary=True), and
@provides functions carrying no interface return type. Nothing is
registered when it is raised.
Connection parameters¶
ParamSource¶
@dataclass(frozen=True)
class ParamSource:
values: Mapping[str, str] = {}
default: str | None = None
How to supply one class of connection parameters (query / path / header /
cookie). values are explicit values keyed by parameter name (falling back to
its alias); default is a string injected for any required parameter of the
source with no explicit value and no declared default. See
Parameters & connection.
ParameterError hierarchy¶
ParameterError— base class (subclass ofRuntimeError) for standalone connection-parameter resolution errors.MissingParameterError— a required query/path/header/cookie parameter could not be supplied.ParameterValidationError— a supplied parameter value is incompatible with its declared type.
Introspection cache¶
DependantCache¶
Cache of FastAPI dependency-tree introspection results, mapping a callable to the
Dependant produced by FastAPI's get_dependant, so repeated resolve calls
skip re-introspecting the same callables. A single instance can be shared across
several containers.
get_dependant(call)/set_dependant(call, dependant)— look up / store aDependant.clear()— drop all cached entries.
DependencyOverrides¶
Type alias: dict[Callable[..., Any], Callable[..., Any]] — the shape accepted by
FastAPIContainer(dependency_overrides=...).