Metadata-Version: 2.4
Name: cinnamon-core
Version: 2.1.3
Summary: A simple framework for general-purpose configuration and code logic de-coupling.
Author-email: Federico Ruggeri <federico.ruggeri6@unibo.it>
License: MIT
Project-URL: Homepage, https://nlp-unibo.github.io/cinnamon/
Project-URL: Documentation, https://nlp-unibo.github.io/cinnamon/
Project-URL: Source, https://github.com/nlp-unibo/cinnamon
Project-URL: Issues, https://github.com/nlp-unibo/cinnamon/issues
Project-URL: Changelog, https://github.com/nlp-unibo/cinnamon/releases
Keywords: configuration,dependency-injection,registry,experiment-management,hyperparameter-search,reproducibility,research
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: networkx<4,>=3.4.2
Requires-Dist: pydantic<3,>=2.0
Provides-Extra: dev
Requires-Dist: nox>=2024.3.2; extra == "dev"
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: ruff>=0.15.20; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Provides-Extra: cli
Requires-Dist: InquirerPy>=0.3.4; extra == "cli"
Provides-Extra: examples
Requires-Dist: pandas>=2.0; extra == "examples"
Requires-Dist: scikit-learn>=1.3; extra == "examples"
Requires-Dist: tqdm>=4.9.0; extra == "examples"
Dynamic: license-file

<div align="center">

# cinnamon

**A lightweight Python framework for decoupling configuration from code logic.**

