Klarety

Tables and schemas

Create, describe, change, edit and restore Nexus tables from Python.

For devs

stores your data in , in the open format, grouped into . The default schema always exists and is used unless you pass schema=. This page covers nexus.tables and nexus.schemas.

Before you start

  • Reading tables needs read access. Creating tables and changing columns needs a key whose Access includes schema changes.
  • Every example below assumes nexus = Nexus.connect().

Schemas

for schema in nexus.schemas.list():   # the default schema first
    print(schema)

nexus.schemas.create("finance")
print(nexus.schemas.describe("finance"))
nexus.schemas.delete("finance")       # only when it holds no tables and no files

Find and describe tables

for table in nexus.tables.list(search="order"):
    print(table["name"], table["column_count"], table["doc"])

details = nexus.tables.describe("orders")

list() follows every page for you. Each entry has name, schema, owner, doc, column_count, about, made_by and mine. Pass a project ID as made_by= to filter.

Create a table

create_orders.py
from klarety import Nexus, RefusedError

with Nexus.connect() as nexus:
    try:
        created = nexus.tables.create(
            "orders",
            [
                {"name": "order_id", "type": "string", "doc": "Order number."},
                {"name": "customer", "type": "string", "doc": "Customer name."},
                {"name": "amount", "type": "double", "doc": "Order total in US dollars."},
                {"name": "ordered_at", "type": "timestamp", "doc": "When the order was placed."},
            ],
            doc="One row per customer order.",
            identifier_columns=["order_id"],
            expectations=[{"column": "amount", "rule": "range", "minimum": 0}],
        )
    except RefusedError as refused:
        raise SystemExit(f"Could not create the table: {refused}")
    print(created)
  • doc is required, on the table and on each column: one line saying what it holds. A column can also set "required": True.
  • identifier_columns name the columns that identify a row (its key).
  • expectations are rules every load checks; breaking rows are set aside under the rule's name.
  • relationships (for example {"columns": ["customer"], "references": "customers", "referenced_columns": ["id"]}) help Nexus lay out both tables. They are not enforced on write.
  • Nexus chooses the physical layout and reports it. cluster_by suggests the columns you filter on most; pin_layout() stops the layout changing as the table grows.

Usually you do not need create(): ingesting a file creates its table.

Describe what a table means

nexus.tables.describe_meaning(
    "orders", grain="one order", time_column="ordered_at", status="ready"
)
nexus.tables.set_column_doc("orders", "amount", "Order total in US dollars, tax included.")

status is draft, ready or deprecated. Only the values you pass change. People and AI read these descriptions to understand your data.

Read rows

for row in nexus.tables.rows("orders", where=["customer:eq:Acme"], order_by="ordered_at"):
    print(row)

rows() pages as you loop. A where filter is column:op:value, where op is eq, ne, gt, ge, lt, le, in, contains, is_null or not_null. To fetch many rows by key in one read, use in: "order_id:in:A1,A2,A3". For anything more, use SQL.

Edit a few rows

edit_rows() adds, updates and deletes up to 500 rows in one commit:

from klarety import ConflictError

try:
    nexus.tables.edit_rows(
        "orders",
        added=[{"order_id": "A9", "customer": "Acme", "amount": "120.00"}],
        updated=[{"row": {"order_id": "A1", "amount": "80.00"}, "values": {"amount": "85.00"}}],
        write_id="fix-a1-2026-11-10",
    )
except ConflictError as changed:
    print("A row changed since you read it:", changed)
  • In updated, row is the row as you read it and values the new values, written as text. If any value in row no longer matches what is stored, nothing is saved and ConflictError names each row that changed.
  • write_id names the write (8 to 64 letters, digits, - or _). After a timeout, resend with the same write_id: a landed write is answered as saved, never made twice.
  • For more than 500 rows, load a file with mode="upsert" instead.

Change columns

nexus.tables.add_column("orders", {"name": "channel", "type": "string", "doc": "Where the order came from."})
nexus.tables.change_columns("orders", rename={"channel": "sales_channel"}, retype={"amount": "decimal(12,2)"})
nexus.tables.change_columns("orders", remove=["sales_channel"])
nexus.tables.restore_column("orders", "sales_channel")

All changes in one change_columns() call happen together. A removed column is kept until it is purged, so restore_column() can bring it back.

History, rollback and restore

Every change creates a snapshot of the table.

history = nexus.tables.snapshots("orders")
nexus.tables.rollback("orders", snapshot_id=1234567890)

new_rows = nexus.tables.changes("orders", since=0)   # pass back "through" next time

delete() removes a table, and it stays restorable for your plan's undelete window:

nexus.tables.delete("orders")
print(nexus.tables.deleted())
nexus.tables.restore("orders")

Erasing is permanent

nexus.tables.erase("orders", "customer = 'Acme'") removes the bytes of the matching rows, not just the rows, and returns a receipt. There is no undo: a rollback to before an erasure is refused, so erased rows never come back.

Check quality and activity

print(nexus.tables.quality("orders"))
print(nexus.tables.authors("orders", days=7))   # who added and changed rows, day by day
print(nexus.tables.lineage("orders"))

What can go wrong

ExceptionCauseWhat to do
NotFoundErrorNo such table in this schema, or you cannot reach it.Check the name and the schema=.
RefusedErrorYour key cannot make this change.Use a key with schema-change access.
ConflictErrorA row changed since you read it, or the table is changing.Read again, then retry.
TableRuleErrorThe change breaks a rule of the table; the message names it.Fix the change; never resend it unchanged.

Next steps

On this page