Skip to content

Versions

A version is a tag: a name such as 1.0 on what a dataset yielded at one moment. A tag never moves. Besides tag names, stable (the newest tag), head (what the dataset yields now), a DatasetVersion id and a prefix of a digest or id resolve too. An upload also has v0, as it arrived. Uploads, merged datasets and mixtures all have versions.

client.versions is on the qualia.v1 Versions API, which every key that can read the dataset reaches. A key restricted to workspaces is refused.

Methods take a version’s full name (accounts/<account>/datasets/<dataset>/versions/<tag>) or a tag plus dataset=. download, the methods that prepare a version and curation_record also take <dataset>:<tag>, with the dataset’s slug or id: a version’s reference, such as "my-dataset:1.0".

from qualia import Qualia
client = Qualia()
diff = client.versions.compare("<DATASET_UUID>") # newest tag → head
tag = client.versions.create(
"<DATASET_UUID>",
diff.suggested_version,
note=diff.draft_note,
digest=diff.target_digest, # refused (409) if head moved since
)
print(tag.reference, tag.dataset_version_id)

list(dataset) pages the tags newest first, get(tag, dataset=) reads one, and update(tag, dataset=, note=) edits its note.

result = client.versions.download("my-dataset:1.0", "./my-dataset-1.0")
print(result.destination, result.file_count, result.byte_count)

download starts the rendering, or picks up the one already there, and waits until it is done (timeout=, default 30 minutes; it keeps rendering after a timeout). It then fetches every file from its signed link into .<name>.qualia-version-partial beside dest, checks each one’s size and sha256 against the listing, and renames the folder to dest. dest never holds part of a version. The API key is never sent to a signed link.

ArgumentDescription
version<dataset>:<tag>, a full version name, or a tag plus dataset=. head is refused: tag it first
destThe folder to create; its parent is created if missing. Default: <dataset>-<tag> here, plus -v21, -mcap, -pi or -dreamlabs
layoutlerobot_v3 (default), lerobot_v2_1, mcap_ros2 (ROS 2 MCAP, one .mcap per episode), or a partner layout: pi_mcap_v1 or dreamlabs_v2_1 (see below)
on_progressCalled with {"step": "preparing", "state": ..., "version": "<slug>:<tag>"} while the server builds the version, then {"step": "downloading", "files_done", "files_total", "bytes_done", "bytes_total"} as each file arrives
timeoutSeconds to wait for the rendering
poll_intervalSeconds between reads while it renders (default 2)
parallelFiles fetched at once (default 4)

It returns a VersionDownload: destination, version, materialization (its digest names the exact files), layout, file_count, byte_count, downloaded_file_count and reused_file_count. to_wire() gives it as JSON.

A call that stops keeps the files it checked; the same call again checks them and fetches only the rest. When dest already holds exactly this rendering, the call checks it and fetches nothing. Any other existing dest raises FileExistsError. A file whose bytes do not match raises QualiaError and nothing is published.

pi_mcap_v1 and dreamlabs_v2_1 are files to upload to Physical Intelligence or Dream Labs with your own account. They hold the tag’s episodes that are not rejected (dreamlabs_v2_1: only the successful ones), with a README.md that says how to upload them (and upload_to_pi.py for Pi); an episode that could not be converted is listed there with its error. See Partner layouts.

Preparing a version in a format the first time needs data:write; once it is prepared, any key that can read the dataset downloads it. To download by dataset name, with the newest version as the default, use client.data.download.

m = client.versions.start_materialization("my-dataset:1.0", layout="lerobot_v2_1")
m = client.versions.get_materialization(m.name) # NOT_STARTED, RUNNING, SUCCEEDED, INTERRUPTED
page = client.versions.list_materialization_files(m.name, limit=512)
for f in page.items:
print(f.path, f.size_bytes, f.sha256, f.uri) # uri is valid until f.expire_time
history = client.versions.merge_history("<MERGED_DATASET_UUID>")
for entry in history.entries:
print(entry.create_time, entry.kind, len(entry.datasets))

What was done to a dataset in the round a version opened: from its creation to the next tag’s (for head, since the newest tag). This is the dashboard’s Curation of 1.0 panel.

record = client.versions.curation_record("my-dataset:1.0")
for event in record.events: # oldest first
print(event.create_time, event.actor.display_name, event.summary)
print(record.start_time, record.end_time, record.truncated)

A merged dataset’s record also reads the uploads it holds, from when its merge took each in. At most 2000 events are read; a longer round keeps its newest and sets truncated. The events come from the dataset’s workspace.