Graph diagnostics¶
The data behind FrozenContainer.graph() and FrozenContainer.explain().
ProviderKey ¶
ProviderKey = (
type[object]
| TokenKeyBase
| str
| GenericAlias
| Underlying
)
What a provider can be bound and resolved under: a class, a Token, a name, or a parameterised generic.
The parameterised case needs no member of its own. A generic written in
expression position — Repo[User], Reader[User] — has the static type
type[Repo[User]], which type[object] already covers; the
types.GenericAlias member covers the runtime object a builtin or ABC origin
produces, such as list[Handler]. A deprecated typing alias
(typing.List[X]) is a key at neither level: Container.freeze() rejects it
and names the canonical spelling to write instead.
An Underlying is the fifth: the identity Container.decorate moves a
decorated binding's inner form to, so the wrapper can occupy the public key.
Condition ¶
Condition = bool | Callable[[], bool]
What when= accepts on a registration.
A bool is read where it is written. A callable is called once per
Container.freeze(), with no arguments, and its result is read for truth — so a
predicate over configuration or the environment is evaluated when the graph is
built, not when a value is resolved.
Underlying
dataclass
¶
The key a decorated binding's inner form is registered under.
Container.decorate leaves the wrapper on the public key and moves what it
wraps here, so both are ordinary nodes of the validated graph: the wrapper
reaches its inner form over a real edge, and the inner form keeps the
lifetime, the cache entry, and the teardown it had undecorated.
applied counts the decorators already applied below the public key, so
the registered binding is Underlying(key, 0) and a second decorator over
the same key sees Underlying(key, 1). Construct one to inspect a
decorated binding — FrozenContainer.explain and DependencyGraph.find
accept it — not to register anything.
Example
>>> from depin import Container, ProviderShape, Underlying
>>> 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.graph().node(Underlying(Store, 0)).shape is ProviderShape.CLASS
True
DependencyGraph ¶
The validated dependency graph, as data.
Returned by FrozenContainer.graph(). Nodes come in resolution order: a
node never precedes one it depends on. The view describes the plan
Container.freeze() validated, so an active FrozenContainer.override
does not change it.
Example
>>> from depin import Container
>>> class Config: ...
>>> class Service:
... def __init__(self, config: Config) -> None: ...
>>> di = Container().bind(Config).bind(Service).freeze()
>>> print(di.graph().mermaid())
graph LR
n0["Config<br/>singleton, class"]
n1["Service<br/>singleton, class"]
n1 -->|config| n0
roots
property
¶
roots: tuple[GraphNode, ...]
The nodes no other node depends on, in resolution order.
node ¶
node(
key: ProviderKey, *, tag: str | None = None
) -> GraphNode
Return the node bound under key and tag.
Raises:
| Type | Description |
|---|---|
MissingProviderError
|
Nothing is bound under that key and tag. Use
|
find ¶
find(
key: ProviderKey, *, tag: str | None = None
) -> GraphNode | None
Return the node bound under key and tag, or None when nothing is.
GraphNode
dataclass
¶
One provider in the validated graph.
dependencies is in the provider's own parameter order, which is what
makes every rendering of the graph reproducible.
Example
>>> from depin import Container, ProviderShape
>>> class Config: ...
>>> di = Container().bind(Config).freeze()
>>> node = di.graph().node(Config)
>>> node.scope.value, node.shape is ProviderShape.CLASS
('singleton', True)
GraphEdge
dataclass
¶
One provider parameter and the binding identity it resolves to.
satisfied is false for a parameter that no binding provides, which
Container.freeze() allows only when the parameter carries a default or
admits None. has_default and optional say which, and
has_default wins when both hold, because depin never replaces a value
the author wrote.
Example
>>> from depin import Container
>>> class Config: ...
>>> class Cache: ...
>>> class Service:
... def __init__(self, config: Config, cache: Cache | None) -> None: ...
>>> di = Container().bind(Config).bind(Service).freeze()
>>> bound, unbound = di.graph().node(Service).dependencies
>>> bound.parameter, bound.satisfied
('config', True)
>>> unbound.parameter, unbound.satisfied, unbound.optional, unbound.has_default
('cache', False, True, False)
ProviderShape ¶
Bases: Enum
How a provider produces its value, and whether it owns a teardown.
Reported by GraphNode.shape. Container.freeze() infers it from the
binding: a class, a factory's kind, or a value.
Attributes:
| Name | Type | Description |
|---|---|---|
CLASS |
A class, instantiated with its resolved constructor arguments. |
|
FUNCTION |
A synchronous factory, called with its resolved arguments. |
|
ASYNC_FUNCTION |
A coroutine factory, awaited. Requires |
|
GENERATOR |
A generator factory that yields once and resumes at teardown. Cannot be transient. |
|
ASYNC_GENERATOR |
An async generator factory that yields once and
resumes at teardown. Requires |
|
CONTEXT_MANAGER |
A factory returning a context manager, entered on construction and exited at teardown. Cannot be transient. |
|
ASYNC_CONTEXT_MANAGER |
A factory returning an async context manager.
Requires |
|
VALUE |
A value bound directly with |
|
FRAME |
A value the active scope frame supplies, bound with
|
|
ALIAS |
A second name for another binding, declared with
|
|
COLLECTION |
A list of several bindings, declared with
|
Example
>>> from depin import Container, ProviderShape
>>> class Config: ...
>>> di = Container().bind(Config).freeze()
>>> di.graph().node(Config).shape is ProviderShape.CLASS
True