Metadata-Version: 2.4
Name: laco
Version: 1.0.0
Summary: Lazy configurations for reproducible deep learning experiments.
Author-email: Kurt Stolle <kurt@khws.io>
Keywords: perception,computer vision,deep learning,object detection,instance segmentation,semantic segmentation
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: omegaconf
Requires-Dist: setuptools
Requires-Dist: regex
Requires-Dist: hydra-core
Requires-Dist: expath>=0.0.5
Requires-Dist: docstring-parser
Provides-Extra: wandb
Requires-Dist: wandb>=0.19.6; extra == "wandb"
Dynamic: license-file

# Laco — Lazy Configuration for Reproducible ML

**LAzy COnfiguration** (Laco) is a Python-first configuration system that
gives you type-checked, IDE-navigable configs with zero magic strings.
It is compatible with [Hydra](https://hydra.cc/) and a drop-in
alternative to [hydra-zen](https://mit-ll-responsible-ai.github.io/hydra-zen/).

## Key ideas

| Construct | Static type | What you get |
|-----------|-------------|--------------|
| `L.call(T)(**kw)` | `T` | lazy `_target_:` node |
| `L.partial(T)(**kw)` | `functools.partial[T]` | lazy `_partial_: true` node |
| `L.just(obj)` | `type(obj)` | identity-instantiated node |
| `L.required[T]()` | `T` | `MISSING` sentinel |
| `class G(L.Group[T])` | typed group | registers in Hydra ConfigStore |
| `@L.config class S` | dataclass schema | structured-config target |
| `@L.task` | task wrapper | auto-instantiates config fields |
| `@laco.main(config_name=…)` | Hydra app | composes config + runs task |
| `L.trace(lambda: …)` | `R` | records a config tree from Python |

## 30-second example

```python
import laco
import laco.language as L
from torch import nn, optim

# --- Groups: swappable variants ---
class OptimGroup(L.Group[optim.Optimizer]):
    sgd  = L.partial(optim.SGD)(lr=L.required[float](), momentum=0.9)
    adam = L.partial(optim.Adam)(lr=1e-3)

# --- Schema: typed config ---
@L.config
class TrainSchema:
    epochs: int = 10
    optimizer: optim.Optimizer = L.slot(OptimGroup)

# --- Model ---
model_cfg = L.call(nn.Linear)(in_features=784, out_features=10)

# --- Task: runs with a composed config ---
@laco.main(config_name="train")
@L.task
def run(model: nn.Module, optimizer: optim.Optimizer, epochs: int = 10):
    opt = optimizer(model.parameters())
    # ... training loop ...

if __name__ == "__main__":
    run()
```

Override from the CLI:
```
python train.py optimizer=adam epochs=20
python train.py -m optimizer=sgd,adam optimizer.lr=1e-2,1e-3   # multirun
```

## Tracing

```python
@L.configurable
class Encoder:
    def __init__(self, depth: int) -> None: ...

# Records a config tree — no constructors run:
cfg = L.trace(lambda: Encoder(depth=24))
# Reproduce the object:
enc = laco.instantiate(cfg)
```

## Examples

The `sources/laco/examples/` directory contains a curriculum of examples:

| Tier | Files | What it demonstrates |
|------|-------|----------------------|
| 0–1 | `mlp.py`, `linear_regression.py`, `cnn_classifier.py`, `text_classifier.py` | Basics: `L.call`, `L.params`, `L.required` |
| 2 | `blocks/residual.py`, `blocks/transformer.py`, `blocks/decoder.py` | Building blocks |
| 3–4 | `models/` | Vision & language model configs |
| 5 | `integrations/` | Lightning, Transformers, TensorDict |
| 6 | `pipelines/mnist_train.py`, `pipelines/clm_finetune.py` | End-to-end training pipelines with `@L.task` |

Typed-group variants live in `examples/typed/` (same models, `@L.config` + `L.Group` API).

## CLI

```
laco compose configs/train.py            # load & dump YAML
laco compose configs/train.py lr=1e-3   # with overrides
laco show   configs/train.py --groups   # list registered groups
laco run    configs/train.py            # run the @L.task entry point
laco diff   configs/a.py configs/b.py   # diff two config files
laco app    my-project train --lr 1e-4  # run a laco.apps entry point
```

### `laco app` — simple argparse CLI for published configs

Researchers and collaborators can reproduce experiments without knowing Hydra:

```bash
laco app my-project train --help          # show all available flags
laco app my-project train --lr 1e-4       # override a hyperparameter
laco app my-project train --dry-run       # preview resolved config as YAML
laco app my-project train --save-dir out/ # save config.yaml before running
```

Flags are derived from `@L.params` blocks and `L.param()` declarations in
the config file.  Authors register the entry point in `pyproject.toml`:

```toml
[project.entry-points."laco.apps"]
train = "my_project.train:run"
```

See `docs/how-to/laco-app.md` for the full author and user guide.

## Migration from 0.x

Add `import laco.compat` once at startup to enable deprecation warnings
for legacy `_target_: laco.ops.partial` configs. Run `laco fix <dir>` to
rewrite them in-place.

## Linting

Two lint passes run in CI and are available locally:

- `nix run .#lint` — ruff over `sources/` and `tests/`.
- `nix run .#lie-lint` (or `laco-lint sources/`) — LACO001, a heuristic
  AST check for attribute access on lie-typed Laco nodes. Suppress with
  `# noqa: LACO001`.