[![PyPI version](https://img.shields.io/pypi/v/cinnamon-core)](https://pypi.org/project/cinnamon-core/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://github.com/nlp-unibo/cinnamon/actions/workflows/ci.yml/badge.svg)](https://github.com/nlp-unibo/cinnamon/actions)

[Documentation](https://nlp-unibo.github.io/cinnamon/) · [Quickstart](https://nlp-unibo.github.io/cinnamon/quickstart.html) · [Tutorial](https://nlp-unibo.github.io/cinnamon/tutorial/index.html) · [Examples](https://nlp-unibo.github.io/cinnamon/examples/index.html)

</div>

---

## What is cinnamon?

Cinnamon separates **what your code does** from **how it is configured**.

Instead of scattering parameters across constructors, config files, or command-line
arguments, you define each component's parameters as a typed `Configuration` class
backed by [Pydantic](https://docs.pydantic.dev/). You then register that configuration
in the `Registry` and bind it to your component. From that point on, the `Registry`
handles construction, validation, type-checking, and dependency resolution automatically.

The result is a project where every component is independently swappable, every
parameter is validated and documented, and the full experiment can be reproduced by a
single `RegistrationKey`.

---

## Features

- **Pydantic-backed configurations** — field types, constraints (`ge`, `le`, `Literal`), and cross-field validators via `@model_validator`.
- **Registry-based dependency injection** — register a `Configuration`, bind it to a component by import path, and let cinnamon build the dependency graph automatically. Your component stays a plain class: no base class, no decorator, no import of cinnamon.
- **Variants** — declare alternative parameter values alongside their defaults and enumerate every valid combination.
- **Conditions** — attach runtime invariants to configurations via `add_condition`, validated before any component is built.
- **Dependencies** — compose configurations by pointing fields at `RegistrationKey` instances, singly or as a `list`/`dict` of them; the `Registry` resolves the graph children-first. A **scalar** dependency's variants propagate to its parents; the members of a `list` or `dict` dependency deliberately do not, because the parent would otherwise gain the cross product of every member's variants.
- **Community-ready** — pull components and `Configuration` classes from external projects via `external_directories` and build on top of them.
- **CLI included** — `cmn-check` reports unresolved keys with suggestions, without importing your components; add `--deep` and it imports each bound component to check its `__init__` against the configuration's fields. `cmn-build` resolves and writes the key list; `cmn-run` and `cmn-generate` run experiments and generate scripts without boilerplate.

---

## Installation

```bash
pip install cinnamon-core
```

The distribution is `cinnamon-core`; the import package is `cinnamon`:

```python
import cinnamon
```

They differ because `cinnamon` on PyPI is an unrelated project. `cinnamon-core`
is the package these releases have always used, and it supersedes the old
`cinnamon-generic`, `cinnamon-th` and `cinnamon-tf` split, which are no longer
maintained.

> **Upgrading from 0.2.x?** 2.0.0 is a rewrite. Configurations are Pydantic
> models with typed class annotations, and the `Component` base class is gone —
> components are plain classes now, bound by import path. Start from the
> [Quickstart](https://nlp-unibo.github.io/cinnamon/quickstart.html); the 0.2.x
> API does not carry over.

That covers the library and the two non-interactive commands, `cmn-build` and
`cmn-check`.

Optional extras:

| Extra | What it adds                                              | Install |
|---|-----------------------------------------------------------|---|
| `cli` | Interactive prompts for `cmn-run` and `cmn-generate` | `pip install "cinnamon-core[cli]"` |
| `examples` | Dependencies for the built-in examples                    | `pip install "cinnamon-core[examples]"` |
| `dev` | pytest, ruff, mypy                                        | `pip install "cinnamon-core[dev]"` |

---

## Quickstart

**1. Define a component** — a plain Python class, no base class required:

```python
class DataLoader:

    def __init__(self, folder_name: str, batch_size: int):
        self.folder_name = folder_name
        self.batch_size  = batch_size

    def load(self):
        ...
```

**2. Define its configuration** — a Pydantic model with typed, documented fields:

```python
from cinnamon.configuration import Configuration, Param
from cinnamon.registry import register_class

@register_class(name='loader', tags={'default'}, namespace='myproject',
                component='components.DataLoader')
class DataLoaderConfig(Configuration):
    folder_name: str = Param('data/', description='Root data directory')
    batch_size: int  = Param(32, ge=1,  description='Samples per batch',
                             variants=[16, 64])
```

**3. Build the registry** — cinnamon scans your `configurations/` folder and resolves dependencies:

```python
from pathlib import Path
from cinnamon.registry import Registry

Registry.build(directory=Path('.'))
```

**4. Instantiate** — retrieve and build a component from its registration key:

```python
loader = Registry.instantiate(name='loader', tags={'default'}, namespace='myproject')
loader.load()
```

The `Registry` builds the configuration, resolves its dependencies, validates its
conditions, and passes the resulting values to `DataLoader.__init__`.

**5. Enumerate variants** — every combination other than the all-defaults one,
which the `Registry` already registers on its own:

```python
config = DataLoaderConfig.default()
for combo in config.variants:
    variant = config.model_copy(update=combo['values'])
    loader = DataLoader(**variant.values)
```

That's it. See the [full quickstart](https://nlp-unibo.github.io/cinnamon/quickstart.html)
for the complete walkthrough.

---

## Performance

Resolution is **linear** in the size of your project, in all three directions
that can grow:

```
resolution ≈ 0.034 ms × registrations
            + 0.045 ms × dependency edges
            + 0.113 ms × variant configurations
```

A 500-registration project with 1,000 dependency edges resolves in about 60 ms.
`import sklearn` on the same machine costs 564 ms — so for any project where
resolution is measurable, importing one component costs more than resolving
everything. That is what binding components by import path buys, and why
`cmn-check` can validate a project without loading a component at all.

Variant configurations are the expensive term, about three times a plain
registration, because each is copied, resolved and validated. If resolution
feels slow, the number to look at is how many configurations your sweep expands
to, not how many you wrote.

Those constants belong to one machine. Reproduce them on yours:

```bash
python benchmarks/dag_scaling.py
```

Dependency chains are not limited by Python's recursion limit — a 1,500-deep
chain resolves under the default of 1,000. See
[Performance](https://nlp-unibo.github.io/cinnamon/performance.html) for the
graph shapes measured, why it stays linear, and the things that turned out
**not** to be bottlenecks.

---

## Key concepts

| Concept | Description | Docs |
|---|---|---|
| `Configuration` | A Pydantic `BaseModel` holding typed, validated parameters | [→](https://nlp-unibo.github.io/cinnamon/configuration.html) |
| `Param` | A `Field` wrapper that adds `tags`, `variants`, and cinnamon metadata | [→](https://nlp-unibo.github.io/cinnamon/configuration.html) |
| Component | Any plain Python class, referenced by its import path (e.g. `components.DataLoader`) | [→](https://nlp-unibo.github.io/cinnamon/component.html) |
| `RegistrationKey` | A `(name, tags, namespace)` identifier that binds a config to a component | [→](https://nlp-unibo.github.io/cinnamon/registration.html) |
| `Registry` | Stores registrations, resolves the dependency DAG, and builds components | [→](https://nlp-unibo.github.io/cinnamon/registration.html) |
| Dependencies | Other registrations referenced by `RegistrationKey` fields, singly or as a `list`/`dict` | [→](https://nlp-unibo.github.io/cinnamon/dependencies.html) |

---

## Learning cinnamon

**[`examples/tutorial/`](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/)** — seven runnable steps, no dependencies
beyond cinnamon itself. Each one is a single file you can read in a screen and change,
and the test suite runs every one of them on each commit.

```bash
pip install cinnamon-core
python examples/tutorial/01_configuration.py
```

The same steps, with commentary and the code included from these files, are at
[nlp-unibo.github.io/cinnamon/tutorial](https://nlp-unibo.github.io/cinnamon/tutorial/index.html).

| | Introduces |
|---|---|
| [1. Configuration](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/01_configuration.py) | `Configuration`, `Param`, validation |
| [2. Registration](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/02_registration.py) | components as plain classes, `RegistrationKey` |
| [3. Variants](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/03_variants.py) | one component, many configurations |
| [4. Dependencies](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/04_dependencies.py) | referencing another registration |
| [5. Collections](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/05_collections.py) | `list` and `dict` of keys |
| [6. Conditions](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/06_conditions.py) | rejecting combinations that make no sense |
| [7. Project layout](https://github.com/nlp-unibo/cinnamon/tree/main/examples/tutorial/07_project_layout/) | the real directory structure and the CLI |

Step 7 is the one to copy when starting a project of your own.

## A full example

The rest of `examples/` is a complete ML pipeline: data loading, preprocessing, SVM
classification, and evaluation on the IMDB sentiment dataset. It downloads the dataset
on first run.

```bash
pip install -e ".[examples]"
python -m examples.demos.demo_benchmark
```

See the [examples documentation](https://nlp-unibo.github.io/cinnamon/examples/index.html)
for a full walkthrough.

---

## Documentation

Full documentation is available at **[nlp-unibo.github.io/cinnamon](https://nlp-unibo.github.io/cinnamon/)**.

---

## Contributing

Contributions are welcome. [`CONTRIBUTING.md`](https://github.com/nlp-unibo/cinnamon/blob/main/CONTRIBUTING.md) covers the working
agreement: one branch per change, `nox` green before pushing, and a pull request into
`main`.

`nox` reproduces the whole CI pipeline locally in about a minute:

```bash
pip install nox
nox                  # lint, type-check, and the suite behind a 100% coverage gate
nox -s core          # the suite without the CLI extra installed
nox -s examples      # the tutorial and the scikit-learn pipeline
nox -s docs          # the documentation, warnings treated as errors
```

For questions, issues, or feature requests, open a
[GitHub issue](https://github.com/nlp-unibo/cinnamon/issues) or contact:

**Federico Ruggeri** — [federico.ruggeri6@unibo.it](mailto:federico.ruggeri6@unibo.it)

---

## License

[MIT](https://github.com/nlp-unibo/cinnamon/blob/main/LICENSE)
