2. Registration#

A component is an ordinary class — no base class, no decorator. cinnamon needs only its import path, as a string, which it resolves when you ask for an instance and not before.

python examples/tutorial/02_registration.py

That is what keeps a build independent of how heavy your components are: nothing imports torch to look at a registry.

class Tokenizer:
    """A component: it does the work, and it is a plain class."""

    def __init__(self, lowercase: bool, separator: str):
        self.lowercase = lowercase
        self.separator = separator

    def tokenize(self, text: str) -> list[str]:
        return (text.lower() if self.lowercase else text).split(self.separator)

A registration binds (configuration, name, namespace, tags) to that component path. A RegistrationKey — name, namespace, and a set of tags — is how you name the binding afterwards, and how you ask for the component.

What to notice#

  • Registry.dag_resolution() returns two sets, valid and invalid. Step 6 explains why the second one is not an error list.

  • Registry.from_key is the moment the component class is imported. Before it, the registry knows the path and nothing more.

  • Registry.retrieve_configuration gets the configuration back without building anything at all.

The whole file#

 1"""
 22. Binding a configuration to a component, and building it.
 3
 4    python examples/tutorial/02_registration.py
 5
 6A component is an ordinary class -- no base class, no decorator. cinnamon needs
 7only its *import path*, as a string, which it resolves when you ask for an
 8instance and not before. That is what keeps a build independent of how heavy
 9your components are: nothing imports torch to look at a registry.
10"""
11
12from cinnamon.configuration import Configuration, Param
13from cinnamon.registry import RegistrationKey, Registry
14
15
16class Tokenizer:
17    """A component: it does the work, and it is a plain class."""
18
19    def __init__(self, lowercase: bool, separator: str):
20        self.lowercase = lowercase
21        self.separator = separator
22
23    def tokenize(self, text: str) -> list[str]:
24        return (text.lower() if self.lowercase else text).split(self.separator)
25
26
27class TokenizerConfig(Configuration):
28    lowercase: bool = Param(True, description="Fold text to lower case first")
29    separator: str = Param(" ", description="Token separator")
30
31
32def main() -> None:
33    Registry.initialize()
34
35    # A registration is (configuration, name, namespace, tags) -> component.
36    # In a real project you would put this in a `configurations/` package and
37    # let `Registry.build` discover it; step 7 shows that. Registering by hand
38    # keeps this file readable in one screen.
39    Registry.register_configuration(
40        config=TokenizerConfig(),
41        name="tokenizer",
42        namespace="tutorial",
43        # Normally "mypackage.components.Tokenizer". Inside a script the module
44        # is __main__, so that is the path cinnamon needs.
45        component=f"{__name__}.Tokenizer",
46    )
47
48    # Resolution walks the dependency graph and validates every configuration.
49    valid_keys, invalid_keys = Registry.dag_resolution()
50    print(f"valid: {len(valid_keys)}   invalid: {len(invalid_keys)}")
51
52    # A RegistrationKey is how you name a registration: name + namespace + tags.
53    key = RegistrationKey(name="tokenizer", namespace="tutorial")
54    print("key:  ", key)
55
56    # ... and how you ask for the component. The class is imported now, at the
57    # moment it is needed.
58    tokenizer = Registry.from_key(key)
59    print("built:", type(tokenizer).__name__)
60    print("work: ", tokenizer.tokenize("The Quick Brown Fox"))
61
62    # The configuration itself is retrievable too, without building anything.
63    config = Registry.retrieve_configuration(registration_key=key)
64    print("config:", config.values)
65
66
67if __name__ == "__main__":
68    main()

Next: 3. Variants — the reason the library exists.