# Runs

A run names a span of time for one test or analysis, such as a flight or a test-stand firing.
It references data rather than copying it.

## Connect the client

This guide assumes you have [installed](https://dev.nominal.io/python/guides/install.md) the client and setup your [API keys](https://dev.nominal.io/python/guides/api-keys-setup.md).

```python
from nominal.core import NominalClient

client = NominalClient.from_profile("default")
```

## Create a run on an asset

When the data already belongs to an [asset](https://dev.nominal.io/python/guides/assets.md), create the run from the asset with the test’s time window.
The run sees the asset’s datasets through their data scopes, so you don’t attach them again.

```python
from datetime import datetime, timezone

asset = client.get_asset("<ASSET_RID>")
run = asset.create_run(
    "Flight acceptance test",
    start=datetime(2026, 9, 10, 14, 38, tzinfo=timezone.utc),
    end=datetime(2026, 9, 10, 14, 48, tzinfo=timezone.utc),
)
```

Start and end accept timezone-aware datetimes or epoch nanoseconds.
Pass `end=None` for a test that’s still running.
For a run across several assets, use [`create_run()`](https://dev.nominal.io/python/ref/generated/nominal.core.NominalClient.md#nominal.core.NominalClient.create_run "nominal.core.NominalClient.create_run") with `assets=[...]`.

## Create a run from datasets

Without an asset, attach each dataset to the run under a ref name with [`add_dataset()`](https://dev.nominal.io/python/ref/generated/nominal.core.Run.md#nominal.core.Run.add_dataset "nominal.core.Run.add_dataset").
This example sets the run’s bounds from an uploaded file.

The examples upload `flight.csv`. To run them as written, save this sample under that name:

```text
time,altitude,temperature,tail_number
2026-09-10T14:38:19Z,100.0,20.0,N456QC
2026-09-10T14:38:20Z,101.5,20.2,N456QC
2026-09-10T14:38:21Z,103.0,20.4,N456QC
```

```python
dataset = client.create_dataset("Frosty flight")
file = dataset.add_tabular_data("flight.csv", timestamp_column="time", timestamp_type="iso_8601")
file.poll_until_ingestion_completed()
assert file.bounds is not None  # set once ingestion succeeds

run = client.create_run("High precipitation flight", start=file.bounds.start, end=file.bounds.end)
run.add_dataset("telemetry", dataset)
```

The ref name, here `"telemetry"`, is the dataset’s name within the run.
Use the same ref name for the same kind of data on every run, so workbook templates and checklists work across runs.
`add_dataset` also takes `series_tags`, to select [tagged data](https://dev.nominal.io/python/guides/streaming/overview.md#add-tags-to-filter-data-in-a-run-or-asset), and a time `offset`.

[`get_dataset()`](https://dev.nominal.io/python/ref/generated/nominal.core.Run.md#nominal.core.Run.get_dataset "nominal.core.Run.get_dataset") and [`list_datasets()`](https://dev.nominal.io/python/ref/generated/nominal.core.Run.md#nominal.core.Run.list_datasets "nominal.core.Run.list_datasets") return only datasets attached this way.
For data on an associated asset, use [`get_dataset()`](https://dev.nominal.io/python/ref/generated/nominal.core.Asset.md#nominal.core.Asset.get_dataset "nominal.core.Asset.get_dataset").

[`remove_data_sources()`](https://dev.nominal.io/python/ref/generated/nominal.core.Run.md#nominal.core.Run.remove_data_sources "nominal.core.Run.remove_data_sources") detaches a dataset without deleting it:

```python
run.remove_data_sources(ref_names=["telemetry"])
```

## Find runs

Retrieve one run by the RID on its page in Nominal, or search with [`search_runs()`](https://dev.nominal.io/python/ref/generated/nominal.core.NominalClient.md#nominal.core.NominalClient.search_runs "nominal.core.NominalClient.search_runs"):

```python
run = client.get_run("<RUN_RID>")
review_runs = client.search_runs(labels=["needs-review"], asset=asset)
```

`name_substring` matches names, descriptions, labels and properties, so results may not contain the text in their name.

## Update a run

[`update()`](https://dev.nominal.io/python/ref/generated/nominal.core.Run.md#nominal.core.Run.update "nominal.core.Run.update") replaces each field you pass and leaves the others unchanged.
To add a property, merge it with the existing ones:

```python
run.update(labels=["reviewed"], properties={**run.properties, "reviewer": "flight-test"})
```

To close a run that’s still going, set its end:

```python
run.update(end=datetime.now(timezone.utc))
```

Changing bounds changes the window the run shows, not the stored data.

## Attachments, workbooks and events

Attach files about this test to the run, and files about the system to its [asset](https://dev.nominal.io/python/guides/assets.md#update-metadata-and-attachments):

```python
run.add_attachments([client.create_attachment("flight_report.pdf")])
```

Create a workbook for the run from a template whose inputs match the run’s ref names or data scope names. See [Workbooks](https://dev.nominal.io/python/guides/workbooks.md).

Mark moments inside the run with [events](https://dev.nominal.io/python/guides/events.md).

## Archive a run

[`archive()`](https://dev.nominal.io/python/ref/generated/nominal.core.Run.md#nominal.core.Run.archive "nominal.core.Run.archive") hides the run without deleting its data:

```python
run.archive()
```
