Metadata-Version: 2.5
Name: molaboard
Version: 0.2.0
Summary: Python client SDK for the MolaBoard ML model lifecycle platform
Project-URL: Homepage, https://github.com/MolaLabs/molaboard-sdk
Project-URL: Repository, https://github.com/MolaLabs/molaboard-sdk
Project-URL: Issues, https://github.com/MolaLabs/molaboard-sdk/issues
Author: MolaLabs
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: experiment-tracking,mlops,model-registry,molaboard
Classifier: Development Status :: 4 - Beta
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: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28
Requires-Dist: loguru>=0.7
Requires-Dist: psutil>=5.9
Requires-Dist: pydantic>=2.7
Requires-Dist: scikit-learn>=1.7.2
Provides-Extra: amd
Requires-Dist: amdsmi>=6; extra == 'amd'
Provides-Extra: dev
Requires-Dist: import-linter>=2.0; extra == 'dev'
Requires-Dist: pandas>=2.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: torch>=2.0; extra == 'dev'
Provides-Extra: gpu
Requires-Dist: nvidia-ml-py>=12; extra == 'gpu'
Description-Content-Type: text/markdown

# molaboard

Python client SDK for the MolaBoard ML lifecycle platform.

```python
import molaboard

run = molaboard.init(project="My Project", config={"lr": 1e-3})
for step in range(steps):
    ...  # train
    run.log_metrics({"loss": loss, "accuracy": acc}, step=step)
run.log_model("weights/model.pt")   # registers + uploads in the background
run.finish()                        # waits for buffers/uploads, closes the run
```

`project` is a project id or a project name; a name is looked up across your
workspaces and must match exactly one existing project — `init` fails with a
clear error when it doesn't exist or is ambiguous, and never creates projects
implicitly.

## Installation

`molaboard` is distributed privately from this repository — there is no public
PyPI release. Install a tagged version straight from the repo (requires access
to `MolaLabs/molaboard-sdk` via an SSH key or a GitHub token):

```bash
uv pip install "molaboard @ git+ssh://git@github.com/MolaLabs/molaboard-sdk.git@v0.2.0"
```

Or pin it in a consuming project's `pyproject.toml`:

```toml
dependencies = [
    "molaboard @ git+ssh://git@github.com/MolaLabs/molaboard-sdk.git@v0.2.0",
]
```

On machines without SSH set up (CI, containers), use an HTTPS URL with a token
that has read access to the repo:

```bash
uv pip install "molaboard @ git+https://${GITHUB_TOKEN}@github.com/MolaLabs/molaboard-sdk.git@v0.2.0"
```

Prefer a pre-built wheel? Every tagged release attaches one — download and
install it directly:

```bash
gh release download v0.2.0 --repo MolaLabs/molaboard-sdk -p '*.whl'
pip install ./molaboard-0.2.0-py3-none-any.whl
```

Requires Python 3.10+.

## Authentication

`molaboard.init` resolves credentials in this order:

1. **API key** — `api_key=...` or the `MOLABOARD_API_KEY` environment
   variable. This is the mechanism for CI and other headless environments.
2. **Stored device-login tokens** — written by a previous login, refreshed
   automatically.
3. **Interactive browser login** — when nothing is configured and a terminal
   is attached, the SDK prints a verification URL (and opens it), you approve
   the device in the MolaBoard web UI, and the tokens are stored for next
   time.

A machine can also be enrolled ahead of time from the shell:

```bash
molaboard login            # browser device flow
molaboard whoami           # sanity check
molaboard logout           # revoke + delete stored credentials
```

## Configuration

| Environment variable   | Meaning                                             |
| ---------------------- | --------------------------------------------------- |
| `MOLABOARD_BASE_URL`   | API server, default `http://localhost:8000/v1`      |
| `MOLABOARD_API_KEY`    | API key (`mb_live_...`) for headless auth           |
| `MOLABOARD_PROJECT_ID` | Default project for `molaboard.init()`              |
| `MOLABOARD_HOME`       | Credentials + cache directory, default `~/.molaboard` |

Explicit arguments to `init()` always win over the environment.

### Pointing at a backend with `.env`

Instead of exporting `MOLABOARD_BASE_URL`, drop a `.env` file next to your
training script:

```dotenv
BACKEND_URL=100.54.23.21:8000
```

A bare `host:port` is expanded to `http://host:port/v1`; a full URL (with a
scheme and/or path) is used as-is. The base URL resolves in order of
precedence:

1. a `base_url=` argument to `init()` / `login()`,
2. the `MOLABOARD_BASE_URL` environment variable,
3. `BACKEND_URL` in the nearest `.env`,
4. the built-in default (`http://localhost:8000/v1`).

Leave `BACKEND_URL` empty to fall through to the default.

## Logging metrics

