gnomon-postgres (Python)
Branches, time travel and merges for PostgreSQL apps on the gnomon extension: the SDK (runs for
agents and tasks, review, the core), psycopg helpers, SQLAlchemy and Django, and pytest fixtures
with snapshots. import gnomon.
from gnomon import Gnomon
gn = Gnomon(app="dbname=shop user=shop_app", runs="dbname=shop user=shop_runs")
with gn.start_run("Refund order 3", actor="support-bot") as run: # a branch for this task
run.sql("update orders set status = 'refunded' where id = 3")
if run.summary()["checks"]["passed"]: run.merge()
else: run.submit("Refund order 3") # a change request insteadLeaving the with block without merge() or submit() discards the branch. run.url(base) and
run.connection(base) give a tool a connection string, or a psycopg connection, pinned to the run
(a login of the run's own, confined to its branch, dropped when the run ends). With asyncpg:
AsyncGnomon(app=asyncpg_source(pool), runs=asyncpg_source(runs_pool)) (from gnomon.asyncpg import asyncpg_source; the asyncpg extra). AsyncGnomon is the
same API for psycopg.AsyncConnection (the sync client is generated from it, so they cannot
drift). gn.requests, gn.branches, history, blame, revert, status() and doctor() are
the review and core layers. Tools for agent frameworks bound to one run:
gnomon.tools.anthropic_tools(run), openai_agents_tools(run), langchain_tools(run),
mcp_handler(run). The two logins, the envelope and the errors are as in the TypeScript SDK
(the TypeScript SDK); both pass the same conformance scenarios
(the specification).
Helpers
import gnomon, psycopg
conn = psycopg.connect(gnomon.branch_dsn("dbname=shop", "feature")) # sessions start on a branch
with gnomon.branch(conn, "feature"): ... # or switch one connection
opts = gnomon.connection_options(admin_conn, "feature") # what the database says the
# branch needs (schema versions, masks)use, begin (one transaction, for poolers), create_branch, create_scratch, drop_branch,
diff, merge. SQLAlchemy: gnomon.sqlalchemy.branch_engine(url, branch) and
attach_branch(engine, get_branch) (each transaction on the branch a callable names). Django:
gnomon.django.BranchMiddleware (a branch per request from X-Gnomon-Branch).
Tests on branches
def test_signup(gnomon_conn, gnomon_snapshot): # a psycopg connection on a fresh scratch branch
signup(gnomon_conn, "a@x.io")
gnomon_snapshot.assert_match(gnomon_conn) # the branch's diff against a stored fileGNOMON_TEST_DSN=... pytest (the plugin loads itself when the package is installed;
--gnomon-update-snapshots records, --gnomon-keep keeps the branches).
gnomon.testing.assert_no_changes and assert_changed check a branch's diff directly.
Install: pip install gnomon-postgres (extras: sqlalchemy, django, test).