<div align="center">
  <h1>🌲 Canopee</h1>
  <p><strong>A type-safe, fluent configuration library for Python 3.11+.</strong></p>
  <p>
    <a href="https://pypi.org/project/canopee/"><img src="https://img.shields.io/pypi/v/canopee.svg" alt="PyPI version"></a>
    <a href="https://pypi.org/project/canopee/"><img src="https://img.shields.io/pypi/pyversions/canopee.svg" alt="Python versions"></a>
    <a href="https://github.com/canopee-py/canopee/actions"><img src="https://img.shields.io/github/actions/workflow/status/canopee-py/canopee/ci.yml?branch=main" alt="CI Status"></a>
  </p>
</div>

---

**Canopee** is a production-ready configuration library that treats Python as the ultimate config DSL. 

Built on top of Pydantic v2, it removes the brittleness of YAMLs, silent mutation footguns, and metaclass magic, giving you IDE autocomplete, instant validation, and elegant hyperparameter sweeping out of the box.

## 🌟 Why Canopee?

* **Always Valid**: Configs are validated precisely at construction. No runtime surprises.
* **Immutable**: `ConfigBase` objects are `frozen=True`. They can be hashed, cached, and safely passed across threads.
* **Type-Safe Evolution**: Evolve new variants purely with Python keywords via the `.evolve(**kwargs)` method.
* **First-Class Sweeps**: Define hyperparameter search spaces directly over your types, using strategies like *grid*, *random*, or *optuna*.
* **Symmetric I/O**: Naturally save and load instances via `.toml`, `.yaml`, or `.json` extensions natively, or inject CLI/Env overrides securely.

## 🚀 Quickstart

### Installation
```bash
pip install canopee
```

### Basic Usage

Subclass `ConfigBase` as you would any Pydantic model. 

```python
from canopee import ConfigBase
from pydantic import computed_field

class TrainingConfig(ConfigBase):
    learning_rate: float = 1e-3
    epochs: int = 20
    batch_size: int = 128

    @computed_field
    @property
    def total_steps(self) -> int:
        return self.epochs * (10_000 // self.batch_size)

# 1. Instantiate (validated instantly)
cfg = TrainingConfig()

# 2. Evolve (returns a new modified frozen instance, IDE autocomplete works perfectly!)
fast_cfg = cfg.evolve(epochs=5, learning_rate=5e-3)

# Computed fields naturally update based on the new instance values
print(fast_cfg.total_steps)

# 3. Save it to disk (JSON, TOML, YAML supported out-of-the-box)
fast_cfg.save("experiment.toml")
```

### Sweeps

Native support for generating massive, reproducible configuration variants.

```python
from canopee.sweep import Sweep, log_uniform, choice

def train(cfg: TrainingConfig) -> float:
    # return accuracy metric here
    return 0.95

best_cfg = (
    Sweep(TrainingConfig())
    .vary("learning_rate", log_uniform(1e-5, 1e-1))
    .vary("batch_size", choice(32, 64, 128))
    .strategy("random", n_samples=20, seed=42)
    .run(train)
    .best(minimize=False)
)
```

## 📖 Documentation

The full documentation—including Guides on advanced `ConfigStore` registries and `cli/env` Source merging—can be generated by running:

```bash
uv run zensical serve
```
