Skip to main content

turbodata (Python)

Python SDK for the turbodata .td container format. Read/write port of the Go reference implementation in ../go. The on-disk format is identical, so files written by any SDK can be read by any other.

Built so model developers can directly manipulate .td files (read training data, write preprocessed shards, sample at specific timestamps for multi-topic alignment).

pip install turbodata          # or, for local dev: pip install -e ".[test]"

Dependencies: msgpack, zstandard. See the top-level README for scope and the format overview. This guide is API usage by feature: write → read → sample → multi-file. Runnable versions of every snippet live in examples/.

Write

Open one or more topics as a group, append messages with non-decreasing timestamps, then close the topic group. The Writer context manager finalizes the file on exit.

from turbodata import Writer

with open("out.td", "wb") as f, Writer(f) as w:
    w.open_topics(
        ["/imu", "/cam"],
        [{"hz": 100}, {"hz": 10}],
        compression=True,
    )
    w.write_message("/imu", b"...", timestamp=1_000)
    w.write_message("/cam", b"...", timestamp=2_000)
    w.close_topic()

The writer enforces non-decreasing timestamps within an open topic group.

Schema metadata

Supply schema_name, schema_encoding, and binary schema_data per topic for downstream schema discovery. Missing, null, or empty values produce one warning per topic when opening it; writing continues by default. The minimal examples above omit these fields and therefore warn. Reading does not warn.

from turbodata import META_KEY_SCHEMA_NAME, META_KEY_SCHEMA_ENCODING, META_KEY_SCHEMA_DATA

metadata = {
    META_KEY_SCHEMA_NAME: "Example",
    META_KEY_SCHEMA_ENCODING: "jsonschema",
    META_KEY_SCHEMA_DATA: b'{"type":"object"}',
}
# Pass metadata as the corresponding entry in open_topics' metadatas list.

Chunk config

Chunks are the unit of indexing and I/O. By default the writer flushes a chunk at a size threshold; chunk_config overrides the policy (by size, message count, or time duration).

from turbodata import ChunkConfig, ChunkThresholdMode

w.open_topics(
    ["/imu"],
    [{"hz": 100}],
    chunk_config=ChunkConfig(mode=ChunkThresholdMode.SIZE, size=1024 * 1024),
)

Video

Mark a group as video with video=True and append frames with write_video_message (which carries an is_key_frame flag). Chunk boundaries are gated on key frames, so a GOP is never split across two chunks.

w.open_topics(["/cam/h264"], [{"codec": "h264"}], video=True)
w.write_video_message("/cam/h264", key_frame_bytes, 1_000, True)  # first frame must be a key frame
w.write_video_message("/cam/h264", delta_bytes, 2_000, False)
w.close_topic()

A video group MUST contain exactly one topic and MUST NOT also be compressed (the codec already compresses the bytes). The first frame MUST be a key frame.

Read

from turbodata import FileReadSource, Reader

with FileReadSource("data.td") as src:
    reader = Reader(src)
    for msg in reader.read_messages():
        # msg.timestamp: int (unit set by the writer)
        # msg.topic_name: str
        # msg.data: bytes (aliases an internal buffer; copy if you need to keep it)
        print(msg.timestamp, msg.topic_name, len(msg.data))

msg.data aliases an internal reusable buffer and is only valid until the next iteration step. Pass copy=True for fresh bytes each iteration:

for msg in reader.read_messages(copy=True):
    ...  # msg.data is independent of subsequent pulls

Order, time, and topic filters

Topics not listed are skipped; chunks fully outside the [start, end] window are never fetched.

from turbodata import Order

reader.read_messages(
    order=Order.REVERSE_TIME,           # default: Order.TIME
    topic_names=["/imu", "/cam"],        # default: all topics
    start_timestamp=1_000_000_000,       # inclusive lower bound
    end_timestamp=2_000_000_000,         # inclusive upper bound
)

Cost-aware concurrent reads

