Skip to main content

Persistent tree database for hierarchical metadata and file-backed datasets

Project description

LOTDB

Light Object Tree DB.

LOTDB is a persistent tree for organizing computation results, metadata, and large data payloads with very little boilerplate.

Core idea

LOTDB is built around two node types:

  • BaseNode for generic hierarchy and metadata
  • DataNode for hierarchy plus data-oriented read/write behavior

Each node can:

  • contain child nodes
  • store arbitrary attributes
  • be persisted with ZODB

Large payloads can be stored through pluggable backends such as:

  • blob
  • zarr

This makes LOTDB useful for pipelines where you want to:

  • branch variants of computations
  • cache intermediate results on disk
  • keep metadata close to the computation tree
  • avoid manually managing lots of folder/path boilerplate

Installation

Install from PyPI:

pip install lotdb

With optional extras:

pip install "lotdb[io,measurements]"

Mental model

  • BaseNode = generic tree node
  • DataNode = specialized node for data payloads
  • LOTDB = persistent container for the root tree

The recommended API is node-centered.

Quick start

from lotdb import BaseNode

root = BaseNode(key="dataset")
node = root.get_node_path(["speaker_01", "session_a", "clip_001"])
node.set_attribute("label", "hello")

print(node.get_attribute("label"))

Persistent usage

from lotdb import LOTDB

db = LOTDB(path="./data", name="lotdb.fs", new=True)
root = db.open_connection()

root.get_node_path(["speaker_01", "clip_001"]).set_attribute("duration", 1.23)

db.commit()
db.close_connection()
db.close()

Recommended data workflow

Use get_data_node(...) when the final node should own data behavior.

import numpy as np
from lotdb import LOTDB

db = LOTDB(path="./data", name="lotdb.fs", new=True)
root = db.open_connection()

capture_1 = root.get_data_node(
    ["sensor_01", "capture_0001"],
    samplerate_hz=1000,
    backend="zarr",   # or "blob"
    data_attribute="imu",
)

capture_2 = root.get_data_node(
    ["sensor_01", "capture_0002"],
    samplerate_hz=1000,
    backend="zarr",
    data_attribute="imu",
)

data = np.random.randn(2000, 6).astype("float32")
capture_1.write_data(data, database=db)
capture_2.write_data(data * 0.5, database=db)

# same node, second payload
capture_1.write_data(np.random.randn(2000, 2).astype("float32"), database=db, data_attribute="control")

window = capture_1.read_seconds(1.0, 2.0)

for block in capture_1.iter_data_blocks(0.5, block_unit="seconds"):
    process(block)

# first iteration layer under the root
for node in root.iterate_tree_level(1):
    print(node.key)

# first iteration layer under sensor_01
sensor_node = root.get_node_path(["sensor_01"])
for node in sensor_node.iterate_tree_level(1):
    print(node.key)

# all leaves below sensor_01
for leaf in sensor_node.iterate_tree_leaves():
    print("leaf", leaf.key)

# buffered iteration over leaves
for batch in sensor_node.iterate_tree_crone_buffered(buffer_size=2):
    print([node.key for node in batch])

db.commit()
db.close_connection()
db.close()

This is the main API LOTDB is optimized for.

Generic nodes vs data nodes

Use BaseNode when you only need:

  • hierarchy
  • metadata
  • relationships

Use DataNode when you want the node itself to own:

  • write_data(...)
  • replace_data(...)
  • append_data(...)
  • has_data()
  • delete_data()
  • read_data(...)
  • read_seconds(...)
  • iter_data_blocks(...)

get_node(...) remains the generic retriever. get_data_node(...) ensures the final node is a DataNode.

Lower-level data API

The lower-level API still exists when you want direct control:

import numpy as np
from lotdb import DataReader, DataWriter

DataWriter.write_array(
    node,
    np.random.randn(1000, 4).astype("float32"),
    samplerate_hz=500,
    backend="blob",
    data_attribute="signal",
)

signal = DataReader.read_interval(node, "signal", 100, 200)

write_data(...) supports explicit payload policies:

  • if_exists="replace" (default)
  • if_exists="error"
  • if_exists="append"

This is especially useful when one DataNode stores multiple payloads such as:

  • audio
  • control

Example:

capture.write_data(audio, database=db, data_attribute="audio", if_exists="replace")
capture.write_data(control, database=db, data_attribute="control", if_exists="replace")

capture.append_data(more_audio, database=db, data_attribute="audio")

if capture.has_data("control"):
    capture.delete_data("control")

File ingestion

External files are now treated as ingestion inputs.

DataWriter.attach_file(...) or DataNode.attach_file(...) loads the file, converts it into the configured backend representation, and stores source metadata on the node attributes.

After that, reads happen only through the backend:

data_node.attach_file("./capture.npy", database=db, data_attribute="imu")
payload = data_node.read_data()

Typical formats currently supported:

  • wav
  • npy
  • csv
  • txt
  • png / jpg / jpeg
  • raw bytes for unknown formats

Typical source attributes written onto the node:

  • _source_filepath
  • _source_format
  • _source_filename
  • _source_samplerate_hz for wav files

The older direct file-writing helper methods were removed to keep the public API focused on backend-managed data and ingestion.

Why LOTDB instead of only HDF5/Zarr?

LOTDB is not just a container for arrays.

It is useful when you want:

  • a persistent computation tree
  • easy branching of variants
  • metadata attached directly to pipeline nodes
  • cached intermediate states across runs
  • backend flexibility for how payloads are stored

Development

  • source package: src/lotdb
  • tests: tests/
  • API notes: docs/API.md

Development install:

pip install -e .

With extras:

pip install -e .[io,measurements]

Publishing

PyPI publishing is configured through GitHub Actions trusted publishing.

Typical release flow:

  1. bump version in pyproject.toml
  2. push to main
  3. create a GitHub release
  4. GitHub publishes to PyPI

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lotdb-1.1.3.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lotdb-1.1.3-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file lotdb-1.1.3.tar.gz.

File metadata

  • Download URL: lotdb-1.1.3.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lotdb-1.1.3.tar.gz
Algorithm Hash digest
SHA256 f62be735374483302794501a7828f634139e2d63e8b740b1a995da5c86fde8bc
MD5 03965c36b57b7b24cc531cd1f3e6c8f0
BLAKE2b-256 ebf7b551ecbaa26c47fe1bbb2a33890cc8d8c2f19f086ace197d839d91b0d6a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for lotdb-1.1.3.tar.gz:

Publisher: publish.yml on HTill/LOTDB

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lotdb-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: lotdb-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lotdb-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5440a1249c77cbad4e7ed6a219b5886b8eccde6038887a5fc34e8f9b82b7a4ba
MD5 de45039f0af93d39d6a902f5e5cd0c6e
BLAKE2b-256 b3bb55aab3c81c8fe889b3c97804c016bc2d48ef3de7d444f5f0c352ad5a9d72

See more details on using hashes here.

Provenance

The following attestation bundles were made for lotdb-1.1.3-py3-none-any.whl:

Publisher: publish.yml on HTill/LOTDB

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page