Skip to main content

Cloud-native graph database on object storage — Python bindings

Project description

namidb (Python bindings)

Python wrapper for the NamiDB storage + query engine. Backed by Rust via 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 are published for Linux (x86_64, aarch64), macOS (arm64), and Windows (x86_64) via the python-wheels.yml workflow on every py-v* tag — pyarrow >= 14 is a hard transitive dependency. Intel macOS users fall back to the sdist (slower install, same runtime behaviour).

Build from source

pip install maturin
cd crates/namidb-py
maturin develop --release --extras test

Once maturin develop finishes, the namidb module is importable from any Python ≥ 3.9 environment:

import uuid
import namidb as tg

client = tg.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 returns 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}

Cypher writes (CREATE / SET / DELETE / MERGE / REMOVE) are durably committed (WAL append + manifest CAS) before cypher() returns — the executor calls commit_batch() internally at the end of every write plan. Call client.flush() periodically to push the memtable into L0 SSTs. This is different from the upsert_node / upsert_edge / tombstone_* API, which stages mutations and requires an explicit client.commit().

Async API

The same surface is available as a Python coroutine via Client.acypher for asyncio / FastAPI / aiohttp integration:

import asyncio
import namidb as tg


async def main() -> None:
    client = tg.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 is driven by 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 the same Client is safe.

Type mapping (Cypher ↔ 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 µs) 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 intentionally checked before int so that Python True / False do not silently round-trip as Integer(1) / Integer(0).

Bulk inserts

Client.merge_nodes and Client.merge_edges batch many writes under a single tokio-runtime + mutex round-trip. They are the right ingestion path when you have thousands of rows (Cypher CREATE parses + plans + executes per call):

import uuid
import namidb as tg

client = tg.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_*) — call client.commit() to make the mutations durable.

Arrow / pandas / polars output

pyarrow >= 14 ships as a hard dependency. Every QueryResult can materialise as a pyarrow.Table; pandas / polars conversions delegate to it.

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 yields columns ["name", "age"] even when zero rows match.

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 pointing at the install command.

For label-wide scans you can skip the Cypher round-trip entirely:

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.
s3://<bucket>[/<prefix>]?ns=<ns>... object_store::aws::AmazonS3 Stable. Works against any S3-compatible service.
file://... object_store::local::LocalFileSystem Not supported in v0. Upstream lacks PutMode::Update, required by manifest CAS.
gs://... object_store::gcp Planned.
az://... object_store::azure Planned.

AWS S3

import namidb as tg

client = tg.Client(
    "s3://my-bucket/data?ns=prod"
    "&region=us-west-2"
)

Credentials are read from the standard AWS environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_DEFAULT_REGION). Query-string region=... overrides the env.

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 = tg.Client(
    "s3://namidb-dev?ns=local"
    "&endpoint=http://localhost:4566"
    "&allow_http=true"
    "&region=us-east-1"
)

The allow_http=true flag is required because LocalStack does not serve TLS by default.

Scope (v0)

  • memory:// for single-process testing, s3:// for persistent storage (any S3-compatible service; LocalStack works for local persistence). file:// is intentionally unsupported — see the backends table above.
  • Synchronous Python API + async coroutine API (acypher). Under the hood every call drives a tokio runtime owned by the Client; the first call you make per process pays the bootstrap cost.
  • The same SST + bloom cache used by the Rust read path (SstCache) is exposed via client.cache_stats() so application-level dashboards can graph hit rate.
  • Cypher coverage matches the Rust engine: LDBC SNB Interactive IC01 through IC12, factorized execution toggleable via NAMIDB_FACTORIZE=1. See the project README for the engine's surface and the RFCs in docs/rfc/ for 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 enable it:

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

  1. Bump version in crates/namidb-py/pyproject.toml and crates/namidb-py/Cargo.toml (they must match).
  2. Update CHANGELOG.md (or this README's release notes section).
  3. Commit, then tag and push:
    git tag py-v0.1.0
    git push origin py-v0.1.0
    
  4. python-wheels.yml builds 4 wheels (Linux x86_64/aarch64, macOS arm64, Windows x86_64) + sdist, smoke-tests one wheel on Python 3.9 and 3.13, then publishes to PyPI via OIDC trusted publishing (configured once per account at https://pypi.org/manage/account/publishing/).

py-v* tag prefix keeps Python releases separate from any future v* tags that mark engine / crate releases.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

namidb-0.1.0.tar.gz (383.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

namidb-0.1.0-cp39-abi3-win_amd64.whl (5.5 MB view details)

Uploaded CPython 3.9+Windows x86-64

namidb-0.1.0-cp39-abi3-manylinux_2_28_x86_64.whl (6.0 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.28+ x86-64

namidb-0.1.0-cp39-abi3-manylinux_2_28_aarch64.whl (5.6 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.28+ ARM64

namidb-0.1.0-cp39-abi3-macosx_11_0_arm64.whl (5.1 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

File details

Details for the file namidb-0.1.0.tar.gz.

File metadata

  • Download URL: namidb-0.1.0.tar.gz
  • Upload date:
  • Size: 383.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for namidb-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eefa8962745fe7ca3e43918e2112e9ea5aa133d5b31c04a81cb203a61ebea4c8
MD5 b24af557a8ebe3c88525776cfef7fa57
BLAKE2b-256 71e5547c21a01763d8ef629734fdc46427478f8eeac589c88a5f41976649e401

See more details on using hashes here.

Provenance

The following attestation bundles were made for namidb-0.1.0.tar.gz:

Publisher: python-wheels.yml on namidb/namidb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file namidb-0.1.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: namidb-0.1.0-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 5.5 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

Hashes for namidb-0.1.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 58d75952bf917291c2945f4b660555a12825eceb27c06a9c5b4442fea611bc55
MD5 7a45dbd0e891b416dfa27df2390a4c74
BLAKE2b-256 b851b7703531bdb32d4b58a4cd0b81238d6d5fbb28096267aa9097b58fa11cb0

See more details on using hashes here.

Provenance

The following attestation bundles were made for namidb-0.1.0-cp39-abi3-win_amd64.whl:

Publisher: python-wheels.yml on namidb/namidb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file namidb-0.1.0-cp39-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for namidb-0.1.0-cp39-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b4d19de8de15145d599309331605cd86e71b97f0c8c747cc5dfd901881690803
MD5 a03eabe9e8e30c9c30980a2b1e7a6c78
BLAKE2b-256 b86a981f7220cdce755caa98a249e32ef94fa16e5a95dca7f1caaf18a1b27acf

See more details on using hashes here.

Provenance

The following attestation bundles were made for namidb-0.1.0-cp39-abi3-manylinux_2_28_x86_64.whl:

Publisher: python-wheels.yml on namidb/namidb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file namidb-0.1.0-cp39-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for namidb-0.1.0-cp39-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 03fbc0120c6c32f7bd50a959dd064e680a78bf19dd38857a0873b4dcfb581709
MD5 ae97a007396949ea18ed2190607e0efd
BLAKE2b-256 ac73b0ed0739dbf890f60fe79e15ac54db56c117041c4cd4ee0db8a5d226152e

See more details on using hashes here.

Provenance

The following attestation bundles were made for namidb-0.1.0-cp39-abi3-manylinux_2_28_aarch64.whl:

Publisher: python-wheels.yml on namidb/namidb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file namidb-0.1.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for namidb-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 138c33600d3c5c641d11bdb8e30ff29766a8dbc9a23108bc5c5b0e1055b37e91
MD5 bd3a1a532aa262a48f0c2eb8064facc1
BLAKE2b-256 a296fed401cbdff7235fbce99c5e5cffb9514e26a6acf946eab6cbc499358ae8

See more details on using hashes here.

Provenance

The following attestation bundles were made for namidb-0.1.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: python-wheels.yml on namidb/namidb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page