API#

Specs are recipes of how objects should be created.

The public functions are named like classes (e.g., Object(), Singleton()), while the actual specs are private classes with underscore prefixes. This mimics the pattern seen in dataclasses.field() func vs. dataclass.Field class.

Note that we “trick” IDEs and static type checkers by casting the spec objects into whatever type they will return when instantiated by the container. Although a bit inelegant, this actually makes all the config wiring type check exactly as expected.

dilib.specs.Forward(obj: T) T#

Spec to simply forward to other spec.

Often useful as a switch to dispatch to other specs.

>>> class FooConfig(dilib.Config):
...     x0 = dilib.Object(1)
...     x1 = dilib.Object(2)
...     x = dilib.Forward(x0)
>>> config = dilib.get_config(FooConfig)
>>> container = dilib.get_container(config)
>>> container.config.x
1

The switch can be perturbed like:

>>> config = dilib.get_config(FooConfig)
>>> config.x = dilib.Forward(config.x1)
>>> container = dilib.get_container(config)
>>> container.config.x
2
dilib.specs.GlobalInput(type_: type[~dilib.specs.T] | None = None, default: ~typing.Any = <object object>) T#

Spec to use user input passed in at config instantiation.

>>> class FooConfig(dilib.Config):
...     x = dilib.GlobalInput(type_=str)
...     y = dilib.GlobalInput(type_=int, default=1)
Parameters:
  • type – Expected type of input, for both static and runtime check.

  • default – Default value if no input is provided.

dilib.specs.LocalInput(type_: type[~dilib.specs.T] | None = None, default: ~typing.Any = <object object>) T#

Spec to use user input passed in at config declaration.

>>> class FooConfig(dilib.Config):
...     x = dilib.LocalInput(type_=str)
...     y = dilib.LocalInput(type_=int, default=1)
>>> class BarConfig(dilib.Config):
...     foo_config = FooConfig(x="abc", y=123)
>>> config = dilib.get_config(BarConfig)
>>> container = dilib.get_container(config)
>>> container.config.foo_config.x
'abc'
>>> container.config.foo_config.y
123
Parameters:
  • type – Expected type of input, for both static and runtime check.

  • default – Default value if no input is provided.

dilib.specs.Object(obj: T) T#

Spec to pass through a fully-instantiated object.

>>> class FooConfig(dilib.Config):
...     x = dilib.Object(1)
Parameters:

obj – Fully-instantiated object to pass through.

dilib.specs.Prototype(func_or_type: ~typing.Callable[[~P], ~dilib.specs.T], *args: ~typing.~P, **kwargs: ~typing.~P) T#

Spec to call with args and no caching.

Can be used with anything callable, including types and functions.

>>> class Foo:
...     def __init__(self, x: int) -> None:
...         self.x = x
>>> class FooConfig(dilib.Config):
...     foo = dilib.Prototype(Foo, x=1)
>>> config = dilib.get_config(FooConfig)
>>> container = dilib.get_container(config)
>>> assert container.config.foo is not container.config.foo
class dilib.specs.PrototypeMixin(*args: Any, **kwargs: Any)#

Helper class for Prototype to ease syntax in Config.

Equivalent to dilib.Prototype(cls, …).

See:
dilib.specs.Singleton(func_or_type: ~typing.Callable[[~P], ~dilib.specs.T], *args: ~typing.~P, **kwargs: ~typing.~P) T#

Spec to call with args and caching per config field.

Can be used with anything callable, including types and functions.

>>> class Foo:
...     def __init__(self, x: int) -> None:
...         self.x = x
>>> class FooConfig(dilib.Config):
...     foo = dilib.Singleton(Foo, x=1)
>>> config = dilib.get_config(FooConfig)
>>> container = dilib.get_container(config)
>>> assert container.config.foo is container.config.foo
dilib.specs.SingletonDict(values: dict[Any, T] | None = None, /, **kwargs: T) dict[Any, T]#

Spec to create dict with args and caching per config field.