For cloud object storage (S3 / GCS) or large local reads, pass a ReadStrategy. The reader pre-plans every needed byte range, coalesces nearby ranges, optionally splits large reads, and issues them concurrently via a thread pool. Without a strategy it uses the memory-minimal lazy path.

from turbodata import strategy_for_latency

reader.read_messages(
    strategy=strategy_for_latency(
        rtt_seconds=0.020,
        per_stream_bw=125 * 1024 * 1024,
        concurrency=16,
    ),
)

Helper constructors mirror the Go SDK: strategy_for_latency, strategy_for_money, strategy_for_blended. Pass a hand-tuned ReadStrategy(coalesce_gap=..., split_threshold=..., max_concurrency=...) when you know the right values for your storage backend.

Video

A non-key frame is only decodable after its GOP's key frame, so a plain time filter that starts mid-GOP yields bytes a decoder can't cold-start on. video_decodable=True snaps the effective start back to the latest key frame at or before it, so the emitted sequence starts decodable. Non-video topics are unaffected.

reader.read_messages(
    topic_names=["/cam/h264"],
    start_timestamp=650,        # mid-GOP
    video_decodable=True,       # snaps back to the key frame at/<=650
)

Sample (floor lookup at concrete timestamps)

sample returns the floor message per (topic, timestamp) pair: the most-recent message at or before each requested timestamp. Use it for "the state at time T" rather than "every message in [t0, t1]".

from turbodata import FileReadSource, Reader, SampleQuery, lin_space_timestamps

with FileReadSource("data.td") as src:
    reader = Reader(src)
    out = reader.sample([
        SampleQuery(topic="/imu", timestamps=lin_space_timestamps(0, 100_000_000, 10)),
        SampleQuery(topic="/cam", timestamps=[1_000_000_000, 2_000_000_000]),
    ])
    for q, row in zip(["/imu", "/cam"], out):
        for r in row:
            if r.found:
                print(q, r.timestamp, len(r.data))

out[i][j] corresponds to queries[i].timestamps[j]. The same topic must not appear in more than one SampleQuery; timestamps within one query must be strictly increasing.

Strategy

Pass strategy=... to override the default sample strategy for your backend:

from turbodata import ReadStrategy

reader.sample(
    queries,
    strategy=ReadStrategy(coalesce_gap=256 * 1024, split_threshold=2 * 1024 * 1024, max_concurrency=4),
)

Video

With video_decodable=True, results for a video topic carry a decoder-ready GOP sequence in frames (and data is empty) instead of the single floor frame. Within a row (one topic, strictly increasing timestamps), the first result in each GOP sets reset_decoder=True with the full prefix [keyframe ... target]; later results in the same GOP set reset_decoder=False with only the new frames since the previous query.

out = reader.sample(
    [SampleQuery(topic="/cam/h264", timestamps=[350, 650])],
    video_decodable=True,
)
for r in out[0]:
    if r.found:
        # r.is_video is True; feed r.frames to a decoder,
        # resetting it first when r.reset_decoder is True.
        print(r.timestamp, r.reset_decoder, len(r.frames))

Multi-file reading (MultiReader)

MultiReader presents several single-file Readers as one time-ordered stream. Read/sample options pass through to each underlying reader. Topic-name collisions across files are resolved with per-Reader topic_remap: names meant to union share an exposed name; names meant to stay distinct are remapped apart.

from turbodata import BytesReadSource, MultiReader, Reader, SampleQuery

mr = MultiReader(
    Reader(BytesReadSource(file_a)),
    # Keep file B's "/cam" distinct instead of unioning it with A's.
    Reader(BytesReadSource(file_b), topic_remap={"/cam": "/cam_b"}),
)

for msg in mr.read_messages():
    print(msg.timestamp, msg.topic_name)

# sample() returns, per cell, the latest floor across the union of files.
out = mr.sample([SampleQuery(topic="/cam", timestamps=[150, 350])])

Topic remap

topic_remap is keyed by in-file name and valued by the exposed name reported by summary(), emitted from read_messages, and accepted by topic_names and SampleQuery.topic. Names absent from the map pass through unchanged. It works on a single Reader too:

