Metadata-Version: 2.4
Name: ilulab
Version: 0.0.7
Summary: Lightweight Python client for IluLab
Author: ilulab contributors
License: MIT
License-File: LICENSE
Keywords: experiment-tracking,logging,machine-learning,mlops
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: build>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.14.7; extra == 'dev'
Requires-Dist: twine>=5.0.0; extra == 'dev'
Requires-Dist: ty>=0.0.1a29; extra == 'dev'
Description-Content-Type: text/markdown

# IluLab Python Client

Lightweight Python client for tracking ML experiments with IluLab.

## Installation

```bash
pip install ilulab
```

## Quick Start

### 1. Get a project ID from the web UI

The Python client is write-only — it can create runs, log metrics and
parameters, add tags, and finish runs, but it cannot create or list
projects. Create your project in the web UI first and copy its ID
from the URL (or the project settings page).

### 2. Set your API token

```bash
export ILULAB_TOKEN="your_api_token_here"
```

### 3. Log an experiment

```python
from ilulab import IlulabClient

# Initialize client
client = IlulabClient()

# Create a run in an existing project (copy the ID from the web UI)
run = client.create_run(
    project_id="YOUR_PROJECT_ID",
    name="experiment-1",
    tags=["baseline", "resnet"],
)

# Log parameters
run.log_params({
    "batch_size": 32,
    "optimizer": "adam",
    "model": "resnet50",
})

# Log metrics during training
for epoch in range(10):
    # ... training code ...
    loss = 0.5 - epoch * 0.03       # example
    accuracy = 0.6 + epoch * 0.04   # example

    run.log_metrics(
        {"train_loss": loss, "train_accuracy": accuracy},
        step=epoch,
    )

# Mark run as completed
run.finish()

# Clean up
client.close()
```

## Using Context Managers

```python
from ilulab import IlulabClient

with IlulabClient() as client:
    with client.create_run(
        project_id="YOUR_PROJECT_ID",
        name="experiment-1",
    ) as run:
        run.log_param("learning_rate", 0.001)

        for step in range(100):
            run.log_metric("loss", 1.0 / (step + 1), step=step)

        # Automatically marked as completed on exit
        # (or failed if an exception occurs)
```

## Supported Operations

| Operation | Method |
|---|---|
| Create a run | `client.create_run(project_id, name=..., description=..., tags=...)` |
| Log a single metric | `run.log_metric(key, value, step=...)` |
| Log multiple metrics at the same step | `run.log_metrics({key: value, ...}, step=...)` |
| Log a parameter (hyperparameter / config) | `run.log_param(key, value)` |
| Log multiple parameters | `run.log_params({key: value, ...})` |
| Add a tag to a run | `run.add_tag(tag)` |
| Mark a run as completed / failed | `run.finish(status="completed")` |

Read operations (listing projects/runs, fetching run details) are not
exposed through the Python client — use the web UI for those.

## Server-Side Validation Limits

The server enforces reasonable upper bounds on user-supplied values. The
client does not currently validate these before sending, so exceeding
them surfaces as an HTTP 400 from `_post`. Known limits:

- **Run name:** ≤ 255 characters
- **Run description:** ≤ 2000 characters
- **Tag name:** 1–100 characters, max 50 tags per run
- **Metric / parameter key:** 1–255 characters
- **Parameter string value:** ≤ 10,000 characters
- **Metric `step`:** integer in `[0, 2_147_483_647]`
- **Metric `value`:** must be finite (NaN / Inf are dropped client-side
  with a `RuntimeWarning` — see the [Non-Finite Values](#non-finite-values)
  section)
- **Batch size** (internal, per flush): ≤ 1000 metrics / parameters

### Non-Finite Values

`log_metric` / `log_metrics` / `log_param` / `log_params` **drop**
`NaN`, `+Inf`, and `-Inf` values (with a warning) rather than sending
them to the server, which requires finite numbers. For a single
auto-stepped metric, the step counter still advances on drop so later
metrics keep their expected offset — a transient `NaN` shows up as a
gap in the x-axis, not a silent left-shift of all subsequent points.
