Skip to main content

lzdb

A schema-emergent database layer for Python and PostgreSQL.

lzdb (Lazy Database) is a datastore that builds and evolves its schema automatically as data is inserted. Instead of designing tables upfront, lzdb lets structure emerge naturally from the data itself.

This approach is part of the Lazy Data Modeling philosophy:

  • store first, understand later
  • let relationships appear through use
  • avoid premature schema design
  • embrace uncertainty during exploration

lzdb is ideal for research workflows, prototypes, and dynamic data environments where the schema cannot be known in advance.


Key Features

  • Automatic schema evolution
  • Virtual primary keys inferred from inserted data
  • Cross-references implemented as foreign keys
  • N-to-N relationships via a dedicated system table
  • Automatic table creation (lzdb__N)
  • Lazy field addition (ALTER TABLE ADD COLUMN)
  • Convenience API (lzitem, lzitems, lzdict, etc.)

Installation

Build using standard Python packaging tools:

pip install build

Then:

rm -rf dist
python3 -m build

Install the wheel:

pip install dist/*.whl

Force-reinstall for updates:

pip install dist/*.whl --force-reinstall --no-deps

Yes, using wildcards. Told ya I'm lazy.


Foreword: Initializing LZDB

import psycopg as pg
from lzdb import *

LZDB.traceon = True  # Optional

dbms = LZDB(
    pg.connect(
        dbname='test',
        host='localhost'
    ),
    traceon=True
)

Table Naming

lzdb creates tables named:

lzdb__1
lzdb__2
lzdb__3
...

The lzdb inventory table stores:

  • the virtual primary key (unique fields)
  • the collection identifier
  • schema metadata

Each table has:

  • a real primary key: id
  • a virtual primary key: a UNIQUE constraint over inferred fields

Important Note

Nothing is persisted until you call:

dbms.commit()

(An autocommit may come later.)


The lzdb_links Table

Relationships between objects are stored in a dedicated system table:

create table if not exists lzdb_links(
    src_collection integer not null,
    src_id integer not null,

    dst_collection integer not null,
    dst_id integer not null,

    reltype smallint not null default 0,

    unique(
        src_collection,
        src_id,
        dst_collection,
        dst_id,
        reltype
    )
)

This table supports:

  • directed relationships
  • undirected relationships
  • N-to-N associations
  • cross-collection links

It is created automatically during dbms.commit().


Instantiating a New Item

item1 = dbms.newItem(
    param='2004',
    starttime='03-jan-2000:00:00:00',
    endtime='04-jan-2000:00:00:00'
)

This produces:

CREATE TABLE IF NOT EXISTS public.lzdb__1
(
    id integer NOT NULL DEFAULT nextval('lzdb__1_id_seq'::regclass),
    endtime character varying,
    param character varying,
    starttime character varying,
    CONSTRAINT lzdb__1_pkey PRIMARY KEY (id),
    CONSTRAINT lzdb__1_endtime_param_starttime_key UNIQUE (endtime, param, starttime)
)

And inserts into lzdb:

  • id: 1
  • ukeys: endtime,param,starttime

Any item with the same virtual primary key goes into the same table.


Cross-References Between Items

item2 = dbms.newItem(refers=item1)

This creates a second table:

CREATE TABLE IF NOT EXISTS public.lzdb__2
(
    id integer NOT NULL DEFAULT nextval('lzdb__2_id_seq'::regclass),
    refers integer,
    CONSTRAINT lzdb__2_pkey PRIMARY KEY (id),
    CONSTRAINT lzdb__2_refers_key UNIQUE (refers),
    CONSTRAINT lzdb__2_refers_fkey FOREIGN KEY (refers)
        REFERENCES public.lzdb__1 (id)
)

Inserted record:

  • id: 1
  • refers: 1

Cross-Reference vs Relationship

Cross-reference

item2 = lzitem(refers=item1)

Characteristics:

  • stored as a foreign key
  • modifies the schema
  • part of the object’s definition

Relationship

item1.link(item2)

Characteristics:

  • stored in lzdb_links
  • supports N-to-N
  • does not modify schema
  • ideal for arbitrary associations

Rule of thumb: Use cross-references for structural relationships. Use link() for semantic relationships.


N-to-N Relationships

Example:

items = [
    lzitem(param='2004', starttime='03-jan-2000:00:00:00', endtime='04-jan-2000:00:00:00'),
    lzitem(param='2005', starttime='03-jan-2000:00:00:00', endtime='04-jan-2000:00:00:00'),
    lzitem(param='2006', starttime='03-jan-2000:00:00:00', endtime='04-jan-2000:00:00:00')
]

sat = lzitem(name='sat1')

sat.link(items)

dbms.commit()

Logical relationships:

sat1 --> 2004
sat1 --> 2005
sat1 --> 2006

Relationship Types

LZDB.REL_DIRECTED   = 0
LZDB.REL_UNDIRECTED = 1

Undirected relationships are stored in both directions.

Retrieving Linked Items

for item in dbms.linkedItems(sat):
    print(item['param'])

Output:

2004
2005
2006

Adding Fields to an Item

Two syntaxes:

Dict-style

item2['clusters'] = [1,2,3]
item2['freqmap'] = [4,5,6]

Method-style

item2.set(
    clusters=[1,2,3],
    freqmap=[4,5,6]
)

lzdb automatically performs:

ALTER TABLE lzdb__2 ADD COLUMN clusters character varying;
ALTER TABLE lzdb__2 ADD COLUMN freqmap character varying;

Record becomes:

  • id: 1
  • refers: 1
  • clusters: [1,2,3]
  • freqmap: [4,5,6]

Updates simply modify the row.


Laziness Helpers: expose()

lzdb installs convenience functions:

  • lzitem → newItem
  • lzc → collections
  • lzcnames → collectionNames
  • lzitems → items

Example:

item1 = lzitem(
    param='2004',
    starttime='03-jan-2000:00:00:00',
    endtime='04-jan-2000:00:00:00'
)

Parquet Loader: lzdict

dd = lzdict()
mydata = dd['PQTFILE']

This loads any file matching *PQTFILE* from data/.

>>> dd.keys()
dict_keys(['PQTFILE'])

Pretty-print helper:

pp = pprint.PrettyPrinter().pprint

Example:

>>> items = dbms.items(param='2004')
>>> pp(items)

Why Lazy Data Modeling?

For the full conceptual foundation behind lzdb, see Why Lazy Data Modeling?.


License

MIT License Copyright (c) 2026

Metadata

Release files for lzdb2 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lzdb2 2.0.0
File Size Uploaded
lzdb2-2.0.0.tar.gz 27.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lzdb2 2.0.0
File Interpreter ABI Platform
lzdb2-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 50.7 kB

Release files / lzdb2-2.0.0.tar.gz

Download URL lzdb2-2.0.0.tar.gz
Size 27.9 kB
Tags Source
SHA-256 checksum
How to use checksums
327b3882dc770d96035c02a973404ef8b48d6a0e0100f2a7ca56068fa85b8b4a
BLAKE2b-256 checksum
How to use checksums
5e5e76ed263979b8b02b38b187fb736c74293522ecf445e1c663f1aa060c39b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / lzdb2-2.0.0-py3-none-any.whl

Download URL lzdb2-2.0.0-py3-none-any.whl
Size 22.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d2dc7a8809ae7cc048d45fe470d2d8bfdfbfb5a1b49dd70b6457c46badb8dccd
BLAKE2b-256 checksum
How to use checksums
f4c977dcb88e0222a0294c4b3a67d442d35a83efb06b49e7b478e41b0e3c459d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

2.1.0

2 release files

2.0.2

2 release files

2.0.1

2 release files

This release

2.0.0 This release

2 release files

1.3.0

2 release files

1.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page