Component
A Component is any class that implements some program logic — data loading,
model training, preprocessing, evaluation, and so on.
Cinnamon does not impose any particular API on your existing code, except that the
class must inherit from Component:
from cinnamon.component import Component
class DataLoader(Component):
def __init__(self, df_path: str):
self.df_path = df_path
def load(self):
...
The same applies to more complex classes — the inheritance is the only required change.
Receiving configuration parameters
When the Registry builds a Component, it unpacks the bound Configuration’s
values dictionary directly into the component’s constructor:
component = ComponentClass(**config.values, **build_args)
This means that your component’s __init__ parameters must match the field names
of its bound Configuration:
from pathlib import Path
from cinnamon.configuration import Configuration, Param
from cinnamon.component import Component
class DataLoaderConfig(Configuration):
df_path: Path = Param('path/to/data')
batch_size: int = Param(32)
class DataLoader(Component):
def __init__(self, df_path: Path, batch_size: int):
self.df_path = df_path
self.batch_size = batch_size
You can also call a component directly from a configuration instance, without going
through the Registry:
config = DataLoaderConfig.default()
loader = DataLoader(**config.values)
Nested configurations
When a Configuration has a dependency field (a RegistrationKey pointing to
another Configuration), the Registry resolves it to a Configuration
instance before building the component.
The component therefore receives a Configuration object for that parameter,
not a primitive value:
from cinnamon.configuration import Configuration, Param
from cinnamon.registry import RegistrationKey
from cinnamon.component import Component
class ProcessorConfig(Configuration):
vocab_size: int = Param(30000)
class DataLoaderConfig(Configuration):
df_path: Path = Param('path/to/data')
processor: RegistrationKey = Param(
RegistrationKey(name='processor', tags={'default'}, namespace='nlp')
)
class DataLoader(Component):
def __init__(self, df_path: Path, processor: ProcessorConfig):
self.df_path = df_path
self.processor = processor # receives a ProcessorConfig instance
Note
See dependencies for a full explanation of how nested configurations are declared and resolved.
Building a component
Once Registry.build() has been called, a component instance can be constructed
in two equivalent ways.
Via the Registry directly:
from cinnamon.registry import Registry
loader = Registry.instantiate_component(
name='data_loader',
tags={'default'},
namespace='showcasing'
)
Via the Component class itself (syntactic sugar that also type-checks the result):
loader = DataLoader.instantiate(
name='data_loader',
tags={'default'},
namespace='showcasing'
)
DataLoader.instantiate() raises a RuntimeError if the built instance is not
actually a DataLoader. Registry.instantiate_component() skips that check but
is otherwise identical.
Both methods accept additional keyword arguments via **build_args, which are merged
into config.values at build time and can be used to override specific parameters:
loader = DataLoader.instantiate(
name='data_loader',
tags={'default'},
namespace='showcasing',
batch_size=64 # overrides the registered default of 32
)
Note
build_args only override values at instantiation time — the registered
Configuration stored in the Registry is not modified.