Metadata-Version: 2.4
Name: confetti-config
Version: 1.0.1
Summary: A tiny, dependency-light config loader with dot-access, for YAML, JSON, and TOML.
Author-email: Artheme Gauthier-Villars <arthemeg@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: config,configuration,json,settings,toml,yaml
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: pyyaml>=5.1
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Provides-Extra: toml
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'toml'
Description-Content-Type: text/markdown

# Confetti 🎊

*Settings that fit Confetti.*

A tiny, dependency-light config loader for Python. Load YAML, JSON, or TOML and access values as attributes instead of dict lookups — with sane defaults, nested access, and clear errors when something's actually wrong.

```python
cfg = confetti.load("settings.yaml")
cfg.database.host      # not cfg["database"]["host"]
```

---

## Author

Artheme Gauthier-Villars (agauthier@ethz.ch)

## Why

Most config loaders make you choose between two annoying extremes: raw `dict` access (`config["db"]["host"]`, brackets everywhere, `KeyError` the moment something's missing) or a heavyweight framework with a learning curve for a problem that shouldn't need one.

`confetti` sits in between:

- **Dot access** on nested config, including lists of objects
- **Missing keys return `None`** instead of crashing, so you can use `or` / `.get()` defaults freely
- **One format, three loaders** — YAML, JSON, and TOML all produce the same `Config` object
- **`require()`** when you *do* want a hard failure for missing critical keys
- **Environment variable overlays** for the classic "base config + secrets from env" pattern
- **No magic** — under 150 lines, easy to read in five minutes, easy to vendor if you don't want a dependency

## Install

```bash
pip install confetti-config
```

(Import name is `confetti`; the PyPI name is `confetti-config` to avoid collisions.)

## Quick start

```yaml
# settings.yaml
app_name: MyService
debug: false

database:
  host: localhost
  port: 5432
  timeout: 1e-3

servers:
  - name: primary
    region: us-east
  - name: backup
    region: eu-west
```

```python
from confetti import load

cfg = load("settings.yaml")

cfg.app_name              # "MyService"
cfg.database.host         # "localhost"
cfg.database.timeout      # 0.001 (scientific notation auto-coerced to float)
cfg.servers[0].region     # "us-east"

cfg.nonexistent_key       # None — no crash
```

## Use cases

**Application settings**
Load a single source of truth for your app and pass it around as one object instead of threading dict keys through every function signature.

```python
cfg = load("config.yaml")
db = connect(host=cfg.database.host, port=cfg.database.port)
```

**Layered config (base + environment overrides)**
Keep shared defaults in one file and override per-environment values in another.

```python
base = load("base.yaml")
prod = load("prod.yaml")
cfg = base.merge(prod)   # prod values win
```

**Config from environment variables**
Useful in containerized deployments where secrets come from env, not files.

```python
from confetti import Config

env_cfg = Config.from_env("APP_")   # reads APP_HOST, APP_PORT, ...
cfg = base.merge(env_cfg)
```

**Validating required settings on startup**
Fail fast and loud instead of hitting an `AttributeError` three layers deep at runtime.

```python
cfg.require("database", "secret_key")
# raises ConfigError listing exactly what's missing
```

**Multi-format projects**
Same API regardless of which file format you're handed.

```python
load("settings.yaml")
load("settings.json")
load("settings.toml")
```

## API reference

| Method | Description |
|---|---|
| `load(path)` / `Config.load(path)` | Load a `.yaml`, `.json`, or `.toml` file into a `Config` |
| `Config.from_dict(d)` | Wrap an existing dict |
| `Config.from_env(prefix="")` | Build a `Config` from environment variables |
| `cfg.get(key, default=None)` | Attribute access with an explicit fallback |
| `cfg.require(*keys)` | Raise `ConfigError` if any key is missing |
| `cfg.merge(other)` | Return a new `Config` with `other`'s values overlaid |
| `cfg.to_dict()` | Convert back to a plain nested `dict` |
| `key in cfg` | Membership check |
| `for key in cfg` | Iterate over top-level keys |

## Error handling

All failures — missing files, bad syntax, unsupported formats, missing required keys — raise a single `ConfigError`, so you only need one `except` clause:

```python
from confetti import load, ConfigError

try:
    cfg = load("settings.yaml")
    cfg.require("database", "api_key")
except ConfigError as e:
    print(f"Config problem: {e}")
    raise SystemExit(1)
```

## What it's not

`confetti` doesn't do schema validation, type enforcement, hot-reloading, or secrets management. If you need any of those, look at `pydantic-settings` or `dynaconf` — they're great at it. `confetti` is for the much more common case: *I have a config file and I want to use it without typing brackets.*

## License

MIT
