timeseries-table-format (Python)
Python-first workflow for managing local, append-only time-series tables stored as Parquet
segments on disk, with SQL querying (DataFusion) that returns pyarrow.Table.
Each table has one ascending chronological index: a physical Timestamp or an Int64/UInt64 logical clock with application-defined units.
- PyPI:
timeseries-table-format - Import:
timeseries_table_format - Docs: https://mag1cfrog.github.io/timeseries-table-format/
v0 is local-filesystem-only (no S3/object storage backend yet).
Install
pip install timeseries-table-format
Requires: Python 3.10+. pyarrow is installed automatically (dependency: pyarrow>=23.0.0).
If pip tries to build from source (Rust errors), see Troubleshooting below.
Verify installation
import timeseries_table_format as ttf
out = ttf.Session().sql("select 1 as x")
print(type(out)) # pyarrow.Table
Development: build and test locally
To run the Python test suite against the local editable Rust extension, build the extension into
the repo virtualenv first, then run pytest with the same interpreter.
cd crates/timeseries-table-python
uv sync --group dev
uv run maturin develop --features test-utils
uv run pytest -q
Return type and interop
Session.sql(...) returns a pyarrow.Table.
- Polars:
pip install polars, thenpolars.from_arrow(out)
Notebook display (Jupyter/IPython)
In IPython/Jupyter (including VS Code notebooks), pyarrow.Table results will display as a bounded HTML preview by default (the return type is still a real pyarrow.Table).
- Defaults:
max_rows=20(head/tail),max_cols=50(left/right),max_cell_chars=2000 - Opt-out: set
TTF_NOTEBOOK_DISPLAY=0before importingtimeseries_table_format, or calltimeseries_table_format.disable_notebook_display() - Configure: call
timeseries_table_format.enable_notebook_display(max_rows=..., max_cols=..., max_cell_chars=..., align=...) - Config file (TOML): set
TTF_NOTEBOOK_CONFIG=path/to/ttf.tomlbefore importingtimeseries_table_format(or calltimeseries_table_format.load_notebook_display_config("path/to/ttf.toml")) (On Python 3.10, installtomlito enable TOML parsing.) - Alignment:
align="right"(default) oralign="auto"(strings left, numbers right); auto-enable can be configured withTTF_NOTEBOOK_ALIGN=auto|left|right - Cells are visually clipped to a bounded column width with an ellipsis indicator; copying a cell copies the underlying value (up to
max_cell_chars).
Example ttf.toml:
[notebook_display]
max_rows = 20
max_cols = 50
max_cell_chars = 2000
align = "auto"
Maintainers: releasing the Python package
The PyPI package version is derived from the local Cargo.toml via maturin.
Published wheels use CPython's stable ABI with a Python 3.10 minimum and carry the
cp310-abi3 tag. Release CI builds one wheel for Linux x86_64, macOS arm64, macOS
x86_64, and Windows x86_64, then tests the same Linux wheel on CPython 3.10 through
3.14. These wheels target ordinary GIL-enabled CPython. Free-threaded CPython, PyPy,
and other Python implementations are not supported by this wheel policy.
Quickstart: create → append → query
import tempfile
from pathlib import Path
import pyarrow as pa
import pyarrow.parquet as pq
import timeseries_table_format as ttf
with tempfile.TemporaryDirectory() as d:
table_root = Path(d) / "my_table"
tbl = ttf.TimeSeriesTable.create(
table_root=str(table_root),
index_column="ts",
index_type="timestamp",
bucket="1h",
entity_columns=["exchange_id", "symbol"],
timezone=None,
)
seg_path = table_root / "incoming" / "prices.parquet"
seg_path.parent.mkdir(parents=True, exist_ok=True)
pq.write_table(
pa.table(
{
"ts": pa.array([0, 3_600 * 1_000_000, 7_200 * 1_000_000], type=pa.timestamp("us")),
"exchange_id": pa.array([1, 1, 1], type=pa.int32()),
"symbol": pa.array(["NVDA", "NVDA", "NVDA"], type=pa.string()),
"close": pa.array([10.0, 20.0, 30.0], type=pa.float64()),
}
),
str(seg_path),
)
tbl.append_parquet(str(seg_path))
sess = ttf.Session()
sess.register_tstable("prices", str(table_root))
out = sess.sql(
"select ts, exchange_id, symbol, close "
"from prices order by exchange_id, symbol, ts"
)
print(out) # pyarrow.Table
entity_columns defines independent identities within one logical table. Different identities may
reuse the same timestamp bucket, and one Parquet file may contain multiple identities. An append
is rejected only when the same complete identity already covers a bucket. In SQL, entity columns
are ordinary columns that can be filtered or grouped.
Entity columns support Arrow string, large_string, int32, int64, and uint64. Actual
entity values must be non-null. Composite identity components follow the configured
entity_columns order. Signed and unsigned integers keep their exact types and are not compared
as strings. Entity column names must be unique and cannot also be the ordered index column.
Unsupported domains and mismatched types are rejected rather than cast.
Parquet rows need not be sorted by the ordered index. SQL query results are unordered unless the
query uses ORDER BY.
Optimize mixed-entity segments
Mixed-entity segments are valid input. Entity-layout optimization is explicit and optional:
report = tbl.optimize()
print(report.source_segments_replaced, report.replacement_segments_written)
For each mixed source segment, optimization writes one replacement segment per complete entity
identity. It preserves logical rows, schema, and per-entity coverage. If no mixed live segments
need rewriting, report.no_op is True, starting_version equals committed_version, and no new
table version is created.
Optimization may change physical row order.
Optimization does not combine small files or accept a target file size. Replaced source files may remain on disk until a future vacuum operation removes unreferenced files.
Bucket size (important):
bucket=1hdoes not resample your data. It defines the time grid used for overlap detection and coverage tracking. Example: withbucket=1h, timestamps10:05and10:55fall into the same bucket (10:00–11:00). See https://mag1cfrog.github.io/timeseries-table-format/concepts/bucketing_and_overlap/
Integer chronological indexes
Integer Parquet columns must be Arrow int64 or uint64 exactly; signedness is not converted.
import timeseries_table_format as ttf
signed = ttf.TimeSeriesTable.create(
table_root="signed_ticks",
index_column="tick",
index_type="int64",
bucket_width=10,
)
signed.append_parquet("signed.parquet")
unsigned = ttf.TimeSeriesTable.create(
table_root="unsigned_counters",
index_column="counter",
index_type="uint64",
bucket_width=100,
)
unsigned.append_parquet("unsigned.parquet")
assert unsigned.index_spec() == {
"column": "counter",
"entity_columns": [],
"kind": "uint64",
"bucket_width": 100,
}
session = ttf.Session()
session.register_tstable("signed_ticks", "signed_ticks")
session.register_tstable("unsigned_counters", "unsigned_counters")
negative = session.sql(
"SELECT tick FROM signed_ticks WHERE tick >= -20 AND tick < 0 ORDER BY tick"
)
large = session.sql(
"SELECT counter FROM unsigned_counters "
"WHERE counter >= CAST('9223372036854775808' AS BIGINT UNSIGNED) "
"ORDER BY counter"
)
See the ordered-index migration table when upgrading timestamp-only code.
Join multiple tables
# Aligned with examples/register_and_join_two_tables.py
import tempfile
from pathlib import Path
import pyarrow as pa
import pyarrow.parquet as pq
import timeseries_table_format as ttf
with tempfile.TemporaryDirectory() as d:
base_dir = Path(d)
prices_root = base_dir / "prices_tbl"
prices = ttf.TimeSeriesTable.create(
table_root=str(prices_root),
index_column="ts",
index_type="timestamp",
bucket="1h",
entity_columns=["symbol"],
timezone=None,
)
prices_seg = base_dir / "prices.parquet"
pq.write_table(
pa.table(
{
"ts": pa.array([0, 3_600 * 1_000_000], type=pa.timestamp("us")),
"symbol": pa.array(["NVDA", "NVDA"], type=pa.string()),
"close": pa.array([1.0, 2.0], type=pa.float64()),
}
),
str(prices_seg),
)
prices.append_parquet(str(prices_seg))
volumes_root = base_dir / "volumes_tbl"
volumes = ttf.TimeSeriesTable.create(
table_root=str(volumes_root),
index_column="ts",
index_type="timestamp",
bucket="1h",
entity_columns=["symbol"],
timezone=None,
)
volumes_seg = base_dir / "volumes.parquet"
pq.write_table(
pa.table(
{
"ts": pa.array([0, 3_600 * 1_000_000], type=pa.timestamp("us")),
"symbol": pa.array(["NVDA", "NVDA"], type=pa.string()),
"volume": pa.array([10, 20], type=pa.int64()),
}
),
str(volumes_seg),
)
volumes.append_parquet(str(volumes_seg))
sess = ttf.Session()
sess.register_tstable("prices", str(prices_root))
sess.register_tstable("volumes", str(volumes_root))
out = sess.sql(
"""
select p.ts as ts, p.symbol as symbol, p.close as close, v.volume as volume
from prices p
join volumes v
on p.ts = v.ts and p.symbol = v.symbol
order by p.ts
"""
)
print(out) # pyarrow.Table
Parameterized queries
DataFusion infers placeholder types from context when possible (e.g. in WHERE clauses).
If you use placeholders in a SELECT projection without type context, you may need an explicit cast.
# Aligned with examples/parameterized_queries.py
import timeseries_table_format as ttf
sess = ttf.Session()
out_positional = sess.sql(
"select cast($1 as bigint) as x, cast($2 as varchar) as y",
params=[1, "hello"],
)
out_named = sess.sql(
"select cast($a as bigint) as x, cast($b as varchar) as y",
params={"a": 2, "b": "world"},
)
print(out_positional)
print(out_named)
Building from source (contributors)
Prereqs:
- Rust toolchain installed
- Python 3.10+ (CI targets 3.10–3.14; examples below use 3.12)
uvinstalled
From the repo root:
cd crates/timeseries-table-python
uv sync --group dev
uv run maturin develop --features test-utils
uv run pytest -q
Type checking (ty):
uv run ty check python tests
Benchmark: SQL conversion and streaming SQL
Session.sql(...) returns results as a pyarrow.Table.
By default, results are exported via the Arrow C Data Interface (C Stream) when supported, and fall back to an in-memory Arrow IPC stream otherwise. To compare the two paths and estimate the conversion overhead, run:
cd crates/timeseries-table-python
uv pip install -p .venv/bin/python numpy
uv run -p .venv/bin/python maturin develop --features test-utils
.venv/bin/python bench/sql_conversion.py --target-ipc-gb 2
To also benchmark the streaming SQL API (Session.sql_reader(...)), include the streaming mode:
cd crates/timeseries-table-python
uv pip install -p .venv/bin/python numpy
uv run -p .venv/bin/python maturin develop --features test-utils
.venv/bin/python bench/sql_conversion.py --target-ipc-gb 2 --include-streaming --summary
Environment variables (useful for debugging and benchmarks):
TTF_SQL_EXPORT_MODE=auto|ipc|c_stream(default:c_stream)TTF_SQL_EXPORT_DEBUG=1to emit a debug warning whenautofalls back from C Stream → IPCTTF_SQL_EXPORT_AUTO_RERUN_FALLBACK=1to re-run the SQL query on C Stream failure inautomode (avoids cloning batches on the hot path, but may change results for non-deterministic queries)
Optional: benchmark IPC ZSTD compression (requires building with ipc-zstd):
uv run -p .venv/bin/python maturin develop --features test-utils,ipc-zstd
.venv/bin/python bench/sql_conversion.py --target-ipc-gb 2 --ipc-compression zstd
The script can print a human-friendly terminal summary (--summary) and/or write a JSON payload
to a file (--json path). It reports separate timings for:
- end-to-end
Session.sql(...) - Rust-side query+IPC encode (
_native._testing._bench_sql_ipc) - Rust-side query+C Stream export (
_native._testing._bench_sql_c_stream) - Python-side decode/import
With --include-streaming, it also reports:
- time to first batch for
Session.sql_reader(...) - total incremental iteration time and batch counts
- process-as-you-go comparison:
Session.sql_reader(...)batch iteration vsSession.sql(...)materialize-then-process Session.sql_reader(...).read_all()vsSession.sql(...)parity- best-effort process peak RSS (
ru_maxrss) after each run - isolated child-process peak RSS for the process-as-you-go comparison
(sampled from Linux
/proc/<pid>/status)
Sample streaming benchmark results
One local Linux run over a generated dataset of about 10.5M rows:
| Query | Metric | Session.sql_reader(...) |
Session.sql(...) materialize-then-process |
Improvement |
|---|---|---|---|---|
select * from prices |
First batch available | 370.7ms |
2.312s |
84.0% earlier |
select * from prices |
Peak RSS | 2.30 GiB |
3.60 GiB |
36.1% lower |
select * from prices order by ts |
First batch available | 2.489s |
13.182s |
81.1% earlier |
select * from prices order by ts |
Peak RSS | 3.66 GiB |
4.84 GiB |
24.4% lower |
sql_reader(...).read_all() stayed in the same general performance range as Session.sql(...).
Large targets can require high peak RAM (IPC bytes + decoded Table + intermediate buffers). Start with
--target-ipc-gb 2 and scale up to 3 or 6 on a machine with plenty of memory.
If you hit Disk quota exceeded, pass --tmpdir /path/with/more/space (the bench uses a temporary
directory and cleans it up on exit).
Troubleshooting
pipis building from source / fails with Rust errors: no wheel is available for your platform/Python; install Rust and retry, or use a supported Python/platform combination.DataFusionErrorabout an unknown table name: callsess.register_tstable("name", "/path/to/table")first; usesess.tables()to list registrations.- Append fails with an ordered-index error: the column must exactly match the configured Arrow
timestamp(...),int64, oruint64type; Timestamp units must also remain consistent across segments. SchemaMismatchErroron append: the new Parquet segment schema must match the table's adopted schema (column names and types).- SQL errors / parameter placeholders: try an explicit
CAST(...)for placeholders used inSELECTprojections.
Metadata
Release files for timeseries-table-format 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| timeseries_table_format-0.4.0.tar.gz | 407.0 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| timeseries_table_format-0.4.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| timeseries_table_format-0.4.0-cp310-abi3-manylinux_2_28_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| timeseries_table_format-0.4.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| timeseries_table_format-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 139.5 MB
Release files / timeseries_table_format-0.4.0.tar.gz
| Download URL | timeseries_table_format-0.4.0.tar.gz |
|---|---|
| Size | 407.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
502d9ace581b320872ae949e7532a45c8efdd233a6e3dcb09d29a7e6d5e7da98
|
|
BLAKE2b-256 checksum How to use checksums |
663a69c7a6777e893c3d97eda21e2c454ebb9dce20bf5150c07734caca2a80e5
|
| 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 Aug 17, 2026.
Transparency logRelease files / timeseries_table_format-0.4.0-cp310-abi3-win_amd64.whl
| Download URL | timeseries_table_format-0.4.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 33.6 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3bbd413d56de986784e1155ae959f922f40a6fa6f011869c1e9618c9a61974d0
|
|
BLAKE2b-256 checksum How to use checksums |
b6ce862eb6f41e2c24d73c0a14af83b1fd8839b748f7458cece2873ee05b7c23
|
| 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 Aug 17, 2026.
Transparency logRelease files / timeseries_table_format-0.4.0-cp310-abi3-manylinux_2_28_x86_64.whl
| Download URL | timeseries_table_format-0.4.0-cp310-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 37.3 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1a3e5d590f147518f9f29cf85227c11fa544c11777f7a0799119aa3cf3dec9ae
|
|
BLAKE2b-256 checksum How to use checksums |
22df13cd3e1e8e1837df2e1ad835374018356052ebbd83c5555b0bbb61b1c983
|
| 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 Aug 17, 2026.
Transparency logRelease files / timeseries_table_format-0.4.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | timeseries_table_format-0.4.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 33.2 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
715f9f724d7f7f9c9ea2728c89ee4a2c394ee59bfa9708d2e1fe7fe0659c4c62
|
|
BLAKE2b-256 checksum How to use checksums |
b6e5e96c8d2ecfbdfd63e862852d09fd170524dc7191c5b0d8cbc2edb7eb29fb
|
| 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 Aug 17, 2026.
Transparency logRelease files / timeseries_table_format-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | timeseries_table_format-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 34.9 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
ed80e793fd94f8fce77915bf243e8671ca2cc9a6afc81394da2ce7f0992f10bd
|
|
BLAKE2b-256 checksum How to use checksums |
ef3c8737d3287dd92a7bdaf2abb75f986f227e529717dd75187a0bee1290866c
|
| 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 Aug 17, 2026.
Transparency log