1. Configuration#
A configuration is a typed, documented parameter set. Nothing is registered yet —
this step is only about what a Configuration is, because everything later is
built on it.
python examples/tutorial/01_configuration.py
Fields are declared as annotated class attributes wrapped in Param, which is a
pydantic Field with a little extra: a description that stays attached, and the
variants that step 3 uses.
class TokenizerConfig(Configuration):
"""Parameters for a tokenizer. Plain types, with defaults."""
lowercase: bool = Param(True, description="Fold text to lower case first")
max_tokens: int = Param(
512,
ge=1, # any pydantic constraint works
description="Truncate sequences beyond this many tokens",
)
separator: str = Param(" ", description="Token separator")
Constraints are pydantic’s, and they are enforced when the value is set rather than when it is used, so an impossible configuration never reaches a component.
What to notice#
config.valuesgives a plain{field: value}dictionary. That is what gets unpacked into the component’s__init__, and it is why a component never needs to know cinnamon exists.Descriptions are not comments. They survive into
model_fields, so a configuration documents itself and the CLI can read them back.The end of the file has a commented-out field holding a
sqlite3.Connection. Uncomment it and run again: cinnamon refuses it and explains why. A configuration holding a live object has stopped describing a component and started being one.
The whole file#
1"""
21. A configuration is a typed, documented parameter set.
3
4 python examples/tutorial/01_configuration.py
5
6Nothing is registered yet -- this step is only about what a ``Configuration``
7is, because everything later is built on it.
8
9The rule to carry forward: **components carry the weight, configurations
10describe it.** A configuration holds the parameters a component needs. It never
11holds the component, or a model, or a database handle. Keeping it light is what
12makes it cheap to write fifty of them, which is the whole point of the library.
13"""
14
15from cinnamon.configuration import Configuration, Param
16
17
18class TokenizerConfig(Configuration):
19 """Parameters for a tokenizer. Plain types, with defaults."""
20
21 lowercase: bool = Param(True, description="Fold text to lower case first")
22 max_tokens: int = Param(
23 512,
24 ge=1, # any pydantic constraint works
25 description="Truncate sequences beyond this many tokens",
26 )
27 separator: str = Param(" ", description="Token separator")
28
29
30def main() -> None:
31 config = TokenizerConfig()
32 print("defaults: ", config.values)
33
34 # Override at construction, exactly like any pydantic model.
35 custom = TokenizerConfig(max_tokens=128, lowercase=False)
36 print("overridden: ", custom.values)
37
38 # Constraints are enforced when the value is set, not when it is used.
39 try:
40 TokenizerConfig(max_tokens=0)
41 except Exception as error:
42 print("max_tokens=0 -> ", type(error).__name__, "(ge=1 rejected it)")
43
44 # Descriptions stay attached to the fields, so a configuration documents
45 # itself. `cmn-build` and the CLI prompts read them back.
46 print("\nfields:")
47 for name, field in TokenizerConfig.model_fields.items():
48 print(f" {name:12s} {str(field.annotation.__name__):6s} {field.description}")
49
50 # Try this: uncomment the following and run again. cinnamon refuses it and
51 # explains why -- a configuration that holds a live object has stopped
52 # describing a component and started being one.
53 #
54 # import sqlite3
55 # class Leaky(Configuration):
56 # connection: sqlite3.Connection = None
57
58
59if __name__ == "__main__":
60 main()
Next: 2. Registration binds a configuration to a component.