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:
BaseNodefor generic hierarchy and metadataDataNodefor 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:
blobzarr
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 nodeDataNode= specialized node for data payloadsLOTDB= 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:
audiocontrol
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:
wavnpycsvtxtpng/jpg/jpeg- raw bytes for unknown formats
Typical source attributes written onto the node:
_source_filepath_source_format_source_filename_source_samplerate_hzfor 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:
- bump version in
pyproject.toml - push to
main - create a GitHub release
- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f62be735374483302794501a7828f634139e2d63e8b740b1a995da5c86fde8bc
|
|
| MD5 |
03965c36b57b7b24cc531cd1f3e6c8f0
|
|
| BLAKE2b-256 |
ebf7b551ecbaa26c47fe1bbb2a33890cc8d8c2f19f086ace197d839d91b0d6a2
|
Provenance
The following attestation bundles were made for lotdb-1.1.3.tar.gz:
Publisher:
publish.yml on HTill/LOTDB
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lotdb-1.1.3.tar.gz -
Subject digest:
f62be735374483302794501a7828f634139e2d63e8b740b1a995da5c86fde8bc - Sigstore transparency entry: 1199940620
- Sigstore integration time:
-
Permalink:
HTill/LOTDB@8f41038b70fc29d66b414054be0cc1533e899e69 -
Branch / Tag:
refs/tags/v1.1.3 - Owner: https://github.com/HTill
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8f41038b70fc29d66b414054be0cc1533e899e69 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5440a1249c77cbad4e7ed6a219b5886b8eccde6038887a5fc34e8f9b82b7a4ba
|
|
| MD5 |
de45039f0af93d39d6a902f5e5cd0c6e
|
|
| BLAKE2b-256 |
b3bb55aab3c81c8fe889b3c97804c016bc2d48ef3de7d444f5f0c352ad5a9d72
|
Provenance
The following attestation bundles were made for lotdb-1.1.3-py3-none-any.whl:
Publisher:
publish.yml on HTill/LOTDB
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lotdb-1.1.3-py3-none-any.whl -
Subject digest:
5440a1249c77cbad4e7ed6a219b5886b8eccde6038887a5fc34e8f9b82b7a4ba - Sigstore transparency entry: 1199940656
- Sigstore integration time:
-
Permalink:
HTill/LOTDB@8f41038b70fc29d66b414054be0cc1533e899e69 -
Branch / Tag:
refs/tags/v1.1.3 - Owner: https://github.com/HTill
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8f41038b70fc29d66b414054be0cc1533e899e69 -
Trigger Event:
release
-
Statement type: