Tables and schemas
Create, describe, change, edit and restore Nexus tables from Python.
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 filesFind 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
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)docis required, on the table and on each column: one line saying what it holds. A column can also set"required": True.identifier_columnsname the columns that identify a row (its key).expectationsare 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_bysuggests 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,rowis the row as you read it andvaluesthe new values, written as text. If any value inrowno longer matches what is stored, nothing is saved andConflictErrornames each row that changed. write_idnames the write (8 to 64 letters, digits,-or_). After a timeout, resend with the samewrite_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 timedelete() 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
| Exception | Cause | What to do |
|---|---|---|
NotFoundError | No such table in this schema, or you cannot reach it. | Check the name and the schema=. |
RefusedError | Your key cannot make this change. | Use a key with schema-change access. |
ConflictError | A row changed since you read it, or the table is changing. | Read again, then retry. |
TableRuleError | The change breaks a rule of the table; the message names it. | Fix the change; never resend it unchanged. |
Next steps
- Ingest data to fill tables from files.
- Run SQL to query them.
- Jobs and batch jobs to change many rows with SQL.