Skip to content

Python SDK

namidb is the Python wrapper around the NamiDB storage + query engine. Backed by Rust via pyo3 and built with maturin.

Install

Terminal window
pip install namidb # core
pip install 'namidb[pandas]' # + DataFrame interop
pip install 'namidb[polars]' # + Polars interop

Pre-built abi3 wheels for Python ≥ 3.9 on Linux (x86_64 + aarch64), macOS (arm64), and Windows (x86_64). Intel macOS falls back to sdist. pyarrow >= 14 is a hard transitive dependency.

Open a namespace

import namidb as tg
client = tg.Client("s3://my-bucket?ns=prod&region=us-east-1")

All six URI schemes are supported: memory://, file://, s3://, gs://, az://.

Cypher

Client.cypher(query, params=None) runs a query and returns a QueryResult:

client.cypher("CREATE (a:Person {name: 'Alice', age: 30})")
client.cypher("CREATE (a:Person {name: 'Bob', age: 25})")
result = client.cypher(
"MATCH (p:Person) WHERE p.age > $min RETURN p.name AS name, p.age AS age",
params={"min": 26},
)
print(result.columns) # ['name', 'age']
print(len(result)) # 1
print(result.first()) # {'name': 'Alice', 'age': 30}
for row in result.rows():
print(row)

Async API

The same surface is available as a coroutine via Client.acypher:

import asyncio
import namidb as tg
async def main() -> None:
client = tg.Client("memory://acme")
await client.acypher("CREATE (p:Person {name: 'Alice'})")
result = await client.acypher(
"MATCH (p:Person {name: $name}) RETURN p.name AS name",
params={"name": "Alice"},
)
print(result.rows())
asyncio.run(main())

Driven by the pyo3-async-runtimes tokio bridge — every call runs on the same multi-threaded tokio runtime that backs the synchronous API. Mixing sync + async from the same Client is safe.

Type mapping (Cypher ↔ Python)

Cypher RuntimeValuePython type
NullNone
Boolbool
Integerint
Floatfloat
Stringstr
Bytesbytes
Vector(Vec<f32>)list[float]
Listlist
Mapdict[str, ...]
Datedatetime.date
DateTime (UTC µs)datetime.datetime UTC
Node{"_kind": "node", "id", "label", "properties"}
Rel{"_kind": "rel", "edge_type", "src", "dst", "properties"}
Pathlist[Node|Rel] alternating

bool is intentionally checked before int so True / False don’t round-trip as Integer(1) / Integer(0).

Bulk inserts

For thousands of rows, prefer merge_nodes / merge_edges:

import uuid
import namidb as tg
client = tg.Client("memory://acme")
client.merge_nodes(
"Person",
[{"id": str(uuid.uuid4()), "name": f"p{i}", "age": 20 + i} for i in range(10_000)],
)
client.merge_edges(
"KNOWS",
[
{"src": "uuid-a", "dst": "uuid-b", "since": 2020},
{"src": "uuid-b", "dst": "uuid-c", "since": 2021},
],
)
client.commit() # WAL + manifest CAS
client.flush() # memtable -> L0 SSTs

Arrow / pandas / polars output

result = client.cypher(
"MATCH (p:Person) RETURN p.name AS name, p.age AS age ORDER BY p.age DESC"
)
table = result.to_arrow() # pyarrow.Table
df = result.to_pandas() # pandas.DataFrame (needs pandas extra)
pl_df = result.to_polars() # polars.DataFrame (needs polars extra)

Column order follows the RETURN projection from the parsed plan, so RETURN p.name AS name, p.age AS age always yields columns ["name", "age"] even when zero rows match.

Label-wide scans without the Cypher round-trip:

table = client.scan_label_arrow("Person")

Cache stats

print(client.cache_stats())
# {"adjacency": {"hits": ..., "misses": ..., "bytes": ...}, ...}

Storage backends

See Operations / Storage backends for the full credential matrix.

Build from source

Terminal window
pip install maturin
git clone https://github.com/namidb/namidb.git
cd namidb/crates/namidb-py
maturin develop --release --extras test