Skip to content

Python Versions

Robin Kooyman edited this page Nov 11, 2026 · 3 revisions

Each commit makes a new version of the table. The old versions stay on disk, so you can read them, undo a change and free the disk space when you no longer need them.

Example files and table for this page

The examples use small NetCDF files of CTD profiles, see Getting started. Run this block first. It writes the files and makes the table profiles.lance from the six files in profiles/.

from pathlib import Path

import numpy as np
import pandas as pd
import xarray as xr


def write_profile(path, station, cruise, lat, lon, levels, time, temp_type="float64", extra=None):
    """Write one CTD profile as a NetCDF file."""
    depth = np.arange(levels, dtype="float64") * 10.0
    ds = xr.Dataset(
        {
            "TEMP": ("depth", (15.0 - depth / 100.0).astype(temp_type), {"units": "degree_Celsius"}),
            "PSAL": ("depth", 35.0 + depth / 1000.0, {"units": "psu"}),
            "LATITUDE": ((), lat),
            "LONGITUDE": ((), lon),
            "TIME": ((), pd.Timestamp(time)),
        },
        coords={"depth": ("depth", depth, {"units": "m"})},
        attrs={"station": station, "cruise": cruise},
    )
    if extra:
        ds["DOXY"] = ("depth", np.full(levels, 250.0), {"units": "umol/kg"})
    Path(path).parent.mkdir(exist_ok=True)
    ds.to_netcdf(path)


# Six profiles of two cruises.
write_profile("profiles/A1.nc", "A1", "cruise-1", 52.5, 4.0, 50, "2026-06-01")
write_profile("profiles/A2.nc", "A2", "cruise-1", 53.0, 4.5, 80, "2026-06-02")
write_profile("profiles/A3.nc", "A3", "cruise-1", 60.0, 2.0, 120, "2026-06-03")
write_profile("profiles/B1.nc", "B1", "cruise-2", 54.0, 3.0, 60, "2026-07-01")
write_profile("profiles/B2.nc", "B2", "cruise-2", 55.5, 3.5, 70, "2026-07-02")
write_profile("profiles/B3.nc", "B3", "cruise-2", 57.0, 5.0, 90, "2026-07-03")
# Three new profiles of a third cruise.
write_profile("new/C1.nc", "C1", "cruise-3", 58.0, 6.0, 40, "2026-08-01")
write_profile("new/C2.nc", "C2", "cruise-3", 58.5, 6.5, 45, "2026-08-02")
write_profile("new/C3.nc", "C3", "cruise-3", 59.0, 7.0, 50, "2026-08-03")
# Corrected profiles, with other depth levels.
write_profile("corrected/A1.nc", "A1", "cruise-1", 52.5, 4.0, 55, "2026-06-01")
write_profile("corrected/A2.nc", "A2", "cruise-1", 53.0, 4.5, 90, "2026-06-02")
write_profile("corrected/B1.nc", "B1", "cruise-2", 54.0, 3.0, 65, "2026-07-01")
write_profile("corrected/B2.nc", "B2", "cruise-2", 55.5, 3.5, 75, "2026-07-02")
# Files that differ from the others.
write_profile("other/int_temp.nc", "D1", "cruise-4", 50.0, 1.0, 10, "2026-09-01", temp_type="int32")
write_profile("other/with_doxy.nc", "E1", "cruise-5", 51.0, 2.0, 20, "2026-10-01", extra=True)
write_profile("other/float32_temp.nc", "F1", "cruise-6", 52.0, 2.0, 10, "2026-11-01", temp_type="float32")

import lance_nd


def stations(table):
    """The station of each dataset in the table, in table order."""
    return [batch.to_xarray().attrs["station"] for batch in table.scan(project=[])]


with lance_nd.create("profiles.lance", mode="overwrite", stats=True) as writer:
    with xr.open_dataset("profiles/A1.nc") as ds:
        writer.write(ds)
    with xr.open_dataset("profiles/A2.nc") as ds:
        writer.write(ds)
    with xr.open_dataset("profiles/A3.nc") as ds:
        writer.write(ds)
    with xr.open_dataset("profiles/B1.nc") as ds:
        writer.write(ds)
    with xr.open_dataset("profiles/B2.nc") as ds:
        writer.write(ds)
    with xr.open_dataset("profiles/B3.nc") as ds:
        writer.write(ds)
table = lance_nd.open("profiles.lance")

List the versions

versions() gives a (version, timestamp) pair for each version that still has its files, oldest first.

table.delete("__global_attrs__.station = 'A1'")
table.delete("__global_attrs__.cruise = 'cruise-2'")
print([version for version, _ in table.versions()])
print(table.version, stations(table))
[1, 2, 3]
3 ['A2', 'A3']

The timestamps are datetime objects in UTC.

Read an earlier version

checkout() gives the table at an earlier version.

old = table.checkout(1)
print(old.version, stations(old))
1 ['A1', 'A2', 'A3', 'B1', 'B2', 'B3']
  • Scan the old table as any other table.
  • Use a table from checkout() to read. A delete() on it changes the latest version, not the old version.

Undo a change

restore() makes the content of an earlier version the latest version, in a new commit.

table.restore(1)
print(table.version, stations(table))
4 ['A1', 'A2', 'A3', 'B1', 'B2', 'B3']
  • restore() removes no version. Versions 2 and 3 stay, so you can also restore them.
  • Readers of the table see the restored content after the commit.

Free disk space

Deletes, overwrites and compactions free no disk space, because the old versions still use the old files. cleanup_old_versions() removes the files of old versions.

from datetime import timedelta

removed = table.cleanup_old_versions(older_than=timedelta(0))
print(removed["old_versions"], removed["bytes_removed"] > 0)
print([version for version, _ in table.versions()])
3 True
[4]
  • older_than is the minimum age of a version to remove. The default is 14 days. The latest version always stays.
  • timedelta(0) keeps only the latest version. Use it only when no other process reads the table: a reader of a removed version fails.
  • A removed version cannot be read or restored.
  • When the commits only added data, the old versions use no files of their own, so a cleanup frees little space.

Which operations make a version

Operation New versions Files
create() or append(), then finish() 1 Adds fragments.
finish() without datasets on an append 0 None.
delete() 1, also when nothing matches Adds deletion files.
compact() 2, or 0 without work Adds fragments. The old files stay.
restore() 1 None.
widen() 1 Adds files for the variable.
cleanup_old_versions() 0 Removes the files of old versions.

Clone this wiki locally