```python
run.log_metrics({"loss": 0.12, "accuracy": 0.94}, step=7)
run.log_metric("loss", 0.11, step=8)      # single-value convenience
run.log_metrics({"loss": 0.10})           # step omitted: auto-increments
```

Metric rows are buffered locally and shipped in batches by a background
flusher, so logging never blocks the training loop; `run.finish()` drains
whatever is still buffered. Invalid values (non-numeric, `NaN`/`inf`) are
skipped with a warning rather than raising.

## System metrics

Every run automatically records the machine's resource usage as a time
series (GPU, CPU, and RAM usage, plus disk and network throughput), and the
hardware inventory — CPU model, core count, total RAM, GPU list — as run
metadata. Nothing to call; tune it through `init`:

```python
run = molaboard.init(
    project="My Project",
    system_metrics=True,           # default; False disables entirely
    # system_metrics=["gpu", "memory"],   # or pick collectors:
    #   "cpu", "memory", "gpu", "disk", "network"
    system_sample_interval=2.0,    # seconds between samples
)
```

Collectors degrade gracefully on every platform: hardware or drivers that
aren't there just mean those series are absent, never an error. CPU/RAM/disk/
network work everywhere (via `psutil`). NVIDIA GPUs are read through NVML —
install `molaboard[gpu]`; AMD GPUs through `amdsmi`, which ships with ROCm
(or install `molaboard[amd]`). Series names are stable across vendors:
`gpu.0.utilization_percent`, `gpu.0.memory_percent`, `cpu.percent`,
`memory.percent`, `proc.memory.rss_bytes`, and friends.

## Console logs

The run also captures the process's stdout/stderr and ships each line to the
run's log stream (stdout as `info`, stderr as `error`). Output still reaches
the terminal untouched, and redraw-style progress bars (tqdm) collapse to
their final rendering instead of one row per redraw. Disable with
`molaboard.init(..., capture_console=False)`.

## Logging models

```python
handle = run.log_model(
    "weights/model.pt",
    name="resnet",          # the model's name; defaults to the file stem
    description="…",        # optional free-text description
)
```

`name` is the model's reference: a bare name registers a **top-level model**, a
`"group/name"` ref (e.g. `name="vision/resnet"`) files it under a group. Omit it
and it defaults to the file's stem (or the object's class name for a live push).

`log_model` also accepts a **live model object** from a supported framework —
currently PyTorch (`nn.Module`) and scikit-learn (any estimator), listed in
`molaboard.SUPPORTED_MODEL_FRAMEWORKS`:

```python
run.log_model(model)        # snapshot + computation graph, name = class name
```

A live object is snapshotted at the moment of the call (`torch.save` /
`pickle`) and uploaded together with its extracted **computation graph**,
which is stored as a file on the model and returned by
`GET /models/{id}` for the web UI to render as a Netron-style diagram
(PyTorch graphs come from `torch.fx` tracing, with a module-hierarchy
fallback; scikit-learn graphs follow pipelines, unions, and column
transformers). Objects from other frameworks are refused with a warning
naming the supported list — serialize them yourself and pass the file path,
which works for any framework (no graph is extracted from paths).

`log_model` is non-blocking: it schedules the registry calls and the file
transfer on the SDK's background event loop and returns a `ModelUpload`
handle immediately. Large files upload as concurrent multipart chunks,
streamed from disk with flat memory use. The handle is optional to look at:

```python
handle.wait(timeout=120)   # block until settled; returns the model or None
handle.done                # has the upload settled?
handle.model               # the RegisteredModel once successful
handle.error               # the exception if it failed
```

`run.finish()` (or using the run as a context manager) waits for in-flight
uploads, marks the run finished, and prints a one-line summary. Runs left
open at process exit are finished automatically.

## Failure behaviour

Failures split at the moment the run exists. Anything before that — bad
credentials, a project that doesn't exist, an unreachable server — makes
`init` raise, because a training job silently logging into the void is worse
than one that fails fast. Once the run is live, the SDK never takes down the
calling script: a failed upload or metric batch is logged as a warning, the
affected handle carries the error, and training continues.

Pass `molaboard.init(..., verbose=False)` to extend the never-raise guarantee
to `init` itself (failures degrade to a disabled no-op run).

## Console output

The SDK is **quiet by default**. Pass `show_logs=True` to see its progress
lines and warnings:

```python
run = molaboard.init(project="My Project", show_logs=True)
```

Interactive device-login prompts are always shown — hiding the verification
URL would make signing in impossible. Everything goes through
[loguru](https://github.com/Delgan/loguru), so host applications can override
either way with `logger.enable("molaboard")` / `logger.disable("molaboard")`.

## Development

```bash
uv sync --extra dev
uv run pytest
uv run ruff check src tests
```

The test suite runs against mocked HTTP (`respx`) — no server needed. See
`examples/` for runnable end-to-end scripts against a live server.
