Metadata-Version: 2.4
Name: modelzone-sdk
Version: 4.1.0
Summary: Modelzone SDK - experiment tracking and AzureML integration
License-Expression: Apache-2.0
Author: Team Enigma
Author-email: enigma@energinet.dk
Requires-Python: >=3.13
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.13
Provides-Extra: azureml
Requires-Dist: azure-ai-ml (>=1.32.0) ; extra == "azureml"
Requires-Dist: azure-identity (>=1.25.3) ; extra == "azureml"
Requires-Dist: azure-storage-blob (>=12.28.0) ; extra == "azureml"
Requires-Dist: mlflow (>=3) ; extra == "azureml"
Requires-Dist: requests (>=2.32.0) ; extra == "azureml"
Description-Content-Type: text/markdown

# ModelZone SDK

Track training runs locally, save model artifacts, and share them through Azure ML.
A **run** is one training attempt. **Registering** a run gives its code and
artifacts a model name and version for use in inference.

## Contents

- [Installation](#installation)
- [Workflow](#workflow)
- [Model artifacts](#model-artifacts)
- [Logging](#logging)
- [CLI options](#cli-options)
- [Examples](#examples)

## Installation

Requires Python 3.13 or newer. For local tracking and artifact loading:

```bash
pip install modelzone-sdk
```

For Azure ML uploads, registration, and downloads:

```bash
pip install modelzone-sdk[azureml]
```

The authentication with Azure ML uses
[DefaultAzureCredential](https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.defaultazurecredential).
Ensure an identity with access to your workspace is logged in (default approach would be via AzureCLI).

## Workflow

### 1. Train locally

Wrap your training function with `mz.default_cli`. In `mypackage/experiment.py`:

```python
import modelzone as mz

def train():
    mz.tracker.log_metric("MAE", 1.23)

def main():
    mz.default_cli(train)
```

Replace the example metric with your training code. Add a `train` command to the
training project's `pyproject.toml`:

```toml
[project.scripts]
train = "mypackage.experiment:main"
```

Install the project, then run it:

```bash
python -m pip install -e .
train
```

Each run saves a code snapshot and logged outputs under `.runs/<timestamp>`.
The folder ends in `__FINISHED` or `__FAILED` when the run ends. It lives beside
the nearest `pyproject.toml` above the training script; local runs do not need
Azure settings.

Snapshots follow Git's ignore rules. Keep `.runs/` and your virtual environment
in `.gitignore`.

For custom training arguments, use `mz.default_parser()` and `mz.run(__file__, ...)`
as shown in the [training example](examples/training_example/example_forecaster/experiment.py).
The `train` command belongs to your project, not the SDK CLI.

### 2. Upload a run

Add your Azure ML settings to the training project's `pyproject.toml`:

```toml
[tool.modelzone]
subscription_id = "<azure-subscription-id>"
resource_group = "<resource-group>"
workspace_name = "<azure-ml-workspace>"
experiment_name = "my_model"
```

To train and upload on success:

```bash
train --azureml
```

Or upload an existing local run without training again:

```bash
modelzone upload .runs/2026-07-16T12:11:55__FINISHED
```

Only finished runs can be uploaded. The saved `results/run.json` must contain
`status` and `duration_seconds`. Upload copies the run to Azure ML's
_Output + logs_ and adds its tags and metrics. The `duration_seconds` metric
records local training time, not upload time.

### 3. Register a model

When a model is ready for production, register the uploaded run in Azure ML's
model registry:

```bash
modelzone register <run_id>
```

Find `run_id` in the run's _Name_ property in Azure ML. Registration uses the
run's uploaded `output/` folder and creates the next version of the model named
by `experiment_name`. The command prints the model name and version to use below.

### 4. Fetch for inference

In the inference project's `pyproject.toml`, set the workspace and the registered
model name and version:

```toml
[tool.modelzone]
subscription_id = "<azure-subscription-id>"
resource_group = "<resource-group>"
workspace_name = "<azure-ml-workspace>"
model_name = "my_model"
model_version = 1
```

Fetch the model, then install its package in your inference environment:

```bash
modelzone fetch model
python -m pip install .model/package
```

`modelzone fetch` on its own is short for `modelzone fetch model`.

To try an uploaded run without registering it, use this fetch command instead:

```bash
modelzone fetch run --run-id <run_id>
```

Both methods download code to `.model/package` and artifacts to `.model/artifacts`
in the current directory. Installation is always a separate step. Use
`--dest-dir <dir>` to change the destination; only its `package/` and `artifacts/`
folders are replaced, leaving other files untouched.

## Model artifacts

Inherit from `mz.Artifact` to save and load your model using pickle. Define the
class in an importable module such as `mypackage/forecaster.py`, not `__main__`:

```python
import modelzone as mz

class Forecaster(mz.Artifact):
    def __init__(self, model):
        self.model = model

    def predict(self, features):
        return self.model.predict(features)
```

Inside your training function, wrap the fitted model and save it:

```python
from mypackage.forecaster import Forecaster

mz.tracker.log_artifact("forecaster", Forecaster(fitted_model))
```

This writes `artifacts/forecaster.pkl` in the run. After fetching and installing
the package, load it from an inference script at the project root:

```python
from pathlib import Path

from mypackage.forecaster import Forecaster

artifacts_dir = Path(__file__).parent / ".model/artifacts"
forecaster = Forecaster.load("forecaster", artifacts_dir)
```

Adjust the artifact path if you used `--dest-dir`. Only load trusted artifacts:
unpickling can execute code. The model's package and dependencies must be installed.

## Logging

Use `mz.tracker` inside your tracked training function:

```python
mz.tracker.log_metric("MAE", 1.23)
mz.tracker.log_metric("MAE_by_country", 1.23, dimensions={"country": "DK"})
mz.tracker.log_tag("model_type", "LinearRegression")
mz.tracker.log_message("Finished fitting the model")
mz.tracker.log_file("notes.txt", "Finished evaluating the model")
mz.tracker.log_file("payload.bin", b"example data")
```

For a Plotly figure, save its HTML as a file:

```python
mz.tracker.log_file("forecast.html", fig.to_html())
```

Logs are saved under `results/`: metrics and tags as JSONL, files under `files/`,
and messages in `logs.log`. Dimensioned metrics also get a text table in
`results/metrics/`; use the same dimension keys for a given metric name.

Tracking calls require an active run. For tracking without code snapshots or
uploads, use a `with mz.LocalTracker(output_dir):` block.

## CLI options

`modelzone upload`, `register`, and `fetch` read `[tool.modelzone]` from the
nearest `pyproject.toml` at or above the current directory.

| Commands                   | Option                      | Purpose                                     |
| -------------------------- | --------------------------- | ------------------------------------------- |
| All                        | `--subscription-id <id>`    | Override `subscription_id`.                 |
| All                        | `--resource-group <group>`  | Override `resource_group`.                  |
| All                        | `--workspace-name <name>`   | Override `workspace_name`.                  |
| `upload`, `register`       | `--experiment-name <name>`  | Override `experiment_name`.                 |
| `fetch run`                | `--run-id <id>`             | Override `run_id`.                          |
| `fetch model`              | `--model-name <name>`       | Override `model_name`.                      |
| `fetch model`              | `--model-version <version>` | Override `model_version`.                   |
| `fetch run`, `fetch model` | `--dest-dir <dir>`          | Change the destination (default: `.model`). |

An option is required when its setting is missing from `[tool.modelzone]`, so no
`pyproject.toml` is needed when all settings are supplied as options:

```bash
modelzone fetch run --run-id <run_id> --subscription-id <id> \
    --resource-group <group> --workspace-name <name>
```

Use `modelzone <command> --help` for the available options and configured values.

## Examples

- [Training project](examples/training_example/)
- [Inference project](examples/predict_example/)
- [Full workflow script](examples/happy_path.sh)

To try local training, start at the repository root in a dedicated Python environment:

```bash
python -m pip install -e .
cd examples/training_example
python -m pip install -e .
train
```

For Azure ML support, install `python -m pip install -e '.[train]'` in the example
directory. Its `train` extra installs the SDK's `azureml` extra.

The full workflow script resets its named Python environments, removes previous
example outputs, and uploads and registers a model in Azure ML. Run it only when
those side effects are intended.