Can specify either by pointing to a dict, passing in kwargs, or unioning both.

>>> class FooConfig(dilib.Config):
...     x = dilib.Object(1)
...     y = dilib.Object(2)
...     values = dilib.SingletonDict(x=x, y=y)
...     # Equivalent to:
...     also_values = dilib.SingletonDict({"x": x, "y": y})
dilib.specs.SingletonList(*args: T) list[T]#

Spec to create list with args and caching per config field.

>>> class FooConfig(dilib.Config):
...     x = dilib.Object(1)
...     y = dilib.Object(2)
...     values = dilib.SingletonList(x, y)
class dilib.specs.SingletonMixin(*args: Any, **kwargs: Any)#

Helper class for Singleton to ease syntax in Config.

Equivalent to dilib.Singleton(cls, …).

See:
dilib.specs.SingletonTuple(*args: T) tuple[T]#

Spec to create tuple with args and caching per config field.

>>> class FooConfig(dilib.Config):
...     x = dilib.Object(1)
...     y = dilib.Object(2)
...     values = dilib.SingletonTuple(x, y)
class dilib.specs.Spec(spec_id: int | None = None)#

Represents delayed object to be instantiated later.

Use one of child classes when describing objects.

dilib.specs.config_context() Generator[None, None, None]#

Enable delayed mode for PrototypeMixin and SingletonMixin.

>>> class Foo(dilib.SingletonMixin):
...     def __init__(self, x: int) -> None:
...         self.x = x
>>> with dilib.config_context():
...     class FooConfig(dilib.Config):
...         foo = Foo(x=1)

Configs contain named specs and their dependencies.

class dilib.config.Config(*args: Any, _materialize: bool = False, **kwargs: Any)#

Description of specs and how they depend on each other.

Config author should subclass this class and describe specs like fields of a dataclass (with optional type annotation).

Config user should use get_config() to instantiate.

>>> class FooConfig(dilib.Config):
...     x = dilib.Object(1)
...     y = dilib.Singleton(lambda x: x + 1)
freeze() None#

Prevent any more perturbations to this Config instance.

dilib.config.get_config(config_cls: type[TC], **global_inputs: Any) TC#

Get instance of config object (that can optionally be perturbed).

User is required to pass in any non-defaulted global inputs.

>>> class FooConfig(dilib.Config):
...     x = dilib.GlobalInput(type_=int)
...     y = dilib.Singleton(lambda x: x + 1)
>>> config = dilib.get_config(FooConfig, x=1)
>>> container = dilib.get_container(config)

See Config.

Containers instantiate objects per specs set in configs.

class dilib.container.Container(config: TC)#

Materializes and caches (if necessary) objects based on given config.

Config user should use get_container() to instantiate.

clear() None#

Clear instance cache.

property config: TC#

More type-safe alternative to attr access.

get(key: str, default: Any | None = None) Any#

Get materialized object aliased by key, with optional default.

dilib.container.get_container(config: TC) Container[TC]#

Get instance of container object, which instantiates objects per specs.

>>> class FooConfig(dilib.Config):
...     x = dilib.GlobalInput(type_=int)
...     y = dilib.Singleton(lambda x: x + 1, x=x)
>>> config = dilib.get_config(FooConfig, x=1)
>>> container = dilib.get_container(config)
>>> container.config.y
2

Errors.

exception dilib.errors.ConfigError#

Base class for all errors.

exception dilib.errors.FrozenConfigError#

Cannot perturb config once frozen.

Note that dilib.container.get_container() automatically freezes given config.

exception dilib.errors.InputConfigError#

Invalid global or local inputs.

See error message for more info. E.g., the config user could have forgotten to include all required inputs.

exception dilib.errors.NewKeyConfigError#

Cannot try to set new key on config not described by config author.

exception dilib.errors.PerturbSpecError#

Cannot perturb spec fields (only config fields can be perturbed).

exception dilib.errors.SetChildConfigError#

Cannot set child configs.