4. Dependencies#

A field typed as RegistrationKey is a dependency. cinnamon records it as an edge in the dependency graph, so it knows what a pipeline is made of before anything is built.

python examples/tutorial/04_dependencies.py
class PipelineConfig(Configuration):
    tokenizer: RegistrationKey = Param(
        RegistrationKey(name="tokenizer", namespace=NAMESPACE),
        description="Which tokenizer this pipeline uses",
    )
    max_tokens: int = Param(4)

Note what the component receives: the key itself, not a built object. The component decides when — and whether — to build its child.

class Pipeline:
    def __init__(self, tokenizer: RegistrationKey, max_tokens: int):
        # The child is built here, by the component, from the key it was given.
        self.tokenizer = Registry.from_key(tokenizer)
        self.max_tokens = max_tokens

    def run(self, text: str) -> list[str]:
        return self.tokenizer.tokenize(text)[: self.max_tokens]

That laziness is deliberate, and it is also why a dependency can be swapped without touching the parent’s code.

What to notice#

  • Sweeps compose down the graph. TokenizerConfig declares a variant on lowercase; the pipeline never mentions it, and yet resolution produces one pipeline configuration per tokenizer variant. The parent’s key records which child it was built against, as a derived tag such as tokenizer.lowercase=False.

  • Nobody joined those up. The edge in the graph is enough.

  • Because the child arrives as a key, a component can hold a dependency it never builds — a branch that only some configurations take.

The whole file#

 1"""
 24. Dependencies -- configurations that reference other registrations.
 3
 4    python examples/tutorial/04_dependencies.py
 5
 6A field typed as ``RegistrationKey`` is a dependency. cinnamon records it as an
 7edge in the dependency graph, so it knows what a pipeline is made of before
 8anything is built.
 9
10Note what the *component* receives: the key itself, not a built object. The
11component decides when -- and whether -- to build its child. That laziness is
12deliberate; it is also why a dependency can be swapped without touching the
13parent's code.
14"""
15
16from cinnamon.configuration import Configuration, Param
17from cinnamon.registry import RegistrationKey, Registry
18
19NAMESPACE = "tutorial"
20
21
22class Tokenizer:
23    def __init__(self, lowercase: bool):
24        self.lowercase = lowercase
25
26    def tokenize(self, text: str) -> list[str]:
27        return (text.lower() if self.lowercase else text).split()
28
29
30class Pipeline:
31    def __init__(self, tokenizer: RegistrationKey, max_tokens: int):
32        # The child is built here, by the component, from the key it was given.
33        self.tokenizer = Registry.from_key(tokenizer)
34        self.max_tokens = max_tokens
35
36    def run(self, text: str) -> list[str]:
37        return self.tokenizer.tokenize(text)[: self.max_tokens]
38
39
40class TokenizerConfig(Configuration):
41    # A variant here will show up on the *pipeline* too, further down.
42    lowercase: bool = Param(True, variants=[False])
43
44
45class PipelineConfig(Configuration):
46    tokenizer: RegistrationKey = Param(
47        RegistrationKey(name="tokenizer", namespace=NAMESPACE),
48        description="Which tokenizer this pipeline uses",
49    )
50    max_tokens: int = Param(4)
51
52
53def main() -> None:
54    Registry.initialize()
55
56    Registry.register_configuration(
57        TokenizerConfig(),
58        name="tokenizer",
59        namespace=NAMESPACE,
60        component=f"{__name__}.Tokenizer",
61    )
62    Registry.register_configuration(
63        PipelineConfig(),
64        name="pipeline",
65        namespace=NAMESPACE,
66        component=f"{__name__}.Pipeline",
67    )
68
69    valid_keys, _ = Registry.dag_resolution()
70
71    pipeline_key = RegistrationKey(name="pipeline", namespace=NAMESPACE)
72
73    # `dependencies` reports the fields cinnamon treats as graph edges.
74    config = Registry.retrieve_configuration(registration_key=pipeline_key)
75    print("pipeline depends on:")
76    for field_name, dependency in config.dependencies.items():
77        print(f"  {field_name} -> {dependency}")
78
79    pipeline = Registry.from_key(pipeline_key)
80    print("\nresult:", pipeline.run("The Quick Brown Fox Jumps Over"))
81
82    # The tokenizer's own variant propagates: the pipeline gains a key per
83    # child variant, tagged with which child it was built against. Sweeps
84    # compose down the graph without anyone wiring them together.
85    print("\nevery pipeline configuration resolution produced:")
86    for key in sorted((k for k in valid_keys if k.name == "pipeline"), key=str):
87        built = Registry.from_key(key)
88        tags = ", ".join(sorted(key.tags)) or "(defaults)"
89        print(f"  {tags:28s} lowercase={built.tokenizer.lowercase}")
90
91
92if __name__ == "__main__":
93    main()

Next: 5. Collections — depending on many registrations at once.