---
myst:
html_meta:
description: "Stream values to a dataset as they're produced, tune batching, and filter streamed data by tags."
---
# Stream data
{.lead}
A write stream sends values to a dataset as your program produces them, batching them in the background.
Use it for live telemetry. For files, see [Datasets](/guides/datasets/overview.md).
For log messages, see [Stream logs](/guides/streaming/logs.md).
```{include} /guides/_snippets/connect-the-client.md
```
## Stream values
{py:meth}`~nominal.core.Dataset.get_write_stream` returns a stream. Leaving the `with` block sends what's left and closes it.
```python
import time
from datetime import datetime, timezone
from nominal.core import ChannelDataType
dataset = client.create_dataset("Motor test telemetry", prefix_tree_delimiter=".")
dataset.add_channel("motor.temperature", ChannelDataType.DOUBLE, unit="K")
dataset.add_channel("motor.mode", ChannelDataType.STRING)
start = datetime.now(timezone.utc)
with dataset.get_write_stream() as stream:
for i in range(50):
now = datetime.now(timezone.utc)
for motor, baseline in [("left", 295.0), ("right", 297.0)]:
stream.enqueue("motor.temperature", now, baseline + 0.1 * i, tags={"motor_id": motor})
stream.enqueue("motor.mode", now, "idle" if i < 10 else "running", tags={"motor_id": motor})
time.sleep(0.2)
end = datetime.now(timezone.utc)
```
Registering channels first sets their types. See [Channels and data types](/guides/datasets/channels.md).
Timestamps can be timezone-aware datetimes, ISO 8601 strings or epoch nanoseconds.
Use the time each value was measured, not the time you enqueue it.
To write several channels at one timestamp, use `enqueue_from_dict`.
To write many points of one channel, use `enqueue_batch`.
Arrays and structs have `enqueue_float_array`, `enqueue_string_array` and `enqueue_struct`.
## Batching and delivery
The stream sends a batch when it reaches `batch_size` points, 250,000 by default, or after `max_wait`, 0.25 seconds by default.
Smaller values lower latency. Larger values lower request overhead.
`enqueue` doesn't confirm delivery, and there's no file to poll.
The two implementations report failures differently:
- **Rust**, the default where `nominal-streaming` is installed, can save failed batches to an `.avro` file with `file_fallback` for [later upload](/guides/datasets/avro-streams.md).
It also supports `num_workers` and `track_metrics`.
- **Python** logs failed uploads and doesn't raise them when the stream closes.
Choose one with `implementation="rust"` or `implementation="python"`.
After streaming, check that the channels cover the expected time range.
## Add tags to filter data in a run or asset
Tags tell apart points in the same channel, such as the two motors above.
A [run](/guides/runs.md) or [asset](/guides/assets.md) can select one tag value with `series_tags`:
```python
run = client.create_run("Left motor test", start=start, end=end)
run.add_dataset("motor", dataset, series_tags={"motor_id": "left"})
```
Use tags for a small set of values, such as component, vehicle or test configuration.
Don't tag with values that change every point, such as a counter or a UUID per sample, because each tag combination adds query work.
## View streamed data
Open the dataset's asset or run in a workbook and click **Go Live** to follow new points.
For a full walkthrough, see the [upload and stream tutorial](/guides/longstart.md#stream-data-to-the-asset).
```{video} /guides/images/b5/stream-gif.mp4
:loop:
:alt: Streamed data in a Nominal workbook
```
## Migrate from connections
`Connection` and `StreamingConnection` were removed. Stream to a dataset instead:
```python
dataset = client.get_dataset("")
with dataset.get_write_stream() as stream:
stream.enqueue("temperature", "2026-01-01T00:00:00Z", 42.0)
```
Get a migrated connection's dataset RID from Nominal. A connection RID passed to `get_dataset` doesn't resolve.
External datasets read from their provider and don't accept streamed writes.
`nominal.experimental.stream_v2.create_write_stream()` now takes a `Dataset` as its first argument.