Skip to content

Append files incrementally

Use this pattern when new Parquet files arrive on a schedule and each file covers a new time window.

Before you start, complete Create, append, and query your first table.

Open or create the table

Create the table on the first run. Open it on later runs:

from pathlib import Path

import pyarrow as pa
import pyarrow.parquet as pq
import timeseries_table_format as ttf

TABLE_ROOT = Path("prices_table")


def open_or_create_table() -> ttf.TimeSeriesTable:
    if TABLE_ROOT.exists():
        return ttf.TimeSeriesTable.open(str(TABLE_ROOT))

    return ttf.TimeSeriesTable.create(
        table_root=str(TABLE_ROOT),
        index_column="ts",
        index_type="timestamp",
        index_granularity="1h",
        entity_columns=["symbol"],
    )

Checking the path first avoids hiding permission errors, damaged metadata, or other failures from TimeSeriesTable.open(...). An existing invalid table root should fail instead of being silently replaced.

Append the new files

Append files in a stable order so job logs and table versions are predictable:

def ingest_files(new_files: list[Path]) -> None:
    table = open_or_create_table()

    for segment in sorted(new_files):
        parquet_file = pq.ParquetFile(segment)
        reader = pa.RecordBatchReader.from_batches(
            parquet_file.schema_arrow,
            parquet_file.iter_batches(),
        )
        report = table.append(reader)
        print(
            f"appended {segment.name} as {report.segment_path} "
            f"at table version {report.committed_version}"
        )

If a file conflicts with committed coverage, append(...) raises IndexIntervalOverlapError and leaves that append uncommitted. Duplicate identity and interval pairs inside one file raise DuplicateIndexIntervalError before commit.

Do not blindly ignore overlap errors

An overlap can mean a fully duplicated file or a file containing both old and new time windows. Skipping every overlap could silently discard new data. Inspect the input and resolve the overlap before continuing.

Verify the result

Query a summary after the append loop:

def print_summary() -> None:
    session = ttf.Session()
    session.register_tstable("prices", str(TABLE_ROOT))
    print(
        session.sql(
            "SELECT min(ts), max(ts), count(*) AS row_count FROM prices"
        )
    )

Call ingest_files(...) and then print_summary() from your scheduled job.

For overlap behavior and recovery details, see Index granularity and conflicts and Exceptions.