Cloud-native graph database on object storage (Python bindings)
Project description
namidb (Python bindings)
Python wrapper around the NamiDB storage and query engine. The heavy
lifting is Rust, bridged with pyo3 and built with
maturin.
Install
pip install namidb # released wheel, Python >= 3.9, abi3
pip install 'namidb[pandas]' # + DataFrame interop
pip install 'namidb[polars]' # + polars interop
Wheels go out for Linux (x86_64, aarch64), macOS (arm64), and Windows
(x86_64) from the python-wheels.yml workflow on every py-v* tag.
pyarrow >= 14 is a hard transitive dependency. Intel macOS falls back
to the sdist, which is slower to install but behaves the same at runtime.
Build from source
pip install maturin
cd crates/namidb-py
maturin develop --release --extras test
Once maturin develop finishes, namidb imports from any Python >= 3.9
environment:
import uuid
import namidb
client = namidb.Client("memory://acme")
alice = str(uuid.uuid7())
bob = str(uuid.uuid7())
client.upsert_node("Person", alice, {"name": "Alice", "age": 30})
client.upsert_node("Person", bob, {"name": "Bob"})
client.upsert_edge("KNOWS", alice, bob, {"since": 2020})
client.commit()
client.flush()
print(client.lookup_node("Person", alice))
# {'id': '...', 'label': 'Person', 'lsn': 1, 'schema_version': 0,
# 'properties': {'name': 'Alice', 'age': 30}}
print(client.scan_label("Person"))
print(client.out_edges("KNOWS", alice))
print(client.cache_stats())
Cypher queries
Client.cypher(query, params=None) runs a Cypher query against the
current namespace and hands back a QueryResult:
client.cypher("CREATE (a:Person {name: 'Alice', age: 30})")
client.cypher("CREATE (a:Person {name: 'Bob', age: 25})")
client.commit()
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) # {'name': 'Alice', 'age': 30}
One thing to keep straight: Cypher writes (CREATE / SET / DELETE / MERGE
/ REMOVE) are durably committed (WAL append plus manifest CAS) before
cypher() returns, because the executor calls commit_batch() at the
end of every write plan. You still want to call client.flush() now and
then to push the memtable into L0 SSTs. That's different from the
upsert_node / upsert_edge / tombstone_* API, which stages mutations
and waits for an explicit client.commit().
Async API
The same surface is available as a coroutine through Client.acypher,
for asyncio / FastAPI / aiohttp:
import asyncio
import namidb
async def main() -> None:
client = namidb.Client("memory://acme")
await client.acypher("CREATE (p:Person {name: 'Alice'})")
client.commit()
result = await client.acypher(
"MATCH (p:Person {name: $name}) RETURN p.name AS name",
params={"name": "Alice"},
)
print(result.rows())
asyncio.run(main())
acypher rides the pyo3-async-runtimes tokio bridge. Every call runs
on the same multi-threaded tokio runtime that backs the synchronous API,
so mixing the two from one Client is fine.
Type mapping (Cypher and Python)
Both cypher parameters and QueryResult.rows() follow the same
mapping:
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, microseconds) |
datetime.datetime UTC |
Node(NodeValue) |
{"_kind": "node", "id", "label", "properties"} |
Rel(RelValue) |
{"_kind": "rel", "edge_type", "src", "dst", "properties"} |
Path |
list[Node|Rel] alternating |
bool is checked before int on purpose, so that Python True /
False don't quietly round-trip as Integer(1) / Integer(0).
Bulk inserts
Client.merge_nodes and Client.merge_edges batch a lot of writes
behind a single tokio-runtime plus mutex round-trip. They're the right
ingestion path once you have thousands of rows, since Cypher CREATE
parses, plans and executes once per call:
import uuid
import namidb
client = namidb.Client("memory://acme")
# Bulk insert: each row needs an "id" UUID string + arbitrary properties.
client.merge_nodes(
"Person",
[{"id": str(uuid.uuid4()), "name": f"p{i}", "age": 20 + i} for i in range(10_000)],
)
# Edges: each row needs "src" + "dst" UUIDs.
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
merge_nodes / merge_edges stage into the current batch (same
lifecycle as upsert_*), so call client.commit() to make the
mutations durable.
Arrow / pandas / polars output
pyarrow >= 14 is a hard dependency. Every QueryResult can
materialise as a pyarrow.Table, and the pandas / polars conversions
just delegate to that.
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)
pl_df = result.to_polars() # polars.DataFrame (needs polars)
Column order follows the RETURN projection from the parsed plan, not
the runtime row's BTreeMap ordering, so RETURN p.name AS name, p.age AS age always gives you columns ["name", "age"] even when nothing
matches.
pandas and polars are optional extras:
pip install 'namidb[pandas]'
pip install 'namidb[polars]'
Calling to_polars() without the polars extra raises a clear
ImportError that points at the install command.
For label-wide scans you can skip the Cypher round-trip:
table = client.scan_label_arrow("Person")
# Columns: id, label, lsn, schema_version, then the union of property
# keys across the scanned views (missing keys filled with None).
Storage backends
| URI scheme | Backend | Status |
|---|---|---|
memory://<ns> |
object_store::memory::InMemory |
Stable. Ephemeral, single-process. |
file:///abs/dir?ns=<ns> (or file://./rel?ns=<ns>) |
NamiDB LocalFileObjectStore (wraps LocalFileSystem and adds manifest CAS via flock + atomic rename) |
Stable. |
s3://<bucket>[/<prefix>]?ns=<ns>... |
object_store::aws::AmazonS3 |
Stable. AWS S3, Cloudflare R2, MinIO, Tigris, LocalStack, any S3-compatible service. |
gs://<bucket>[/<prefix>]?ns=<ns> |
object_store::gcp::GoogleCloudStorage |
Stable. Auth via GOOGLE_APPLICATION_CREDENTIALS or ?service_account=.... |
az://<account>/<container>[/<prefix>]?ns=<ns> |
object_store::azure::MicrosoftAzure |
Stable. Auth via AZURE_STORAGE_* env vars; ?use_emulator=true for Azurite. |
Local filesystem
For development, single-machine deployments, and CI fixtures. Full
manifest CAS via per-namespace flock plus atomic rename, and it passes
the same concurrency test suite as s3://.
import namidb
client = namidb.Client("file:///var/lib/namidb?ns=prod")
# or relative
client = namidb.Client("file://./data?ns=dev")
AWS S3
import namidb
client = namidb.Client(
"s3://my-bucket/data?ns=prod"
"®ion=us-west-2"
)
Credentials come from the standard AWS environment variables
(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN,
AWS_DEFAULT_REGION). A query-string region=... overrides the env.
Cloudflare R2
import namidb
client = namidb.Client(
"s3://my-bucket?ns=prod"
"&endpoint=https://<ACCOUNT_ID>.r2.cloudflarestorage.com"
"®ion=auto"
)
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY should hold the R2 API
token credentials.
Google Cloud Storage
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/etc/gcs-key.json"
client = namidb.Client("gs://my-bucket/data?ns=prod")
Azure Blob Storage
import os
os.environ["AZURE_STORAGE_ACCOUNT_NAME"] = "myacct"
os.environ["AZURE_STORAGE_ACCESS_KEY"] = "..."
client = namidb.Client("az://myacct/mycontainer?ns=prod")
LocalStack (local persistent storage)
docker run -p 4566:4566 -e SERVICES=s3 localstack/localstack
aws --endpoint-url=http://localhost:4566 s3 mb s3://namidb-dev
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
client = namidb.Client(
"s3://namidb-dev?ns=local"
"&endpoint=http://localhost:4566"
"&allow_http=true"
"®ion=us-east-1"
)
You need allow_http=true because LocalStack doesn't serve TLS by
default.
Scope (v0)
- Six storage backends:
memory://,file://,s3://,gs://,az://. All five non-memory backends share the same manifest CAS protocol (If-Matchon object stores,flockplus atomic rename on the filesystem) and the same single-writer-per-namespace epoch fencing. - A synchronous Python API plus an async coroutine API (
acypher). Under the hood every call drives a tokio runtime owned by theClient; the first call per process pays the bootstrap cost. - The same SST plus bloom cache the Rust read path uses
(
SstCache) is exposed throughclient.cache_stats(), so application dashboards can graph the hit rate. - Cypher coverage matches the Rust engine: LDBC SNB Interactive IC01
through IC12, with factorized execution toggled by
NAMIDB_FACTORIZE=1. See the projectREADMEfor the engine's surface and the RFCs indocs/rfc/for the design details.
Running the integration test (optional)
The pytest suite under tests/ ships a LocalStack round-trip test that
is @pytest.mark.skipif-guarded on the NAMIDB_TEST_LOCALSTACK_BUCKET
env var. To turn it on:
docker run -p 4566:4566 -e SERVICES=s3 localstack/localstack &
aws --endpoint-url=http://localhost:4566 s3 mb s3://namidb-it
export NAMIDB_TEST_LOCALSTACK_BUCKET=namidb-it
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test
.venv/bin/pytest tests/test_uri.py::test_s3_localstack_round_trip -v
Releasing to PyPI
- Bump
versionincrates/namidb-py/pyproject.tomlandcrates/namidb-py/Cargo.toml(they have to match). - Update
CHANGELOG.md(or this README's release notes section). - Commit, then tag and push:
git tag py-v0.2.0 git push origin py-v0.2.0
python-wheels.ymlbuilds 4 wheels (Linux x86_64/aarch64, macOS arm64, Windows x86_64) plus the sdist, smoke-tests one wheel on Python 3.9 and 3.13, then publishes to PyPI via OIDC trusted publishing (set up once per account at https://pypi.org/manage/account/publishing/).
The py-v* tag prefix keeps Python releases separate from any future
v* tags that mark engine or crate releases.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file namidb-1.4.0.tar.gz.
File metadata
- Download URL: namidb-1.4.0.tar.gz
- Upload date:
- Size: 813.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e919e17abebdf4ac7b3ad5d868da1295bc8f0c3bcd2c5f886377940de13d899c
|
|
| MD5 |
13382cf21165ad4bd80460e8b73c151c
|
|
| BLAKE2b-256 |
083b516f5b32a8d31899e89ad3865e1501bc1fe8781beb581acc6cac8f59c764
|
Provenance
The following attestation bundles were made for namidb-1.4.0.tar.gz:
Publisher:
python-wheels.yml on namidb/namidb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namidb-1.4.0.tar.gz -
Subject digest:
e919e17abebdf4ac7b3ad5d868da1295bc8f0c3bcd2c5f886377940de13d899c - Sigstore transparency entry: 1923248734
- Sigstore integration time:
-
Permalink:
namidb/namidb@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Branch / Tag:
refs/tags/py-v1.4.0 - Owner: https://github.com/namidb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-wheels.yml@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Trigger Event:
push
-
Statement type:
File details
Details for the file namidb-1.4.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: namidb-1.4.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 7.1 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
649ee29eabbff2d74d838515c9164ad194cdc7fcf741498f04330b1e94aa57a2
|
|
| MD5 |
44dd1b2f24b1e0da027bd04c8d3b9b2c
|
|
| BLAKE2b-256 |
98fcb7c4fa8bca72df025ae1cec3c20cb54bb6daa8034ffe8f455a74c8ec76af
|
Provenance
The following attestation bundles were made for namidb-1.4.0-cp39-abi3-win_amd64.whl:
Publisher:
python-wheels.yml on namidb/namidb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namidb-1.4.0-cp39-abi3-win_amd64.whl -
Subject digest:
649ee29eabbff2d74d838515c9164ad194cdc7fcf741498f04330b1e94aa57a2 - Sigstore transparency entry: 1923249082
- Sigstore integration time:
-
Permalink:
namidb/namidb@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Branch / Tag:
refs/tags/py-v1.4.0 - Owner: https://github.com/namidb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-wheels.yml@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Trigger Event:
push
-
Statement type:
File details
Details for the file namidb-1.4.0-cp39-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: namidb-1.4.0-cp39-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 7.8 MB
- Tags: CPython 3.9+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4d711c741092e2e91439664b87d471f6f484a0b84f84deaef5774729f084923
|
|
| MD5 |
7932aef100db26dd929b8fa49ff3d78c
|
|
| BLAKE2b-256 |
334ec0d964c169e2ef8af07ae076002957e5a1d5dd735e552debd8fc56757e8a
|
Provenance
The following attestation bundles were made for namidb-1.4.0-cp39-abi3-manylinux_2_28_x86_64.whl:
Publisher:
python-wheels.yml on namidb/namidb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namidb-1.4.0-cp39-abi3-manylinux_2_28_x86_64.whl -
Subject digest:
a4d711c741092e2e91439664b87d471f6f484a0b84f84deaef5774729f084923 - Sigstore transparency entry: 1923248891
- Sigstore integration time:
-
Permalink:
namidb/namidb@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Branch / Tag:
refs/tags/py-v1.4.0 - Owner: https://github.com/namidb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-wheels.yml@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Trigger Event:
push
-
Statement type:
File details
Details for the file namidb-1.4.0-cp39-abi3-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: namidb-1.4.0-cp39-abi3-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 7.2 MB
- Tags: CPython 3.9+, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea9e76fa1e01e74f06edc737bcca87dfe911d4f545178b8a1afdea2ab7e618a8
|
|
| MD5 |
18f487d4c78877350a0ba1445dcc9ef6
|
|
| BLAKE2b-256 |
0426893c9ea2498c3025a5816f9e1b6bbe9a058bdd9aadd46e7e2418de4f510a
|
Provenance
The following attestation bundles were made for namidb-1.4.0-cp39-abi3-manylinux_2_28_aarch64.whl:
Publisher:
python-wheels.yml on namidb/namidb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namidb-1.4.0-cp39-abi3-manylinux_2_28_aarch64.whl -
Subject digest:
ea9e76fa1e01e74f06edc737bcca87dfe911d4f545178b8a1afdea2ab7e618a8 - Sigstore transparency entry: 1923249241
- Sigstore integration time:
-
Permalink:
namidb/namidb@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Branch / Tag:
refs/tags/py-v1.4.0 - Owner: https://github.com/namidb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-wheels.yml@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Trigger Event:
push
-
Statement type:
File details
Details for the file namidb-1.4.0-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: namidb-1.4.0-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 6.6 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ece75a3247f17951dc5b05b33d0597760d63bf9f7763ddf7b288e60dd9332680
|
|
| MD5 |
7b83f42ccda02c46e714c1d95e9d6311
|
|
| BLAKE2b-256 |
220a206f31fd6bd6ab5974280663a015ec89107c0adafd05876441ef7e7edd4f
|
Provenance
The following attestation bundles were made for namidb-1.4.0-cp39-abi3-macosx_11_0_arm64.whl:
Publisher:
python-wheels.yml on namidb/namidb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
namidb-1.4.0-cp39-abi3-macosx_11_0_arm64.whl -
Subject digest:
ece75a3247f17951dc5b05b33d0597760d63bf9f7763ddf7b288e60dd9332680 - Sigstore transparency entry: 1923248814
- Sigstore integration time:
-
Permalink:
namidb/namidb@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Branch / Tag:
refs/tags/py-v1.4.0 - Owner: https://github.com/namidb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-wheels.yml@891118e9cecbc49736fc415ecac3c8f6e21611ef -
Trigger Event:
push
-
Statement type: