Core concepts
Data descriptor
DataDescriptor identifies a value and defines how to build it.
from dataioc import DataDescriptor, DataIoC
class Samples(DataDescriptor[tuple[int, ...]]):
pass
class Total(DataDescriptor[int]):
def __build__(self, container: DataIoC) -> int:
return sum(container[Samples])
container = DataIoC().add(Samples, (1, 2, 3))
assert container[Total] == 6
The descriptor is the key used by the container. A descriptor can hold hashable parameters, so one descriptor class can represent several related values. Do not mutate a descriptor after registration: its parameters participate in key equality and hashing.
Builder
A builder is either:
- a
__build__method on a descriptor or provider object; - a callable accepting one
DataIoCargument; - a class with a suitable
__build__method.
Builders request their dependencies with container[Target]. The container follows those requests when the target is accessed.
Register data and builders
| Operation | Meaning |
|---|---|
with_data(*values) |
Register instances by type; indexed instances keep their descriptors |
add(Target) |
Register a lazy builder |
add(Target, value) |
Register an existing value other than None |
container[Target] = value |
Set a value directly, including an explicit None |
add_provider(Target, provider) |
Configure an alternative builder |
add(Target, None) registers a builder because None is the method's default argument. Use item assignment to store None as data.
By default, the container can register a target's builder on first access. Strict registration requires each requested key to have data or a registered builder:
strict = DataIoC(allow_implicit_registering=False)
strict.add(Total).add(Samples, (1, 2, 3))
assert strict[Total] == 6
Cache
Each successfully built key is cached, including a value of None. Different indexed keys have separate cache entries. Ordinary types and UniqueData are shared across IDs; see Indexed data.
Register providers before the first access. Replacing a provider or source value does not invalidate already cached dependents. Use a fresh container for a new dataset or provider configuration that needs fresh results.
Failure and diagnostics
Failed builds are not cached; dependencies that were successfully built remain cached. A failed top-level build prints its dependency tree and re-raises the original exception. After providing the missing data or fixing the builder, the target can be requested again.
Successful access trees are normally cleared. Set record_all=True to retain them in container.logger; see Providers and diagnostics.
Evaluation is synchronous. The container does not provide thread safety, async construction, automatic dependent invalidation, or cycle detection.