Language Bindings
Beyond the CLI and server, patinaDB is reachable from Python, an MCP server for AI agents, and a graph browser / admin UI.
Python (patinadb)
A native Python extension (PyO3). The API is shaped for a data-science workflow: open a database, bulk-load a DataFrame, query it, run a graph algorithm, and get results back as ordinary Python objects.
Install the wheel provided with your distribution (or from your organization’s package index):
python -m venv .venv && . .venv/bin/activate
pip install patinadb-<version>-<platform>.whl
Open, query, close
The class is Database, opened via Database.open(path):
from patinadb import Database
db = Database.open("./mygraph") # opens or creates an embedded db
db.query("CREATE (n:Person {name: 'Ada', age: 36})")
# query_rows → list[dict], keyed by RETURN column (the pandas-friendly path)
rows = db.query_rows("MATCH (n:Person) RETURN n.name AS name, n.age AS age")
# rows == [{"name": "Ada", "age": 36}]
db.close()
query() returns patinaDB’s column-oriented QueryResult shape;
query_rows() returns row-oriented list[dict] with native Python values
(a node/relationship cell becomes a nested dict). Both accept named parameters:
db.query_with_params(
"MATCH (n:Person) WHERE n.name = $name RETURN n", {"name": "Ada"}
)
db.query_rows("MATCH (n:Person) WHERE n.age > $min RETURN n.name", {"min": 30})
Bulk-load a DataFrame → graph
bulk_load_nodes / bulk_load_edges take a list of dicts — exactly what
DataFrame.to_dict("records") produces — and stream them through the engine’s
batched, durable bulk-insert path. Loaded data is visible at HEAD (it bypasses
the versioning layer, so it is not in AS OF time-travel reads).
import pandas as pd
people = pd.read_parquet("people.parquet") # columns: user_id, name, age
follows = pd.read_parquet("follows.parquet") # columns: src, dst
# Nodes: deterministic UUIDs from a business key (id_key) → idempotent re-loads.
db.bulk_load_nodes("Person", people.to_dict("records"), id_key="user_id")
# Edges: 'from'/'to' are node UUIDs. Database.node_id() reproduces the id.
edges = [
{"from": Database.node_id("Person", str(r.src)),
"to": Database.node_id("Person", str(r.dst))}
for r in follows.itertuples()
]
db.bulk_load_edges("FOLLOWS", edges)
Graph algorithms
Thin wrappers over the built-in patinadb.algo.* procedures, returning the
same list[dict] shape:
ranked = db.page_rank("Person", "FOLLOWS") # -> list of {node, score}
components = db.wcc("Person", "FOLLOWS") # -> list of {node, componentId}
degrees = db.degree("Person", "FOLLOWS", "in") # -> list of {node, score}
You can also call any procedure (full-text, vector, stats, export, …) directly
through query / query_rows via CALL.
API surface
| Method | Purpose |
|---|---|
Database.open(path) | Open or create an embedded database. |
db.query(cypher) | Run a query → column-oriented QueryResult. |
db.query_with_params(cypher, params) | Same, with a dict of named parameters. |
db.query_rows(cypher, params=None) | Row-oriented list[dict] (DataFrame-friendly). |
db.bulk_load_nodes(label, rows, id_key=None) | Bulk-load node records; deterministic UUIDs from id_key. |
db.bulk_load_edges(rel_type, edges) | Bulk-load edge records (from/to UUIDs). |
Database.node_id(label, key) | Reproduce the deterministic UUID for a business key. |
db.page_rank(label=None, rel_type=None, iterations=20, damping_factor=0.85) | PageRank → list[{node, score}]. |
db.wcc(label=None, rel_type=None) | Weakly-connected components → list[{node, componentId}]. |
db.degree(label=None, rel_type=None, direction="both") | Degree centrality → list[{node, score}]. |
db.close() | Close and flush. |
The bindings talk to an embedded database directly. Time-travel/engram methods (
AS OF, diffs) are reachable from the CLI, MCP, and the server; exposing them on theDatabasepyclass is planned.
MCP server (patinadb-mcp)
A Model Context Protocol server (stdio JSON-RPC) that exposes a database to an AI agent as tools. Beyond the core graph operations it surfaces patinaDB’s differentiators — time-travel, diffs, provenance, statistics, and graph algorithms:
| Tool | Purpose |
|---|---|
schema | Inspect the graph schema (labels, types, properties). |
cypher | Run a read query. |
write | Run a write query. |
begin | Start a pending engram (transaction). |
commit | Commit the pending engram. |
rollback | Discard the pending engram. |
snapshot | Capture/inspect a full-graph snapshot. |
engrams | List the versioned commit history. |
diff | Single-engram git-style diff. |
diff_range | Structural diff between two engrams. |
time_travel_query | Read the graph AS OF a past engram. |
provenance | Read the Anamnesis PROV projection (<db>__anamnesis). |
statistics | Data-shape catalog (CALL patinadb.stats) before querying. |
graph_algorithm | Run pageRank / wcc / degree / betweenness / closeness / triangleCount / labelPropagation. |
spatial_search | Nearest nodes to a point within a radius (geo metres or cartesian). |
constraints | List (SHOW CONSTRAINTS) or create a unique / exists / node_key constraint. |
advise | EXPLAIN plan + index suggestions (CALL patinadb.advisor), read-only. |
cache_stats | Per-level cache hit-rate / bytes (CALL patinadb.cache.stats). |
import_csv | Batched, deterministic-UUID CSV bulk-load (HEAD-only). |
This lets an agent query, mutate, inspect history, time-travel, and analyze a
patinaDB graph through structured tool calls. Full-text and vector search are
reachable through the cypher tool via CALL.
spatial_search takes a label, a point-typed property, and a query point as
either lat+lon (WGS-84, distance in metres via Haversine) or x+y
(cartesian, Euclidean), plus an optional radius and limit; it returns the
nearest nodes first with their distance. A CREATE POINT INDEX on the property
turns it into a fast curve-range seek. Point/Polygon cells render as compact
JSON so an agent can parse the coordinates. Label/property names are validated as
plain identifiers before inlining, so the generated Cypher can’t be injected.
Security note. The MCP server has no read-only mode and no import sandbox today — every tool listed above, including
write(arbitrary CREATE/SET/DELETE/DETACH DELETE) andimport_csv(bulk-load from any filesystem path the process can read), is available to whatever agent holds the stdio connection. If you point an LLM agent atpatinadb-mcp, treat it like handing that agent a database admin credential — there is no--read-onlyflag or import-directory allowlist to fall back on yet. Run it only against a database (and from an environment) you’re comfortable letting an agent mutate freely, and keep prompt-injected query results in mind: a malicious result returned bycyphercould steer a subsequentwritecall.
Graph browser & admin UI (patinadb-browser)
patinadb-browser is not a WASM module — it is a single axum HTTP binary
that serves one self-contained index.html (inline CSS/JS) and connects to a
database in one of three modes:
| Mode | Flag | Data plane |
|---|---|---|
| File | <db-path> | opens an embedded Dataset directly |
| Server | --server http://host:8080 | proxies a patinadb-raft node’s REST API |
| Bolt | --bolt bolt://host:7687 | native Bolt client (neo4rs) |
# File mode (embedded — no server needed)
patinadb-browser /path/to/mydb
# Server mode (against a running patinadb-raft node)
patinadb-browser --server http://127.0.0.1:8080 --user admin --password … --database default
# Bolt mode
patinadb-browser --bolt bolt://127.0.0.1:7687 --user neo4j --password …
Then open http://127.0.0.1:4200. It renders a force-directed graph view with a
Neo4j-style node detail panel and neighbor expansion, an engram/diff timeline,
graph-algorithm visualization, and (in server mode) a cluster/metrics/cache
admin dashboard. See Graph Browser & Admin UI for details.