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 — |
()
|
Raises:
| Type | Description |
|---|---|
InvalidProviderError
|
A source does not satisfy |
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
|
required |
scope
|
Scope
|
Lifetime of the produced value. Defaults to
|
SINGLETON
|
provides
|
type[object] | TokenKeyBase | str | None
|
Key to register under, overriding the inferred one — for
example to bind a concrete class against a |
None
|
tag
|
str | None
|
Disambiguator when several providers share a key; resolve it with
a matching |
None
|
when
|
Condition | None
|
Condition deciding whether this binding enters the plan.
A callable is evaluated inside |
None
|
check
|
Callable[[T], object] | None
|
Callable verifying the produced value, exposed by
|
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
None
|
check
|
Callable[[T], object] | None
|
Callable verifying the produced value, exposed by
|
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 |
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 |
required |
to
|
ProviderKey
|
The binding to delegate to. May itself be an alias. |
required |
tag
|
str | None
|
Disambiguator for the alias, matching the |
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 |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
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 |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
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
|
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 |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
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 |
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 |
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
|
|
Raises:
| Type | Description |
|---|---|
InvalidProviderError
|
A source does not satisfy |
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
|
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
|
required |
scope
|
Scope
|
Lifetime of the produced value. Defaults to
|
SINGLETON
|
provides
|
type[object] | TokenKeyBase | str | None
|
Key to register under, overriding the inferred one — for
example to bind a concrete class against a |
None
|
tag
|
str | None
|
Disambiguator when several providers share a key; resolve it with
a matching |
None
|
when
|
Condition | None
|
Condition deciding whether this binding enters the plan.
A callable is evaluated inside |
None
|
check
|
Callable[[T], object] | None
|
Callable verifying the produced value, exposed by
|
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
None
|
check
|
Callable[[T], object] | None
|
Callable verifying the produced value, exposed by
|
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 |
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 |
required |
to
|
ProviderKey
|
The binding to delegate to. May itself be an alias. |
required |
tag
|
str | None
|
Disambiguator for the alias, matching the |
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 |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
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 |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
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
|
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 |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
|
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 |
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 |
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 |
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
|
|
Raises:
| Type | Description |
|---|---|
InvalidProviderError
|
A source does not satisfy |
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
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
|
|