API#
The three modules a project imports. Everything here is re-exported from
cinnamon itself, so from cinnamon import Configuration and from
cinnamon.configuration import Configuration name the same class.
cinnamon.configuration#
- class cinnamon.configuration.Configuration(**data)[source]#
Bases:
BaseModelA Configuration specifies the parameters of a Component. Configurations store parameters and allow flow control via conditions.
- add_condition(condition, name, description=None, tags=None)[source]#
Adds a condition to be validated.
- Parameters:
condition (
Callable[[Configuration],bool]) – a function that receives as input the currentConfigurationinstance and returns a boolean.name (
str) – unique identifier.description (
str|None) – a string description for readability purposes.tags (
Optional[AbstractSet[str]]) – a set of string tags to mark the condition with metadata.
- Warns:
``RuntimeWarning`` – if the provided name already exists in the Configuration instance. The new condition replaces the old one.
- classmethod default()[source]#
Returns the default
Configurationinstance.- Return type:
TypeVar(C, bound= Configuration)- Returns:
Configurationinstance.
- property dependencies: dict[str, Any]#
Map every dependency field to its value, container shape intact.
Values are whatever the field holds: a single
RegistrationKey, alistof them, adictof them, orNonefor an unset optional dependency. Usecinnamon.utility.dependencies.iter_dependency_keysto walk the keys without caring which.
- dependency_shape(field_name, field)[source]#
Classify field as a scalar, list or dict dependency, or
None.See
cinnamon.utility.dependenciesfor the supported shapes.- Return type:
- is_dependency(field_name, field)[source]#
Report whether field declares a dependency on another registration.
A dependency is a
RegistrationKey(orConfiguration) field, alistof them, or adictof them keyed by string – optionally wrapped inOptional[...]. Nested containers raiseTypeError.- Return type:
- model_config: ClassVar[ConfigDict] = {'validate_default': True}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_copy(*, update=None, deep=False)[source]#
Copy this configuration, validating any values supplied via update.
BaseModel.model_copydoes not run validators, so anupdatehas to be pushed back throughmodel_validate– that round-trip is what letsRegistry.expand_configurationreject invalid variants. With no update there are no unvalidated values, and the round-trip costs roughly 4x a plain deep copy on the hottest call inRegistry.build, so it is skipped.The two paths differ in one visible way:
model_validaterebuilds_instance_metafrom the class defaults, discarding per-instance metadata, whereas the no-update path keeps pydantic’s copy of it (isolated whendeep, shared otherwise). Preserving is the expected behaviour for a copy; the update path keeps the reset because the registry’s DAG construction depends on variant metadata coming from the class.- Return type:
Self
- model_post_init(_Configuration__context)[source]#
Runs automatically right after Pydantic instantiates an object.
- Return type:
- classmethod retrieve(registration_key=None, name=None, namespace=None, tags=None)[source]#
Syntactic sugar for retrieving a Configuration from a
RegistrationKeyin implicit format.- Parameters:
registration_key (
Union[RegistrationKey,str,None]) – theRegistrationKeyused to register theConfigurationclass.tags (
Optional[AbstractSet[str]]) – thetagsfield ofRegistrationKeynamespace (
str|None) – thenamespacefield ofRegistrationKey
- Return type:
TypeVar(C, bound= Configuration)- Returns:
A
Configurationinstance- Raises:
InvalidConfigurationTypeException – if there’s a mismatch between the
Configurationclass usedduring registration and the type of the built Configuration –
instance using the registered –
constructor` method (see ConfigurationInfo arguments) –
- validate_conditions(strict=True)[source]#
Validates all provided conditions related to the
Configurationinstance.- Args:
- strict: if True, a failed validation process will raise
InvalidConfigurationException
- Returns:
A
ValidationResultthat stores the boolean result of the validation process along with an error message if the result isFalse.- Raises:
ValidationFailureException: ifstrict = Trueand the validationprocess failed
- Return type:
cinnamon.registry#
- cinnamon.registry.Registration#
A registration key, or its canonical string form. Defined after the class so the alias holds the real type: a forward reference here cannot be resolved from other modules’ namespaces, which broke the generated API docs.
alias of
RegistrationKey|str
- class cinnamon.registry.RegistrationKey(name, namespace=None, tags=None, description=None, metadata=None, special_tags=None)[source]#
Bases:
Generic[T]Compound key used for registration.
- property compound_tags#
- classmethod from_string(string_format)[source]#
Parses a
RegistrationKeyinstance from its string format.- Parameters:
string_format (
str) – the string format of aRegistrationKeyinstance.- Return type:
- Returns:
The corresponding parsed
RegistrationKeyinstance
- from_tags_simplification(tags)[source]#
Builds a new
RegistrationKeyfrom current instance by removing provided tags.- Parameters:
tags (
Optional[AbstractSet[str]]) – a Tag set containing tags to remove- Return type:
- Returns:
A
RegistrationKeyinstance that is the same as the current instance but withtagsremoved.
- property hierarchy_tags#
- classmethod parse(registration_key=None, name=None, namespace=None, tags=None)[source]#
Parses a given
RegistrationKeyinstance. If the givenregistration_keyis in its string format, it is converted toRegistrationKeyinstance- Parameters:
registration_key (
Union[RegistrationKey,str,None]) – aRegistrationKeyinstance in its class instance or string formatnamespace (
str|None) – thenamespacefield ofRegistrationKeytags (
Optional[AbstractSet[str]]) – thetagsfield ofRegistrationKey
- Return type:
- Returns:
The parsed
RegistrationKeyinstance
- to_dict()[source]#
The key as plain JSON-compatible data.
{"name": ..., "namespace": ..., "tags": [...]}, with tags sorted so the result is stable across runs and comparable byte for byte.Only the three components that make up the key’s identity are included –
descriptionandmetadataannotate a key rather than identify it, and two keys differing only in those are equal.Prefer this to the string form for anything that has to survive a round trip: the string form has to be parsed back out of a single line, while this cannot be ambiguous no matter what a tag contains.
- class cinnamon.registry.Registry[source]#
Bases:
objectThe registration registry. The registry has three main functionalities: - Storing/Retrieving registered
Configuration: via theConfigurationInfointernal wrapper. - Storing/RetrievingConfigurationtoComponentbindings: the binding operation allows to build aComponentinstance from its registeredConfiguration. - Storing/Retrieving registered builtComponentinstances: aComponentinstance can be registered as well to mimic Singleton behaviors. This functionality is useful is a singleComponentinstance is used multiple times in a program.All the above functionalities require to specify a
RegistrationKey(either directly or indirectly).- REGISTRATION_CONTEXT: RegistrationContext = <cinnamon.registry.RegistrationContext object>#
- classmethod build(cls, directory, external_directories=None)[source]#
Main entrypoint of cinnamon. The registry checks provided directories for configurations to populate its internal registry and build the dependency DAG. Eventually, the dependency DAG is expanded to account for variants and invalid configurations.
- Parameters:
- Returns:
a
ResolutionInfo` containing valid ``RegistrationKeyinvalid_keys: aResolutionInfo` containing invalid ``RegistrationKey- Return type:
Tuple[Set[RegistrationKey[Any]],Set[RegistrationKey[Any]]]- Raises:
RuntimeWarning – if duplicate namespaces are found.
InvalidDirectoryException – if one of the provided directories does not exist or is not a directory.
AlreadyExpandedException – if the dependency DAG has been expanded.
NotADAGException – if the dependency DAG is not a DAG.
DisconnectedGraphException – if the dependency DAG contains disconnected nodes. This should never happen through the cinnamon APIs, only through manual edits to the graph.
- classmethod check_graph_topology()[source]#
The two properties the graph has to hold, before and after expansion.
Expansion adds a node and an edge per variant, so a graph that was a DAG on the way in is not necessarily one on the way out. Running these only before expansion is what let a variant’s self-loop through.
- Raises:
NotADAGException – if the dependency DAG is not a DAG.
DisconnectedGraphException – if the dependency DAG contains disconnected nodes.
- Return type:
- classmethod check_registration_graph()[source]#
Checks if the dependency DAG is valid.
- Raises:
AlreadyExpandedException – if the dependency DAG has been expanded.
NotADAGException – if the dependency DAG is not a DAG.
DisconnectedGraphException – if the dependency DAG contains disconnected nodes. This should never happen through the cinnamon APIs, only through manual edits to the graph.
- Return type:
- classmethod dag_resolution(cls)[source]#
Expands and resolves every dependency in the registration DAG.
Keys are expanded children first, in reverse topological order.
expand_configurationrecurses into a key’s dependencies, so reaching a parent before its children makes the recursion as deep as the longest chain in the project – and Python’s stack limit then caps that chain at roughly 490 links.That cap used to depend on the order modules happened to register in: the same graph resolved when children were registered first (each expansion finding its children already done, so nesting stayed shallow) and hit
RecursionErrorwhen parents came first. Taking the order from the graph rather than from registration removes both the depth limit and the dependence on something no user controls.- Returns:
the set of valid registration keys invalid_keys:the set of invalid registration keys
- Return type:
Tuple[Set[RegistrationKey[Any]],Set[RegistrationKey[Any]]]
- classmethod expand_configuration(key, valid_key_buffer=None, invalid_key_buffer=None)[source]#
Recursively expand configuration and dependencies.
- Return type:
- classmethod forget_loaded_modules()[source]#
Undo what
load_registrationsdid to the interpreter.Loading a project puts its root on
sys.pathand executing its registration scripts leaves their packages insys.modules. Neither was ever taken back out, so a second project’sconfigurationsresolved to the first one’s – the earliersys.pathentry wins, and an already-imported namespace package keeps the__path__it was found on. Two projects that both name a folderconfigurationsis not an unusual arrangement; it is the only arrangement.Only what a load imported is forgotten. Deciding by file location alone drops modules the caller imported before the build, and a module re-imported afterwards is a second class object: a component held from before the reset then fails
issubclassagainst its own class, with a message naming one class twice. A library whose tests scan its own package is exactly that arrangement.And only configuration modules. A registration script that imports its library puts that library’s modules in
sys.modulesduring the load. A caller that imports a class after the build holds that module’s class, so forgetting the module hands the next build a second copy. Only modules inside aconfigurationsfolder collide between projects, so those are the only ones forgotten.Registrations are re-executed on every
load, so nothing here is a cache being thrown away.- Return type:
- classmethod freeze_runtime()[source]#
The registry reduced to what another process needs in order to build.
A built registry is two things at once: the machinery that produced it – the dependency graph, the registration decorators, the record of which modules were imported – and the answer that machinery arrived at. Only the answer is needed to build a component, and only the answer is portable. This returns it: per key, the component’s import path, the values its configuration resolved to, and the method that runs it.
The component is kept as a path and not as a class, deliberately. A worker importing that path gets the class its own interpreter defines, so an
expected_typecheck compares classes that are genuinely the same – where a pickled class would arrive as a second object thatissubclassrejects. It is also what lets this work whatever the registration modules were loaded as: nothing here refers to them.Every value is pickled here, and one that cannot be raises
UnserializableRuntimeExceptionnaming the key and the field. A lambda in a configuration is the usual cause. Failing here is the point: the alternative is a worker failing to unpickle something, in a stack that says nothing about which registration it came from.Nothing is mutated: the registry this is read from is left as it was.
- Return type:
Dict[RegistrationKey,Dict[str,Any]]
- classmethod from_key(registration_key, **build_args)[source]#
Build component from key.
- Return type:
TypeVar(T)
- classmethod from_keys(dependency, **build_args)[source]#
Build every component in a dependency, keeping the shape it came in.
A component receives its dependencies as keys, so that it decides when each child is built. When the dependency is a container that usually means a comprehension per field:
self.losses = [Registry.from_key(key) for key in losses] self.metrics = {name: Registry.from_key(key) for name, key in metrics.items()}
which says nothing except “build these”.
from_keyssays it once:self.losses = Registry.from_keys(losses) # list -> list, in order self.metrics = Registry.from_keys(metrics) # dict -> dict, same labels
A single key builds a single component, so a field typed
RegistrationKey | list[RegistrationKey]needs no branch.NonereturnsNone, which is what an unset optional dependency should do. Anything that is not a key is passed through untouched.build_argsare forwarded to every component built.This builds eagerly. Keep the loop when a child should only be built under some condition – the laziness is the reason components are handed keys rather than instances.
- Return type:
- classmethod in_graph(registration_key=None, name=None, namespace=None, tags=None)[source]#
Return True if key in dependency DAG.
- Return type:
- classmethod install_runtime(runtime)[source]#
Make this process able to build from a frozen runtime.
For a worker, which has to resolve keys and has no reason to scan anything: nothing is imported, no directory is walked, no configuration is registered a second time, and the dependency graph is neither rebuilt nor needed, having been resolved by whoever froze this.
from_key()then behaves as it does after a build, nested dependencies included – those are keys held in the values, and they resolve through the same registry.- Return type:
- classmethod instantiate(registration_key=None, name=None, namespace=None, tags=None, expected_type=None, **build_args)[source]#
Builds a
Componentinstance from its boundedConfigurationvia the implicitRegistrationKey.- Parameters:
registration_key (
Union[RegistrationKey,str,None]) – theRegistrationKeyused to register theConfigurationclass.tags (
Optional[AbstractSet[str]]) – thetagsattribute ofRegistrationKeynamespace (
str|None) – thenamespaceattribute ofRegistrationKeyexpected_type (
type|None) – type of the component to be castbuild_args – additional custom component constructor args
- Return type:
- Returns:
The built component instance, carrying a
registration_keyattribute with the key that built it and abuild_argsattribute with the overrides it was built with. Components that cannot hold attributes are returned without them.- Raises:
InvalidConfigurationTypeException – if there’s a mismatch between the
Configurationclass used during registration and the type of the builtConfigurationinstance using the registeredconstructor` method (see ConfigurationInfo arguments) –
NotBoundException – if the
Configurationis not bound to any component.
- classmethod is_namespace_covered(registration_key)[source]#
Return True if namespace covered.
- Return type:
- classmethod is_skipped(path, root)[source]#
Whether
pathlies inside a directory the scan does not walk.Judged on the part of the path below
root, never on the whole of it: a library installed under~/.cache/uv/...or a project inside a dotted directory is a perfectly ordinary place to scan from, and testing the absolute path would skip everything in it.- Return type:
- classmethod load(cls, directory, external_directories=None)[source]#
Populate the registry and the dependency DAG, without resolving them.
This is the first half of
build: after it returns, every registration has been executed and every referenced key is a node in the DAG – including keys that were referenced but never registered. That makes it the point at which the whole set of broken references can be inspected at once, whichdag_resolutioncannot do because it stops at the first one.
- classmethod load_registrations(cls, directory)[source]#
Imports a Python’s module for registration. The Registry looks for
register()andregister_method()decorators. These functions are the entry points for registrations: that is, where theRegistryAPIs are invoked to issue registrations.- Parameters:
- Raises:
InvalidDirectoryException – if the provided directory is not a directory or does not exist.
- classmethod parse_configuration_files(cls, directories)[source]#
Runs a static code analyzer to inspect code scripts containing cinnamon registrations with the goal of determining unique namespaces.
- classmethod register_configuration(config, name, namespace, tags=None, component=None, run_method=None)[source]#
Registers a
Configurationin the registry. In particular, aConfigurationInfowrapper is stored in theRegistry.- Parameters:
config (
Configuration) – Configuration` instancename (
str) – thenamefield ofRegistrationKeynamespace (
str) – thenamespacefield ofRegistrationKeytags (
Optional[AbstractSet[str]]) – thetagsfield ofRegistrationKey,run_method (
str|None) –Componentmethod to run when instantiating theComponentas runnable
- Returns:
The built
RegistrationKeyinstance that can be used to retrieve the registeredConfigurationInfo.- Raises:
NotExpandedException – if the dependency DAG has not been expanded yet.
AlreadyRegisteredException – if the
RegistrationKeyis already usedNamespaceNotFoundException – if one of the dependencies of
RegistrationKeybelongs to a namespace not covered.
- classmethod register_configuration_from_key(config, registration_key, component=None, run_method=None)[source]#
Register a
Configurationunder a key you already hold.register_configurationtakesname,namespaceandtagsand assembles the key itself, which is right when that is what you have. Resolution is the other case:expand_configurationderives a variant key withkey.from_variant(...)and then had to take it apart into three fields for a callee that put them straight back together.The key is used as given, not copied. It therefore keeps whatever
descriptionandspecial_tagsit arrived with, where the rebuilt key lost them and started from empty.- Parameters:
config (
Configuration) – theConfigurationinstance to register.registration_key (
RegistrationKey[Any]) – the key to register it under.component (
str|None) – component module path, as a string.run_method (
str|None) – component method to run when it is instantiated as runnable.
- Return type:
- Returns:
The key it registered, which is the one passed in.
- Raises:
AlreadyExpandedException – if the dependency DAG has been expanded.
AlreadyRegisteredException – if the key is already used.
NamespaceNotFoundException – if one of the dependencies belongs to a namespace not covered.
- classmethod registered_items()[source]#
Return a read-only view over
(key, ConfigurationInfo)pairs.Public counterpart to
_REGISTRYfor consumers that need to walk the whole registry (the static analyzer, reporting tools) without depending on the internal container.- Return type:
ItemsView[RegistrationKey[Any],ConfigurationInfo]
- classmethod resolve_configuration(config)[source]#
Replace every dependency key with the
Configurationit names.Container shapes survive: a
list[RegistrationKey]field becomes a list of configurations in the same order, adict[str, RegistrationKey]keeps its labels. Members that are already resolved are left alone, so the call is idempotent.This runs on throwaway copies during validation. The registered configuration keeps its raw keys, which is what components receive – they call
Registry.from_keyon them to build their own children.- Return type:
- classmethod resolve_external_directories(cls, external_directories)[source]#
Checks if provided directories are valid directories and exist.
- classmethod retrieve_configuration(registration_key=None, name=None, namespace=None, tags=None)[source]#
Retrieves a
Configurationinstance from the registry via itsRegistrationKey.- Parameters:
registration_key (
Union[RegistrationKey,str,None]) – key used to register the configurationnamespace (
str|None) – thenamespacefield ofRegistrationKeytags (
Optional[AbstractSet[str]]) – thetagsfield ofRegistrationKey
- Returns:
the built configuration instance
- Return type:
- classmethod retrieve_configuration_info(registration_key=None, name=None, namespace=None, tags=None)[source]#
Retrieves a
Configurationinstance from the registry via itsRegistrationKey.- Parameters:
registration_key (
Union[RegistrationKey,str,None]) – key used to register the configurationnamespace (
str|None) – thenamespacefield ofRegistrationKeytags (
Optional[AbstractSet[str]]) – thetagsfield ofRegistrationKey
- Return type:
ConfigurationInfo- Returns:
The ConfigurationInfo stored under the parsed key.
- classmethod retrieve_keys(names=None, namespaces=None, tags=None, special_tags=None, keys=None)[source]#
Retrieves
RegistrationKeyvia given name, tags, namespaces filters. The search can be limited to a fixed set of keys, optionally given in input.- Parameters:
names (
Union[List[str],str,None]) – a name or a list of names to filter registration keys.namespaces (
Union[List[str],str,None]) – a namespace or a list of namespaces to filter registration keys.tags (
Optional[AbstractSet[str]]) – a tag set to filter registration keys.special_tags (
Optional[AbstractSet[str]]) – a special tag set to filter registration keys.keys (
Optional[List[RegistrationKey[TypeVar(T)]]]) – an optional list ofRegistrationKeyon which to apply the search.
- Return type:
- Returns:
Matching RegistrationKey instances.
- classmethod retrieve_runnable_keys()[source]#
Return keys marked runnable (run_method set).
- Return type:
- classmethod snapshot()[source]#
The registry’s whole state, shallow, plus the paths it put on
sys.path.Shallow on purpose. The point is to restore the objects a failed build replaced, not to defend against something mutating them: nothing mutates a registry while a build of another directory is running, and deep-copying a registration graph on every build would cost more than the build.
- classmethod unresolved_keys()[source]#
Keys that something depends on but nothing registered.
Meaningful between
loadanddag_resolution: registration adds a node for every referenced key, so anything in the graph without a registry entry is a broken reference. After a successfulbuildthe set is empty, since resolution would have failed.- Return type:
- classmethod update_namespaces(cls, namespaces, module_mapping)[source]#
Merge namespaces into registry mappings.
- Raises:
RuntimeWarning – if a namespace is already mapped to a directory. Two directories claiming one namespace makes resolution ambiguous, so the merge is refused rather than silently resolved.
- cinnamon.registry.json_default(value)[source]#
default=hook that teachesjsonabout registration keys.A class cannot make itself serializable to
json.dumps()– the encoder dispatches on a fixed set of types and consultsdefaultonly for what it does not recognise. So this is the hook rather than a method:json.dumps({"losses": [key_a, key_b]}, default=json_default)
Keys become the mapping from
RegistrationKey.to_dict(), whatever they are nested inside. Anything else is passed on to the normalTypeError, so unrelated unserializable objects still fail where they should.Inside a
Configurationnone of this is needed: pydantic already knows how to serialize a key, andconfig.model_dump_json()writes its string form.- Return type:
- cinnamon.registry.register_class(name, namespace, tags=None, component=None, run_method=None)[source]#
Register a
Configurationsubclass, as a decorator on the class itself.register_method()needs a method to hang on, so a configuration that changes nothing but its parameters still has to write one out:class TransformerFRConfig(GRUFRConfig): backbone: RegistrationKey = Param(TRANSFORMER) @classmethod @register_method( name="model", tags={"fr", "transformer"}, namespace=NAMESPACE, component="myproject.models.FR", ) def default(cls): return super().default()
That method says nothing the class does not already say. Decorating the class says the same thing and stops there:
@register_class( name="model", tags={"fr", "transformer"}, namespace=NAMESPACE, component="myproject.models.FR", ) class TransformerFRConfig(GRUFRConfig): backbone: RegistrationKey = Param(TRANSFORMER)
A configuration whose
defaultdoes real work – adding a condition, say – writes it as an ordinary classmethod; that is the one the registration builds from, since it is the one the class has.- Parameters:
name (
str) – name of the registration key.namespace (
str) – namespace of the registration key. Pass it as a keyword, named by a literal or a module-level constant:Registry.buildreads namespaces out of the source before importing anything, so a namespace it cannot see statically leaves the directory looking as though it registers nothing.tags (
Optional[AbstractSet[str]]) – tags of the registration key.component (
str|None) – import path of the component the configuration describes.run_method (
str|None) – name of the component method a runner invokes.
- Return type:
- Returns:
The class, unchanged.
- cinnamon.registry.register_method(name, namespace, tags=None, component=None, run_method=None)[source]#
- Return type:
- cinnamon.registry.setup(directory=None, external_directories=None, logging_level=20)[source]#
Build the registry around an entry point, and run it.
Every script that uses cinnamon opens the same four lines:
if __name__ == '__main__': directory = Path(__file__).parent.parent.resolve() Registry.build(directory=directory) logging.basicConfig(level=logging.INFO) logger = getLogger(__name__)
which this replaces with:
@setup(directory=Path(__file__).parent.parent) def main(): benchmark = Registry.instantiate(name='benchmark', namespace='examples') benchmark.run()
The decorated function runs immediately when its module is
__main__, which is what removes theif __name__guard rather than merely moving the build call into a decorator. Imported from anywhere else it does not run, so a module keeps its ordinary behaviour underimportand under test; the decorated function stays callable, and calling it rebuilds the registry.Because it runs at decoration time, put it where you would have put the
if __name__block: after everything it refers to. That is the same discipline, not a new one.- Parameters:
directory (
Union[Path,str,None]) – the project directory to build from. Defaults to the working directory, matching the-dirflag of thecmn-*commands.external_directories (
Optional[List[Union[str,Path]]]) – external directories containing configurations.logging_level (
int|None) – level to pass tologging.basicConfig. PassNoneto leave logging alone, for a script that configures its own.
- Return type:
- Returns:
The decorator, which returns the function unchanged apart from the build that precedes each call.
cinnamon.cli#
- cinnamon.cli.check()[source]#
Report registration problems without running anything.
Two passes, in the order the problems occur:
1. Keys – run after
Registry.loadso that every broken reference is visible.dag_resolutionstops at the first one, which is why a project with three typos otherwise takes three runs to fix. 2. Bindings – only once the keys resolve, since the component analyzer needs an expanded registry.The binding pass resolves component paths on the filesystem without importing them, so the command stays fast whatever the components weigh.
--deepimports each one to check its__init__against the configuration’s fields, at the cost of that import.Exits non-zero when errors are found, so it can gate a commit or a CI job.
- Return type: