Skip to main content

SQL views over CSV files, with safe edits back to text.

Project description

LenzDB

SQL views over CSV files, with safe edits back to text.

LenzDB is a small, Git-native data tool for project data that belongs in plain files. You define SQL views, called lenses, over CSV tables; inspect them from the CLI; export rows for editing; and safely write supported changes back to the source CSV files. Your data stays text, so Git can track ordinary, reviewable diffs.

LenzDB is installed as the Python package lenzdb and used from the command line as lnz. A lens is the saved SQL view concept inside a LenzDB project.

Screen recording 2026-04-25 12.05.35 PM.webm

Why

  • Keep project data in boring, diffable CSV files.
  • Use SQL to shape the views people actually want to inspect and edit.
  • Put schema, relationships, and write policies next to the data.
  • Review data changes with normal Git diffs.
  • Edit a projection and write safe changes back to the source rows.

LenzDB is for small project data, operational notes, lightweight catalogs, curated datasets, and other places where a full database is more ceremony than the job needs.

Install

Install via pipx:

pipx install lenzdb

Or via pip:

pip install lenzdb

Getting started

Example:

# Show usage
lnz --help

cd examples/basic
ls  # all_tasks.sql  open_tasks.sql  projects.csv  tasks.csv

lnz list
lnz view projects
lnz view all_tasks
lnz view all_tasks --distinct project_name
lnz view open_tasks

export LENZDB_EDITOR="code --wait"  # or "vim" or any editor

# make changes in a *view* (or "lens") of the data
lnz edit open_tasks

# upon saving and closing the editor, changes will update in the source CSV file (tasks.csv)
cat tasks.csv

Configuration

Most commands locate a project in this order:

  1. --project PATH
  2. $LENZDB_PROJECT_ROOT
  3. The current working directory

lnz edit chooses an editor in this order:

  1. --editor COMMAND
  2. $LENZDB_EDITOR
  3. $EDITOR

Interactive lnz view --format table chooses a pager in this order:

  1. $LENZDB_PAGER
  2. $PAGER

Table output width uses $LENZDB_COLUMNS, then $COLUMNS, then the terminal width. LENZDB_PAGE_SIZE overrides view.page_size for --page; set it to -1 to require an explicit --page-size.

Example:

export LENZDB_PROJECT_ROOT=examples/basic
export LENZDB_EDITOR=vim
lnz list
lnz edit open_tasks

Project Layout

A minimal project looks like this:

my-project/
  tasks.csv
  projects.csv
  open_tasks.sql

  .lenzdb/
    schema/
      tasks.yaml
      projects.yaml

    policies/
      open_tasks.yaml

Root-level *.csv files are tables. Root-level *.sql files are lenses. Schema and policies live under .lenzdb/. CSV is currently the only supported persisted table data format.

LenzDB also reads managed files from:

.lenzdb/data/*.csv
.lenzdb/lenses/*.sql

Subfolders are ignored unless you register them in .lenzdb/project.yaml:

tables:
  - path: clients/acme/*.csv
    namespace: acme

lenses:
  - path: reports/acme/*.sql
    namespace: acme

view:
  page_size: 100

Folders and globs must specify a namespace. Single registered files may omit one and will use main. view.page_size controls lnz view --page; when omitted, it defaults to 100.

Namespaces

Every table and lens has a namespace. The default is main.

select * from tasks;
select * from main.tasks;
select * from acme.tasks;

Unqualified names work only when they are unambiguous. If both main.tasks and acme.tasks exist, tasks is rejected and LenzDB asks you to be precise.

Usage

Run the bundled example:

lnz check --project examples/basic
lnz view open_tasks --project examples/basic
lnz view tasks --project examples/basic
lnz view tasks --project examples/basic --columns id,title,status
lnz view tasks --project examples/basic --filter "status = 'todo'"
lnz view tasks --project examples/basic --order status,-title --limit 20
lnz view tasks --project examples/basic --page 2
lnz view tasks --project examples/basic --describe
lnz view tasks --project examples/basic --count
lnz view open_tasks --project examples/basic --sql "select title from resource where status = 'doing'"
lnz list --project examples/basic
lnz list --project examples/basic --check
lnz explain open_tasks --project examples/basic

view, explain, diff, plan, apply, and edit accept either a lens or a table name. CSV tables behave like they have an implicit identity lens, so tasks.csv is roughly select * from tasks. Table and lens names must be distinct.

lnz view has convenience flags for quick inspection. --columns chooses a comma-separated set of output columns, --filter accepts a SQL WHERE fragment, and --order accepts comma-separated columns with a - prefix for descending sorts. --page uses the project page size. --limit and --offset control the returned row range. --sql runs a SQL query where the selected table or lens is available as resource. --describe returns the final output shape instead of rows.

Export a lens, edit it, and preview the writeback plan:

lnz view open_tasks --project examples/basic --format csv > /tmp/open_tasks.csv
$EDITOR /tmp/open_tasks.csv
lnz diff open_tasks /tmp/open_tasks.csv --project examples/basic
lnz plan open_tasks /tmp/open_tasks.csv --project examples/basic

The same flow works for raw CSV tables:

lnz view tasks --project examples/basic --format csv > /tmp/tasks.csv
$EDITOR /tmp/tasks.csv
lnz plan tasks /tmp/tasks.csv --project examples/basic
lnz apply tasks /tmp/tasks.csv --project examples/basic

Apply the changes:

lnz apply open_tasks /tmp/open_tasks.csv --project examples/basic

Or let LenzDB open the editor for you:

LENZDB_EDITOR=vim lnz edit open_tasks --project examples/basic
LENZDB_EDITOR=vim lnz edit open_tasks --project examples/basic --columns id,title
LENZDB_EDITOR=vim lnz edit tasks --project examples/basic --filter "status = 'doing'"

apply and edit do not ask a final y/N question. Keep project files under version control so data changes can be reviewed and reverted normally.

lnz edit accepts the identity-preserving view flags --columns, --filter, --order, --limit, --offset, --page, and --page-size. LenzDB treats those as a temporary edit view and rejects it before opening the editor if it cannot map edited rows back to source CSV rows.

If edit fails after the editor opens, LenzDB preserves the edited CSV in .lenzdb/recovery/. The next lnz edit RESOURCE resumes the newest recovery file for that resource; pass --discard-recovery to start from current data instead. A successful edit clears the recovery files for that resource.

Writeback uses CSV snapshots only: lnz edit opens a CSV file, and lnz diff, lnz plan, and lnz apply read edited CSV files.

Output Formats

lnz view and lnz list support these output formats:

lnz view open_tasks --format table
lnz view open_tasks --format markdown
lnz view open_tasks --format csv
lnz view open_tasks --format tsv
lnz view open_tasks --format json
lnz view open_tasks --format ndjson
lnz view open_tasks --format yaml
lnz view open_tasks --format html

These are output formats only; they do not change persisted table files or edit snapshot formats.

Schema Sketch

table: tasks
primary_key: id

columns:
  id:
    type: string
    immutable: true
  title:
    type: string
  status:
    type: enum
    values: [todo, doing, done]
  project_id:
    type: ref
    table: projects

For composite primary keys, use a list:

table: memberships
primary_key: [org_id, user_id]

Lens Sketch

select
  t.id,
  t.title,
  t.status,
  p.name as project_name
from tasks as t
join projects as p on p.id = t.project_id
where t.status != 'done'
order by t.id

Policy Sketch

lens: open_tasks
primary_table: tasks
primary_key: id

editable:
  title: tasks.title
  status: tasks.status

references:
  project_name:
    display: projects.name
    write_to: tasks.project_id
    lookup:
      table: projects
      match: name
      create_if_missing: true

Development

pip install -e ".[dev]"
python -m pytest -q
python -m ruff check .

License

MIT.

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

lenzdb-0.1.3.tar.gz (39.3 kB view details)

Uploaded Source

Built Distribution

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

lenzdb-0.1.3-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file lenzdb-0.1.3.tar.gz.

File metadata

  • Download URL: lenzdb-0.1.3.tar.gz
  • Upload date:
  • Size: 39.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.20

File hashes

Hashes for lenzdb-0.1.3.tar.gz
Algorithm Hash digest
SHA256 cb43188d6fd9b494dbcae8c49ab37a095c970dde328ec255234b6d93d3f68aba
MD5 d2e6d32a2c1d7105161d55a69a7e85ef
BLAKE2b-256 2d2c9bb3ab9ec757b8d61a3637bed0fec07220b828b63806d5573f5577128df1

See more details on using hashes here.

File details

Details for the file lenzdb-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: lenzdb-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 35.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.20

File hashes

Hashes for lenzdb-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d74095649df9ecc113555f0c0fa1f24b40004389a89147599f8db95c3c6265c3
MD5 36be866551f01404aecdb905f58f711f
BLAKE2b-256 de6f319da990015b4424778ce68e92a3d24d8f93120121b7d8b05aa7fadf8435

See more details on using hashes here.

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