Skip to content

Container & Registry

Container

Bases: BindingCollector

Mutable builder for a dependency graph.

Collect bindings with bind(), value(), scope_value(), alias(), collect(), decorate(), explicit Manifest sources, and the singleton() / scoped() / transient() decorators, then call freeze() to validate the graph and obtain an immutable FrozenContainer. A Container performs no resolution itself; nothing is constructed until you resolve from the frozen view. Registration order does not matter — providers are matched by key and ordered at freeze() time.

Parameters:

Name Type Description Default
*sources Bindings

Binding sources to load up front — Manifest or Registry instances, other containers, or anything satisfying Bindings. Equivalent to calling include() with the same arguments.

()

Raises:

Type Description
InvalidProviderError

A source does not satisfy Bindings, its records() result is malformed, or a Catalog is supplied without first composing it through a Manifest.

Example
>>> from depin import Container
>>> class Config:
...     value = 42
>>> class Service:
...     def __init__(self, config: Config) -> None:
...         self.config = config
>>> di = Container().bind(Config).bind(Service).freeze()
>>> di[Service].config.value
42

bind

bind(
    source: type[T],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., Generator[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., AsyncGenerator[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., AbstractContextManager[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., AbstractAsyncContextManager[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., Awaitable[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., T],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: type[T]
    | Callable[..., Generator[T]]
    | Callable[..., AsyncGenerator[T]]
    | Callable[..., AbstractContextManager[T]]
    | Callable[..., AbstractAsyncContextManager[T]]
    | Callable[..., Awaitable[T]]
    | Callable[..., T],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self

Register a class or factory as a provider.

The provider key is inferred from source: a class is keyed by itself (or by its @provides(...) target), a factory by its return annotation (unwrapped for generator and context-manager factories). Constructor and factory parameters are themselves resolved from their type hints, so the whole graph is wired by type.

Parameters:

Name Type Description Default
source type[T] | Callable[..., Generator[T]] | Callable[..., AsyncGenerator[T]] | Callable[..., AbstractContextManager[T]] | Callable[..., AbstractAsyncContextManager[T]] | Callable[..., Awaitable[T]] | Callable[..., T]

A class to instantiate, or a callable returning the value. Sync and async functions, generators, async generators, and @(async)contextmanager factories are all accepted.

required
scope Scope

Lifetime of the produced value. Defaults to Scope.SINGLETON.

SINGLETON
provides type[object] | TokenKeyBase | str | None

Key to register under, overriding the inferred one — for example to bind a concrete class against a Protocol, or a factory under a Token or a name instead of its return annotation.

None
tag str | None

Disambiguator when several providers share a key; resolve it with a matching tag or Annotated[..., Tag(...)].

None
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
check Callable[[T], object] | None

Callable verifying the produced value, exposed by FrozenContainer.checks and run by FrozenContainer.health. It receives the value and is healthy unless it raises or returns False.

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from depin import Container
>>> class Clock:
...     def now(self) -> str:
...         return 'noon'
>>> di = Container().bind(Clock).freeze()
>>> di[Clock].now()
'noon'

value

value(
    token: Token[T],
    value: T,
    *,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self

Bind a ready-made value to a Token.

The value is registered as a singleton and returned as-is on resolution — no construction, no parameter wiring. Use this for configuration and other plain values that have no factory.

Parameters:

Name Type Description Default
token Token[T]

The key to register the value under.

required
value T

The value returned on resolution.

required
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
check Callable[[T], object] | None

Callable verifying the produced value, exposed by FrozenContainer.checks and run by FrozenContainer.health. It receives the value and is healthy unless it raises or returns False.

None
Example
>>> from depin import Container, Token
>>> max_conn = Token[int]('max.conn')
>>> di = Container().value(max_conn, 10).freeze()
>>> di[max_conn]
10

scope_value

scope_value(
    key: type[T] | Token[T],
    *,
    tag: str | None = None,
    when: Condition | None = None,
) -> Self

Declare a key whose value is supplied by whoever opens the scope.

No factory is called. At resolution time the value must already have been placed into the active scope with ScopeFrame.provide() — by ASGI middleware, a CLI entry point, a test fixture. The binding is Scope.SCOPED, so resolving it outside a scope raises OutsideScopeError, and resolving inside a scope that never received the value raises MissingProviderError. This is how the FastAPI integration exposes the per-request Request.

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
Example
>>> from depin import Container
>>> class Principal:
...     def __init__(self, name: str) -> None:
...         self.name = name
>>> class Audit:
...     def __init__(self, who: Principal) -> None:
...         self.who = who
>>> from depin import Scope
>>> di = Container().scope_value(Principal).bind(Audit, scope=Scope.SCOPED).freeze()
>>> with di.scope() as frame:
...     frame.provide(Principal, Principal('ana'))
...     di[Audit].who.name
'ana'

alias

alias(
    key: ProviderKey,
    *,
    to: ProviderKey,
    tag: str | None = None,
    to_tag: str | None = None,
    when: Condition | None = None,
) -> Self

Register key as a second name for an existing binding.

Resolving the alias resolves the target and returns its value. The target keeps its own lifetime, its own cache entry, and its own teardown, so a singleton reached through an alias is still built once and torn down once, and both names return the same object.

The alias caches nothing itself, which is why it takes no scope. It is an ordinary node in the validated graph: an unbound target, a duplicate alias, a cycle through an alias, and a singleton that reaches a scoped provider through one are all rejected by Container.freeze(), and the alias appears in FrozenContainer.explain() and in both graph exports.

depin does not check that the target satisfies the alias key. A Protocol that is not runtime_checkable cannot be checked at all, and a structural alias between unrelated classes is legitimate.

Parameters:

Name Type Description Default
key ProviderKey

The new name to register. A class, a Token, or a string. A string key is reachable only from a parameter annotated Annotated[T, Named('...')]; FrozenContainer.resolve and frozen[key] do not accept one, so prefer a class or a Token for a key resolved directly.

required
to ProviderKey

The binding to delegate to. May itself be an alias.

required
tag str | None

Disambiguator for the alias, matching the tag of a resolution or of an Annotated[..., Tag(...)] parameter.

None
to_tag str | None

The target's tag, when the target is registered under one.

None
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from typing import Protocol
>>> from depin import Container
>>> class Store(Protocol):
...     def get(self) -> str: ...
>>> class PostgresStore:
...     def get(self) -> str:
...         return 'pg'
>>> di = Container().bind(PostgresStore).alias(Store, to=PostgresStore).freeze()
>>> di.resolve(Store) is di[PostgresStore]
True

collect

collect(
    element: ProviderKey,
    members: Sequence[ProviderKey],
    *,
    tag: str | None = None,
    when: Condition | None = None,
) -> Self

Register a list of existing bindings under the key list[element].

Resolving that key returns each member's value, in the order given here. Members keep their own lifetimes, cache entries, and teardowns, so a singleton member is built once however many collections name it, and a scoped member is rebuilt per scope. Every resolution returns a new list, so no caller can mutate another's.

The declaration is what makes a multi-binding explicit. Members stay bound under their own keys, so registering two implementations under one key by accident still raises DuplicateProviderError, and the collection occupies list[element], which no ordinary binding claims.

A collection is an ordinary node in the validated graph: an unbound member, a member listed twice, two collections over one element and tag, a cycle through a collection, and a singleton that reaches a scoped member through one are all rejected by Container.freeze(). An empty collection is legal and resolves to an empty list.

Parameters:

Name Type Description Default
element ProviderKey

The key each member provides. The collection is registered under a list of it. A string element registers list['name'], which resolves directly through FrozenContainer.resolve or frozen[list['name']], but cannot be written as a parameter annotation: get_type_hints treats the string inside list[...] as a forward reference and fails to resolve it.

required
members Sequence[ProviderKey]

The bindings to gather, in the order they should appear.

required
tag str | None

Disambiguator when several collections share an element.

None
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from typing import Protocol
>>> from depin import Container
>>> class Handler(Protocol):
...     def run(self) -> str: ...
>>> class Email:
...     def run(self) -> str:
...         return 'email'
>>> class Sms:
...     def run(self) -> str:
...         return 'sms'
>>> di = Container().bind(Email).bind(Sms).collect(Handler, [Email, Sms]).freeze()
>>> [handler.run() for handler in di.resolve(list[Handler])]
['email', 'sms']

decorate

decorate(
    key: ProviderKey,
    wrapper: type[object] | Callable[..., object],
    *,
    tag: str | None = None,
    when: Condition | None = None,
) -> Self

Wrap an existing binding without changing its registration.

Every consumer of key receives what wrapper returns, including consumers deep in the graph. The binding that was registered keeps its lifetime, its cache entry, and its teardown: it is built once, in the position it would have occupied undecorated, and the wrapper is built after it and torn down before it.

wrapper declares one parameter whose key and tag are the decorated ones — that parameter receives the value being wrapped — and any number of further parameters, which are ordinary dependencies resolved from the graph. The wrapper takes no scope of its own: it runs at the lifetime of the binding it wraps.

Decorators stack. Two calls over one key apply in registration order, so the last registered is the outermost.

Parameters:

Name Type Description Default
key ProviderKey

The binding to wrap. A class, a Token, a string, or a parameterised generic.

required
wrapper type[object] | Callable[..., object]

A class or factory producing the decorated value. Any provider shape is accepted, async ones included; an async wrapper makes the key resolvable only through FrozenContainer.aresolve.

required
tag str | None

The decorated binding's tag, when it has one.

None
when Condition | None

Condition deciding whether this decorator enters the plan. A callable is evaluated inside Container.freeze().

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from depin import Container
>>> class Store:
...     def get(self) -> str:
...         return 'plain'
>>> class Loud:
...     def __init__(self, inner: Store) -> None:
...         self.inner = inner
...     def get(self) -> str:
...         return self.inner.get().upper()
>>> di = Container().bind(Store).decorate(Store, Loud).freeze()
>>> di[Store].get()
'PLAIN'

singleton

singleton(
    *,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
) -> ScopeDecorator

Decorator form of bind(..., scope=Scope.SINGLETON).

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
Example
>>> from depin import Registry
>>> registry = Registry()
>>> @registry.singleton()
... class Cache: ...
>>> from depin import Container
>>> di = Container(registry).freeze()
>>> di[Cache] is di[Cache]
True

scoped

scoped(
    *,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
) -> ScopeDecorator

Decorator form of bind(..., scope=Scope.SCOPED).

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

transient

transient(
    *,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
) -> ScopeDecorator

Decorator form of bind(..., scope=Scope.TRANSIENT).

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

include

include(*sources: Bindings) -> Self

Append the bindings of one or more sources, in order.

Each source is anything satisfying Bindings, including a Manifest, Registry, or another Container. A source is materialized and appended atomically as one contiguous segment; if a later independent source fails, earlier segments remain. Records are not de-duplicated: a repeated key reaches DuplicateProviderError at Container.freeze().

Parameters:

Name Type Description Default
*sources Bindings

Completed binding sources in append order.

()

Returns:

Type Description
Self

self, for chaining.

Raises:

Type Description
InvalidProviderError

A source does not satisfy Bindings, exposes a malformed records() member or result, yields something other than a binding record, or is a Catalog not composed through a Manifest.

Example
>>> from depin import Catalog, Container, Manifest, provider
>>> class Logger: ...
>>> class Metrics: ...
>>> providers = Catalog(__name__, provider(Logger), provider(Metrics))
>>> manifest = Manifest(__name__, providers)
>>> di = Container().include(manifest).freeze()
>>> isinstance(di[Logger], Logger) and isinstance(di[Metrics], Metrics)
True

records

records() -> Iterable[BindRecord]

Return a snapshot of the bindings (the Bindings contract).

The returned tuple is a copy; mutating it does not affect this collector.

freeze

freeze() -> FrozenContainer

Validate the dependency graph and return an immutable runtime view.

All static checks happen here, before anything is constructed: every required provider exists, the graph is acyclic, no key is bound twice, no singleton depends on a scoped provider it would capture for life, and every factory exposes enough type information to infer its key and parameters. The frozen container also pre-computes which providers need async resolution, so FrozenContainer.resolve() can reject async providers up front instead of blocking an event loop. Every binding's when condition is evaluated here as well; a binding whose condition does not hold contributes no node and is not validated at all.

Raises:

Type Description
MissingProviderError

A required dependency has no provider, a decorator names a key nothing binds, or a parameter requires a key that only an inactive binding declares.

CircularDependencyError

The dependency graph contains a cycle.

DuplicateProviderError

Two bindings resolve to the same key and tag, or a collection lists the same member twice.

CaptiveDependencyError

A singleton depends on a scoped provider.

InvalidProviderError

A factory lacks a return annotation (and no provides=), a parameter has no type annotation and no default, a binding is neither a class nor a callable, an alias names a key or a target that is not a class, a Token, a string, or a list[X], a collection names an element or a member that is not one of those either, a parameter is annotated with a union that names two or more providers, a decorator declares no parameter for the key it decorates (or declares two or more), the decorated binding is a scope_value, or a when value is neither a bool nor a callable.

InvalidScopeError

A generator or context-manager provider is bound as transient, or such a decorator wraps a transient binding.

Example
>>> from depin import Container, Scope
>>> from depin.errors import CaptiveDependencyError
>>> class Session: ...
>>> class Repo:
...     def __init__(self, session: Session) -> None: ...
>>> builder = (
...     Container()
...     .bind(Session, scope=Scope.SCOPED)
...     .bind(Repo, scope=Scope.SINGLETON)
... )
>>> try:
...     builder.freeze()
... except CaptiveDependencyError:
...     print('rejected')
rejected

Registry

Bases: BindingCollector

A reusable, composable collection of bindings.

A Registry holds the same kind of bindings as a Container but performs no validation and no resolution. Unlike an immutable provider-only Catalog, it is a mutable builder for every explicit binding form. Registries compose with |, can include a Manifest, and can feed one or more containers.

Parameters:

Name Type Description Default
name str

Optional label, used only to identify the registry; combining two named registries keeps the first non-empty name.

''
Example
>>> from depin import Container, Registry
>>> class Logger: ...
>>> class Metrics: ...
>>> infra = Registry('infra').bind(Logger)
>>> obs = Registry('obs').bind(Metrics)
>>> di = Container(infra | obs).freeze()
>>> isinstance(di[Logger], Logger) and isinstance(di[Metrics], Metrics)
True

bind

bind(
    source: type[T],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., Generator[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., AsyncGenerator[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., AbstractContextManager[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., AbstractAsyncContextManager[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., Awaitable[T]],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: Callable[..., T],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self
bind(
    source: type[T]
    | Callable[..., Generator[T]]
    | Callable[..., AsyncGenerator[T]]
    | Callable[..., AbstractContextManager[T]]
    | Callable[..., AbstractAsyncContextManager[T]]
    | Callable[..., Awaitable[T]]
    | Callable[..., T],
    *,
    scope: Scope = Scope.SINGLETON,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self

Register a class or factory as a provider.

The provider key is inferred from source: a class is keyed by itself (or by its @provides(...) target), a factory by its return annotation (unwrapped for generator and context-manager factories). Constructor and factory parameters are themselves resolved from their type hints, so the whole graph is wired by type.

Parameters:

Name Type Description Default
source type[T] | Callable[..., Generator[T]] | Callable[..., AsyncGenerator[T]] | Callable[..., AbstractContextManager[T]] | Callable[..., AbstractAsyncContextManager[T]] | Callable[..., Awaitable[T]] | Callable[..., T]

A class to instantiate, or a callable returning the value. Sync and async functions, generators, async generators, and @(async)contextmanager factories are all accepted.

required
scope Scope

Lifetime of the produced value. Defaults to Scope.SINGLETON.

SINGLETON
provides type[object] | TokenKeyBase | str | None

Key to register under, overriding the inferred one — for example to bind a concrete class against a Protocol, or a factory under a Token or a name instead of its return annotation.

None
tag str | None

Disambiguator when several providers share a key; resolve it with a matching tag or Annotated[..., Tag(...)].

None
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
check Callable[[T], object] | None

Callable verifying the produced value, exposed by FrozenContainer.checks and run by FrozenContainer.health. It receives the value and is healthy unless it raises or returns False.

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from depin import Container
>>> class Clock:
...     def now(self) -> str:
...         return 'noon'
>>> di = Container().bind(Clock).freeze()
>>> di[Clock].now()
'noon'

value

value(
    token: Token[T],
    value: T,
    *,
    when: Condition | None = None,
    check: Callable[[T], object] | None = None,
) -> Self

Bind a ready-made value to a Token.

The value is registered as a singleton and returned as-is on resolution — no construction, no parameter wiring. Use this for configuration and other plain values that have no factory.

Parameters:

Name Type Description Default
token Token[T]

The key to register the value under.

required
value T

The value returned on resolution.

required
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
check Callable[[T], object] | None

Callable verifying the produced value, exposed by FrozenContainer.checks and run by FrozenContainer.health. It receives the value and is healthy unless it raises or returns False.

None
Example
>>> from depin import Container, Token
>>> max_conn = Token[int]('max.conn')
>>> di = Container().value(max_conn, 10).freeze()
>>> di[max_conn]
10

scope_value

scope_value(
    key: type[T] | Token[T],
    *,
    tag: str | None = None,
    when: Condition | None = None,
) -> Self

Declare a key whose value is supplied by whoever opens the scope.

No factory is called. At resolution time the value must already have been placed into the active scope with ScopeFrame.provide() — by ASGI middleware, a CLI entry point, a test fixture. The binding is Scope.SCOPED, so resolving it outside a scope raises OutsideScopeError, and resolving inside a scope that never received the value raises MissingProviderError. This is how the FastAPI integration exposes the per-request Request.

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
Example
>>> from depin import Container
>>> class Principal:
...     def __init__(self, name: str) -> None:
...         self.name = name
>>> class Audit:
...     def __init__(self, who: Principal) -> None:
...         self.who = who
>>> from depin import Scope
>>> di = Container().scope_value(Principal).bind(Audit, scope=Scope.SCOPED).freeze()
>>> with di.scope() as frame:
...     frame.provide(Principal, Principal('ana'))
...     di[Audit].who.name
'ana'

alias

alias(
    key: ProviderKey,
    *,
    to: ProviderKey,
    tag: str | None = None,
    to_tag: str | None = None,
    when: Condition | None = None,
) -> Self

Register key as a second name for an existing binding.

Resolving the alias resolves the target and returns its value. The target keeps its own lifetime, its own cache entry, and its own teardown, so a singleton reached through an alias is still built once and torn down once, and both names return the same object.

The alias caches nothing itself, which is why it takes no scope. It is an ordinary node in the validated graph: an unbound target, a duplicate alias, a cycle through an alias, and a singleton that reaches a scoped provider through one are all rejected by Container.freeze(), and the alias appears in FrozenContainer.explain() and in both graph exports.

depin does not check that the target satisfies the alias key. A Protocol that is not runtime_checkable cannot be checked at all, and a structural alias between unrelated classes is legitimate.

Parameters:

Name Type Description Default
key ProviderKey

The new name to register. A class, a Token, or a string. A string key is reachable only from a parameter annotated Annotated[T, Named('...')]; FrozenContainer.resolve and frozen[key] do not accept one, so prefer a class or a Token for a key resolved directly.

required
to ProviderKey

The binding to delegate to. May itself be an alias.

required
tag str | None

Disambiguator for the alias, matching the tag of a resolution or of an Annotated[..., Tag(...)] parameter.

None
to_tag str | None

The target's tag, when the target is registered under one.

None
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from typing import Protocol
>>> from depin import Container
>>> class Store(Protocol):
...     def get(self) -> str: ...
>>> class PostgresStore:
...     def get(self) -> str:
...         return 'pg'
>>> di = Container().bind(PostgresStore).alias(Store, to=PostgresStore).freeze()
>>> di.resolve(Store) is di[PostgresStore]
True

collect

collect(
    element: ProviderKey,
    members: Sequence[ProviderKey],
    *,
    tag: str | None = None,
    when: Condition | None = None,
) -> Self

Register a list of existing bindings under the key list[element].

Resolving that key returns each member's value, in the order given here. Members keep their own lifetimes, cache entries, and teardowns, so a singleton member is built once however many collections name it, and a scoped member is rebuilt per scope. Every resolution returns a new list, so no caller can mutate another's.

The declaration is what makes a multi-binding explicit. Members stay bound under their own keys, so registering two implementations under one key by accident still raises DuplicateProviderError, and the collection occupies list[element], which no ordinary binding claims.

A collection is an ordinary node in the validated graph: an unbound member, a member listed twice, two collections over one element and tag, a cycle through a collection, and a singleton that reaches a scoped member through one are all rejected by Container.freeze(). An empty collection is legal and resolves to an empty list.

Parameters:

Name Type Description Default
element ProviderKey

The key each member provides. The collection is registered under a list of it. A string element registers list['name'], which resolves directly through FrozenContainer.resolve or frozen[list['name']], but cannot be written as a parameter annotation: get_type_hints treats the string inside list[...] as a forward reference and fails to resolve it.

required
members Sequence[ProviderKey]

The bindings to gather, in the order they should appear.

required
tag str | None

Disambiguator when several collections share an element.

None
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from typing import Protocol
>>> from depin import Container
>>> class Handler(Protocol):
...     def run(self) -> str: ...
>>> class Email:
...     def run(self) -> str:
...         return 'email'
>>> class Sms:
...     def run(self) -> str:
...         return 'sms'
>>> di = Container().bind(Email).bind(Sms).collect(Handler, [Email, Sms]).freeze()
>>> [handler.run() for handler in di.resolve(list[Handler])]
['email', 'sms']

decorate

decorate(
    key: ProviderKey,
    wrapper: type[object] | Callable[..., object],
    *,
    tag: str | None = None,
    when: Condition | None = None,
) -> Self

Wrap an existing binding without changing its registration.

Every consumer of key receives what wrapper returns, including consumers deep in the graph. The binding that was registered keeps its lifetime, its cache entry, and its teardown: it is built once, in the position it would have occupied undecorated, and the wrapper is built after it and torn down before it.

wrapper declares one parameter whose key and tag are the decorated ones — that parameter receives the value being wrapped — and any number of further parameters, which are ordinary dependencies resolved from the graph. The wrapper takes no scope of its own: it runs at the lifetime of the binding it wraps.

Decorators stack. Two calls over one key apply in registration order, so the last registered is the outermost.

Parameters:

Name Type Description Default
key ProviderKey

The binding to wrap. A class, a Token, a string, or a parameterised generic.

required
wrapper type[object] | Callable[..., object]

A class or factory producing the decorated value. Any provider shape is accepted, async ones included; an async wrapper makes the key resolvable only through FrozenContainer.aresolve.

required
tag str | None

The decorated binding's tag, when it has one.

None
when Condition | None

Condition deciding whether this decorator enters the plan. A callable is evaluated inside Container.freeze().

None

Returns:

Type Description
Self

self, for chaining.

Example
>>> from depin import Container
>>> class Store:
...     def get(self) -> str:
...         return 'plain'
>>> class Loud:
...     def __init__(self, inner: Store) -> None:
...         self.inner = inner
...     def get(self) -> str:
...         return self.inner.get().upper()
>>> di = Container().bind(Store).decorate(Store, Loud).freeze()
>>> di[Store].get()
'PLAIN'

singleton

singleton(
    *,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
) -> ScopeDecorator

Decorator form of bind(..., scope=Scope.SINGLETON).

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None
Example
>>> from depin import Registry
>>> registry = Registry()
>>> @registry.singleton()
... class Cache: ...
>>> from depin import Container
>>> di = Container(registry).freeze()
>>> di[Cache] is di[Cache]
True

scoped

scoped(
    *,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
) -> ScopeDecorator

Decorator form of bind(..., scope=Scope.SCOPED).

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

transient

transient(
    *,
    provides: type[object]
    | TokenKeyBase
    | str
    | None = None,
    tag: str | None = None,
    when: Condition | None = None,
) -> ScopeDecorator

Decorator form of bind(..., scope=Scope.TRANSIENT).

Parameters:

Name Type Description Default
when Condition | None

Condition deciding whether this binding enters the plan. A callable is evaluated inside Container.freeze().

None

include

include(*sources: Bindings) -> Self

Append the bindings of one or more sources, in order.

Each source is anything satisfying Bindings, including a Manifest, Registry, or another Container. A source is materialized and appended atomically as one contiguous segment; if a later independent source fails, earlier segments remain. Records are not de-duplicated: a repeated key reaches DuplicateProviderError at Container.freeze().

Parameters:

Name Type Description Default
*sources Bindings

Completed binding sources in append order.

()

Returns:

Type Description
Self

self, for chaining.

Raises:

Type Description
InvalidProviderError

A source does not satisfy Bindings, exposes a malformed records() member or result, yields something other than a binding record, or is a Catalog not composed through a Manifest.

Example
>>> from depin import Catalog, Container, Manifest, provider
>>> class Logger: ...
>>> class Metrics: ...
>>> providers = Catalog(__name__, provider(Logger), provider(Metrics))
>>> manifest = Manifest(__name__, providers)
>>> di = Container().include(manifest).freeze()
>>> isinstance(di[Logger], Logger) and isinstance(di[Metrics], Metrics)
True

records

records() -> Iterable[BindRecord]

Return a snapshot of the bindings (the Bindings contract).

The returned tuple is a copy; mutating it does not affect this collector.

__or__

__or__(other: Registry) -> Registry

Combine two registries into a new one, concatenating their bindings.

Neither operand is modified. The result takes this registry's name, or the other's when this one is unnamed.

Bindings

Bases: Protocol

Anything that can hand a container a set of bindings.

Manifest, Registry, and Container satisfy it, so any of them can seed a new container. Implement it on your own type to plug a custom binding source into the same call.

Example
>>> from depin import Bindings, Catalog, Container, Manifest, provider
>>> class Svc: ...
>>> manifest = Manifest(__name__, Catalog(__name__, provider(Svc)))
>>> isinstance(manifest, Bindings)
True
>>> di = Container(manifest).freeze()
>>> isinstance(di[Svc], Svc)
True

records

records() -> Iterable[BindRecord]

Return this source's bindings, in declaration order.

ScopeDecorator

Callable returned by the singleton / scoped / transient methods.

Applying it to a class or factory registers that target at the chosen scope and returns the target unchanged, so it works as a decorator.

__call__

__call__(target: type[T]) -> type[T]
__call__(target: Callable[P, R]) -> Callable[P, R]
__call__(target: object) -> object

Register target and return it unchanged.

Raises:

Type Description
InvalidProviderError

target is neither a class nor a callable.