Python SDK
namidb is the Python wrapper around the NamiDB storage + query
engine. Backed by Rust via pyo3 and built with
maturin.
Install
pip install namidb # corepip install 'namidb[pandas]' # + DataFrame interoppip install 'namidb[polars]' # + Polars interopPre-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®ion=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)) # 1print(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 asyncioimport 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 RuntimeValue | Python type |
|---|---|
Null | None |
Bool | bool |
Integer | int |
Float | float |
String | str |
Bytes | bytes |
Vector(Vec<f32>) | list[float] |
List | list |
Map | dict[str, ...] |
Date | datetime.date |
DateTime (UTC µs) | datetime.datetime UTC |
Node | {"_kind": "node", "id", "label", "properties"} |
Rel | {"_kind": "rel", "edge_type", "src", "dst", "properties"} |
Path | list[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 uuidimport 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 CASclient.flush() # memtable -> L0 SSTsArrow / 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.Tabledf = 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
pip install maturingit clone https://github.com/namidb/namidb.gitcd namidb/crates/namidb-pymaturin develop --release --extras test