7. A real project#
Steps 1–6 registered everything by hand in a single file so each concept stayed
readable in one screen. Real projects do not do that. They put registrations in a
configurations/ package, let Registry.build discover them, and drive the
whole thing from the command line.
examples/tutorial/07_project_layout/
├── components/
│ ├── __init__.py
│ └── summariser.py # the logic: plain classes
└── configurations/
├── __init__.py
└── summariser.py # the registrations
Registry.build finds every configurations/ package beneath the directory it
is given, imports the modules inside, and runs whatever the @register and
@register_method decorators buffered. components/ is a convention, not a
requirement — a component is found by the import path its registration names.
Those modules are ordinary Python. They may sit in subfolders of
configurations/, they may import each other — a configuration subclassing one
from a sibling module is the usual reason — and the namespace they register into
may be a constant imported from a shared module rather than repeated as a literal
in each file. Registry.build reads namespaces off the source before importing
anything, and follows such an import on disk to find the literal behind it.
Building on another project’s registrations#
A second project may register configurations that depend on the first one’s –
a package of published-experiment settings over a library of components, say.
Point Registry.build at the project whose registrations you want run, and
name the other under external_directories:
Registry.build(
directory=Path(experiments.__file__).parent,
external_directories=[Path(library.__file__).parent],
)
The distinction matters: directory is where registration scripts are
executed, while external_directories are indexed so their namespaces can be
referenced. A key from an external directory is loaded when something depends
on it, not because the directory was listed.
The registrations#
1"""
2Registrations for the worked project.
3
4`Registry.build` finds every `configurations/` package under the directory it is
5given, imports the modules inside, and runs whatever the `@register` and
6`@register_method` decorators buffered. Nothing else about the layout matters --
7`components/` is a convention, not a requirement.
8"""
9
10from cinnamon.configuration import Configuration, Param
11from cinnamon.registry import RegistrationKey, Registry, register, register_method
12
13NAMESPACE = "tutorial/summarisation"
14
15DOCUMENT = (
16 "cinnamon separates logic from configuration. Components carry the weight. "
17 "Configurations describe it. That split is what makes sweeps cheap."
18)
19
20
21class TruncatorConfig(Configuration):
22 sentences: int = Param(1, ge=1, variants=[2], description="How many to keep")
23
24 @classmethod
25 @register_method(
26 name="strategy",
27 tags={"truncate"},
28 namespace=NAMESPACE,
29 component="components.summariser.Truncator",
30 )
31 def default(cls):
32 return super().default()
33
34
35class SummariserConfig(Configuration):
36 strategy: RegistrationKey = Param(
37 RegistrationKey(name="strategy", tags={"truncate"}, namespace=NAMESPACE),
38 description="How to shorten the document",
39 )
40 document: str = Param(DOCUMENT, description="Text to summarise")
41
42
43# `@register` is the other entry point: a plain function that registers whatever
44# it likes. Use it when a `default()` classmethod would be contrived.
45@register
46def register_summarisers():
47 Registry.register_configuration(
48 config=SummariserConfig(),
49 name="summariser",
50 namespace=NAMESPACE,
51 component="components.summariser.Summariser",
52 run_method="run", # makes it discoverable by `cmn-run`
53 )
Two entry points appear here, and they are equivalent:
@register_methoddecorates adefault()classmethod on the configuration. Concise when the registration belongs naturally to the class.@registerdecorates a plain function that registers whatever it likes. Use it when adefault()classmethod would be contrived, or to register a configuration you did not write.
run_method="run" is what makes a registration discoverable by cmn-run.
The components#
1"""Components for the worked project: plain classes, no cinnamon imports needed."""
2
3from cinnamon.registry import RegistrationKey, Registry
4
5
6class Truncator:
7 """Keeps the first `sentences` sentences."""
8
9 def __init__(self, sentences: int):
10 self.sentences = sentences
11
12 def summarise(self, text: str) -> str:
13 parts = [part.strip() for part in text.split(".") if part.strip()]
14 return ". ".join(parts[: self.sentences]) + "."
15
16
17class Summariser:
18 """A runnable component: `cmn-run` calls its `run` method."""
19
20 def __init__(self, strategy: RegistrationKey, document: str):
21 self.strategy = Registry.from_key(strategy)
22 self.document = document
23
24 def run(self) -> str:
25 summary = self.strategy.summarise(self.document)
26 print(f"summary: {summary}")
27 return summary
Driving it from the command line#
cd examples/tutorial/07_project_layout
cmn-check -dir . # look for mistakes before running anything
cmn-build -dir . # resolve, and write the key list to registrations/
cmn-run -dir . # pick a configuration interactively and run it
cmn-check and cmn-build need only pip install cinnamon-core. cmn-run
prompts, so it also wants pip install "cinnamon-core[cli]". Cinnamon entry points
covers all four commands in detail.
Four registrations come out of two declarations, because the strategy declares a variant and the summariser inherits it:
strategy--tags=['truncate']
strategy--tags=['sentences=2', 'truncate']
summariser
summariser--tags=['strategy.sentences=2', 'strategy.truncate']
That last key is worth a second look: it records which strategy the summariser was built against. Nobody wrote it down — resolution derived it, and it will be the same key on the next machine and in six months.
After the tutorial#
Worked pipeline — a full scikit-learn pipeline: loader, processors, model, benchmark. Needs
pip install "cinnamon-core[examples]"and downloads the IMDB dataset on first run.Concepts covers configuration, component, registration and dependencies in depth.
CONTRIBUTING.md — the design principle in more depth, and how to work on cinnamon itself.