Metadata-Version: 2.4
Name: pandm
Version: 0.2.0
Summary: Beautiful, local-first experiment tracking. A lightweight alternative to wandb / tensorboard.
Author-email: Jannchie <jannchie@gmail.com>
License: MIT
Requires-Python: >=3.10
Requires-Dist: fastapi>=0.110
Requires-Dist: httpx>=0.27
Requires-Dist: pillow>=10
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: rich>=13
Requires-Dist: typer>=0.12
Requires-Dist: uvicorn[standard]>=0.29
Description-Content-Type: text/markdown

# pandm

pandm tracks ML experiments locally. The Python SDK writes metrics and images straight to a `.pandm/` directory next to your code — no account, no daemon, no cloud — and `pandm ui` serves a dashboard to compare runs. Unlike wandb there is nothing to sign up for, and unlike tensorboard the data is plain SQLite + PNG files you can query yourself. The same scripts report to a shared server over HTTP when you set one env var.

![dashboard](docs/screenshot.png)

## Install

```sh
pip install pandm
```

## Quick start

```python
import pandm

run = pandm.init(project="mnist", config={"lr": 1e-3, "batch_size": 64})
for step in range(1000):
    loss, acc = train_step()
    run.log({"train/loss": loss, "train/acc": acc}, step=step)
    if step % 100 == 0:
        run.log_image("samples", sample_grid, step=step)  # PIL / numpy / torch / path
run.finish()
```

```sh
pandm ui   # opens http://127.0.0.1:7878
```

The dashboard overlays selected runs per metric, with smoothing, log scale, step/time axes, an image browser with a step slider, and a config/summary comparison table. It polls while runs are alive, so curves grow during training.

## Usage

`step` is optional (an internal counter is used). Runs end as `finished` or `crashed`: uncaught exceptions are detected via `sys.excepthook` (and the context manager), and hard-killed processes (`kill -9`, OOM) are presumed crashed once their 15s heartbeat goes quiet for 60s — self-healing if the process was merely suspended.

```python
with pandm.init(project="mnist") as run:
    run.log({"loss": 0.5})
```

List or delete runs from the terminal:

```sh
pandm ls
pandm delete <run_id>
```

Data lives in `./.pandm` by default; override with `--dir` or `PANDM_DIR`.

### Cloud mode

Training scripts never change — sign in once per machine and `pandm.init()` dual-writes: local stays the source of truth, a background thread syncs to the server, and anything logged offline is backfilled on reconnect. Delivery is exact-once (re-pushes are deduped server-side).

```sh
pandm login https://pandm.example.com   # browser approval, like `gh auth login`
python train.py                          # local + cloud
pandm sync                               # backfill runs whose process already exited
```

Each user signs in with GitHub and sees only their own runs. Two interchangeable server implementations speak the same protocol — the full walkthrough (OAuth App, custom domain, backups, troubleshooting) is in **[docs/deploy.md](docs/deploy.md)**:

**Cloudflare Workers** (serverless: D1 for metrics, R2 for media — `workers/`):

```sh
cd workers && pnpm install
npx wrangler d1 create pandm             # paste the database_id into wrangler.jsonc
npx wrangler secret put GITHUB_CLIENT_ID     # OAuth App callback: https://<domain>/api/auth/callback
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put PANDM_SECRET_KEY     # e.g. `openssl rand -hex 32`
npx wrangler d1 migrations apply pandm --remote
pnpm run deploy
```

> Note: D1 bills per row written (100k/day free). Logging ~10 metrics/sec around the clock lands in the paid tier — a few dollars a month.

**Self-hosted Python server** (same binary as `pandm ui`):

```sh
GITHUB_CLIENT_ID=… GITHUB_CLIENT_SECRET=… docker compose up -d   # multi-user mode
```

Without OAuth env vars the server falls back to single-tenant mode — `pandm server --api-key my-secret` plus `PANDM_REMOTE`/`PANDM_API_KEY` on the client (remote-only, no local copy, no accounts).

## API

| | |
|---|---|
| `pandm.init(project, name=None, config=None, *, directory=None, remote=None, api_key=None)` | start a run |
| `run.log(metrics, step=None)` | log scalar metrics |
| `run.log_image(key, image, step=None, caption=None)` | log an image |
| `run.finish(status="finished")` | end the run (also via `atexit`) |
| `GET /api/docs` | REST API reference on any running server |

## Development

```sh
uv sync && uv run pytest          # python sdk + server
cd web && pnpm install && pnpm dev   # dashboard dev server (proxies to :7878)
pnpm build                        # bundles the dashboard into src/pandm/static
cd workers && pnpm install && pnpm test   # cloudflare workers server (contract tests)
```

## License

MIT
