Some checks failed
CI / skinny-install (aco) (push) Successful in 1m12s
CI / skinny-install (api) (push) Successful in 30s
CI / skinny-install (bcda) (push) Successful in 36s
CI / skinny-install (bib) (push) Successful in 35s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 41s
CI / skinny-install (cms) (push) Successful in 37s
CI / skinny-install (conf) (push) Successful in 38s
CI / skinny-install (opps) (push) Successful in 33s
CI / skinny-install (perf) (push) Successful in 38s
CI / skinny-install (pfs) (push) Successful in 38s
CI / skinny-install (rex) (push) Successful in 34s
Deploy / build-scan-report (push) Failing after 46s
Infra CI / notebooks (push) Failing after 25s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Failing after 16s
CI / lint-test (push) Failing after 11m2s
Infra CI / mc (push) Successful in 21s
Infra CI / api (push) Successful in 29s
Package Supply Chain / pkg-supply-chain (push) Failing after 41s
Mail: Maddy on DO (corwins.media+Resend, fhirworx.io+Postmark), touchless/stateless/idempotent. Gitea SMTP via env_file. CMS inbox at cmsupdates@mail.fhirworx.io with IMAP→bib poller. Bib: regulations.gov v4 client, Federal Register discovery, 164K comment backfill (running), IMAP email ingest, Zotero sync routing. PRISMA: altcha PoW solver, CrossRef DOI resolution, 83/129 PDFs. Zotero: schema parity, ops module, CLI, fail-fast guard. CI: docs.Dockerfile COPY glob fix (tracks #341). Infra: Gitea+marimo fhirworx themes, IOM/OIG modules.
308 lines
6.6 KiB
Python
308 lines
6.6 KiB
Python
import marimo
|
|
|
|
__generated_with = "0.19.8"
|
|
app = marimo.App(width="medium")
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _():
|
|
import marimo as mo
|
|
|
|
mo.md("""
|
|
# Nessie Tutorial - Git for Your Data Lake
|
|
|
|
Nessie provides Git-like version control for Iceberg tables: branches, commits, merges, and time travel.
|
|
|
|
This notebook walks through the key concepts and operations.
|
|
""")
|
|
return (mo,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _():
|
|
import json
|
|
|
|
import requests
|
|
|
|
from conf import cfg
|
|
|
|
NESSIE_API = f"{cfg.services.nessie}/api/v2"
|
|
return NESSIE_API, json, requests
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 1. Check Nessie Configuration
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, requests):
|
|
config = requests.get(f"{NESSIE_API}/config").json()
|
|
print(f"Nessie API Version: {config['specVersion']}")
|
|
print(f"Default Branch: {config['defaultBranch']}")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 2. List Branches and Tags
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, requests):
|
|
refs = requests.get(f"{NESSIE_API}/trees").json()
|
|
print("References:")
|
|
for _r in refs.get("references", []):
|
|
print(f" {_r['type']:6} {_r['name']:20} @ {_r['hash'][:12]}...")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 3. Get Main Branch Details
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, requests):
|
|
main_resp = requests.get(f"{NESSIE_API}/trees/main").json()
|
|
main_ref = main_resp.get("reference", main_resp)
|
|
print(f"Branch: {main_ref['name']}")
|
|
print(f"Hash: {main_ref['hash']}")
|
|
return (main_ref,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 4. Create an Experiment Branch
|
|
|
|
The v2 API uses:
|
|
- Query params: `name` (new branch) and `type` (branch/tag)
|
|
- Body: Source reference object `{"type": "BRANCH", "name": "main", "hash": "..."}`
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, json, main_ref, requests):
|
|
# Create experiment branch from main
|
|
new_branch = "experiment"
|
|
|
|
resp = requests.post(
|
|
f"{NESSIE_API}/trees?name={new_branch}&type=branch",
|
|
headers={"Content-Type": "application/json"},
|
|
data=json.dumps(
|
|
{"type": "BRANCH", "name": main_ref["name"], "hash": main_ref["hash"]}
|
|
),
|
|
)
|
|
|
|
if resp.status_code == 200:
|
|
print(f"Created branch: {new_branch}")
|
|
print(resp.json())
|
|
elif resp.status_code == 409:
|
|
print(f"Branch '{new_branch}' already exists")
|
|
else:
|
|
print(f"Error {resp.status_code}: {resp.text}")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 5. List Tables on a Branch
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, requests):
|
|
entries = requests.get(f"{NESSIE_API}/trees/main/entries").json()
|
|
print("Tables on main:")
|
|
for _e in entries.get("entries", []):
|
|
_key = ".".join(_e["name"]["elements"])
|
|
print(f" {_e['type']:15} {_key}")
|
|
if not entries.get("entries"):
|
|
print(" (none yet)")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 6. View Commit History
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, requests):
|
|
log = requests.get(f"{NESSIE_API}/trees/main/history").json()
|
|
print("Recent commits:")
|
|
for _e in log.get("logEntries", [])[:5]:
|
|
_c = _e["commitMeta"]
|
|
_author = _c.get("authors", ["?"])[0]
|
|
print(f" {_c['hash'][:12]} {_c.get('message', '')[:40]} ({_author})")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 7. Query with Trino
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _():
|
|
import trino
|
|
|
|
conn = trino.dbapi.connect(
|
|
host="trino",
|
|
port=8080,
|
|
user="kert",
|
|
catalog="iceberg",
|
|
)
|
|
|
|
def query(sql):
|
|
cur = conn.cursor()
|
|
cur.execute(sql)
|
|
return cur.fetchall()
|
|
|
|
print("Connected to Trino")
|
|
return (query,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(query):
|
|
schemas = query("SHOW SCHEMAS IN iceberg")
|
|
print("Schemas:", [s[0] for s in schemas])
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 8. Create Sample Data
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(query):
|
|
query("CREATE SCHEMA IF NOT EXISTS iceberg.tutorial")
|
|
print("Schema ready")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(query):
|
|
query("""
|
|
CREATE TABLE IF NOT EXISTS iceberg.tutorial.events (
|
|
id VARCHAR,
|
|
ts TIMESTAMP(6),
|
|
event VARCHAR
|
|
) WITH (format = 'PARQUET')
|
|
""")
|
|
print("Table ready")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(query):
|
|
query("""
|
|
INSERT INTO iceberg.tutorial.events VALUES
|
|
('e1', CURRENT_TIMESTAMP, 'click'),
|
|
('e2', CURRENT_TIMESTAMP, 'view')
|
|
""")
|
|
print("Data inserted")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(query):
|
|
rows = query("SELECT * FROM iceberg.tutorial.events")
|
|
print("Events:")
|
|
for _r in rows:
|
|
print(f" {_r}")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 9. Branch Operations
|
|
|
|
Query specific branches with `FOR VERSION AS OF 'branch'`
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(query):
|
|
count = query(
|
|
"SELECT count(*) FROM iceberg.tutorial.events FOR VERSION AS OF 'main'"
|
|
)
|
|
print(f"Events on main: {count[0][0]}")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## 10. Delete a Branch
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NESSIE_API, requests):
|
|
def delete_branch(name):
|
|
ref = requests.get(f"{NESSIE_API}/trees/{name}").json()
|
|
_hash = ref.get("reference", ref).get("hash")
|
|
if not _hash:
|
|
print(f"Branch {name} not found")
|
|
return
|
|
resp = requests.delete(
|
|
f"{NESSIE_API}/trees/{name}", headers={"Expected-Hash": _hash}
|
|
)
|
|
if resp.status_code == 204:
|
|
print(f"Deleted: {name}")
|
|
else:
|
|
print(f"Error: {resp.status_code}")
|
|
|
|
# Uncomment to delete:
|
|
# delete_branch("experiment")
|
|
print("delete_branch() ready")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
## Summary
|
|
|
|
| Operation | API |
|
|
|-----------|-----|
|
|
| List refs | `GET /trees` |
|
|
| Get ref | `GET /trees/{name}` |
|
|
| Create branch | `POST /trees?name=X&type=branch` + body |
|
|
| Delete ref | `DELETE /trees/{name}` + Expected-Hash header |
|
|
| List tables | `GET /trees/{ref}/entries` |
|
|
| History | `GET /trees/{ref}/history` |
|
|
""")
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run()
|