reader = Reader(src, topic_remap={"/cam": "/cam_v2"})
for msg in reader.read_messages(topic_names=["/cam_v2"]):
    print(msg.topic_name)  # "/cam_v2"

The remap is validated lazily against the file's summary on first use: it raises TopicRemapCollisionError if two topics collapse onto the same exposed name.

ReadSource protocol

A ReadSource exposes two methods:

class ReadSource(Protocol):
    def size(self) -> int: ...
    def read_at(self, offset: int, n: int) -> bytes: ...

read_at MUST be safe to call from multiple threads concurrently when used with the cost-aware path. Built-in sources:

  • FileReadSource(path_or_fd) — local files, uses os.pread on POSIX
  • BytesReadSource(bytes) — in-memory bytes for tests/small files

Roll your own for HTTP range reads, S3, GCS, etc.

Mapping to the Go SDK

Go Python
NewReader(rs) Reader(source)
Summary() reader.summary()
ReadMessages(opts...) reader.read_messages(**kwargs)
Sample(queries, opts...) reader.sample(queries, **kwargs)
WithTopicNames topic_names=[...]
WithStartTimestamp start_timestamp=...
WithEndTimestamp end_timestamp=...
WithOrder(ReverseTimeOrder) order=Order.REVERSE_TIME
WithReadStrategy(s) strategy=...
WithTailPrefetch(n) tail_prefetch=n
WithVideoDecodable() video_decodable=True
WithSampleVideoDecodable() video_decodable=True (on sample)
WithTopicRemap(m) Reader(src, topic_remap={...})
NewWriter(f) Writer(f)
OpenTopics(names, metas, opts...) writer.open_topics(...)
WithChunkConfig(cc) chunk_config=ChunkConfig(...)
WithCompression() compression=True
WithVideoTopic() video=True
WriteMessage(topic, data, ts) writer.write_message(topic, data, ts)
WriteVideoMessage(topic, data, ts, kf) writer.write_video_message(topic, data, ts, kf)
CloseTopic() writer.close_topic()
Close() writer.close() (or with)

Run the examples

Run write_demo.py first; it produces examples/demo.td, which the read and sample demos consume.

python examples/write_demo.py     # produces examples/demo.td (incl. a video group)
python examples/read_demo.py      # filters, order, strategy, video snap-back
python examples/sample_demo.py    # floor lookup, strategy, video GOP-prefix
python examples/multiread_demo.py # union, split-via-remap, cross-file sample

Run the tests

pip install -e ".[test]"
pytest tests/

Cross-language tests run against the Go SDK's example fixtures (../ts/test/fixtures/*.td and ../go/examples/demo.td) when those files are present.

Metadata

Release files for turbodata 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for turbodata 0.1.1
File Size Uploaded Provenance Actions
turbodata-0.1.1.tar.gz 67.8 kB With provenance Details for turbodata-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for turbodata 0.1.1
File Interpreter ABI Platform Actions
turbodata-0.1.1-py3-none-any.whl Python 3 none any Details for turbodata-0.1.1-py3-none-any.whl

Total release size: 117.5 kB

Release files / turbodata-0.1.1.tar.gz

Download URL turbodata-0.1.1.tar.gz
Size 67.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d648263ab8232334db55e4fe1fee2b55d63abecdfc008a26989de543e2d79640
BLAKE2b-256 checksum
How to use checksums
1cda651ddbabe9d1286cbb4ef519e54f7a5d197c9ea10f1657becb24216afe16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / turbodata-0.1.1-py3-none-any.whl

Download URL turbodata-0.1.1-py3-none-any.whl
Size 49.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ed6dae616eabf68368ec77b0116b207c5492a62bd203e1551ab6cfa26976102a
BLAKE2b-256 checksum
How to use checksums
8b18cbd92d233d8c8cc1b470c58ca7297a79b29a92778b4baf70a885f77ac9a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for turbodata 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for turbodata-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page