Skip to main content

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().

Vector and full-text indexes

Published wheels include the same persistent Vamana ANN and BM25 index features as the server image. Their DDL works directly through the embedded client:

client.cypher(
    "CREATE VECTOR INDEX doc_emb ON :Doc(embedding) "
    "METRIC cosine DIMENSION 384"
)
client.cypher("CREATE FULLTEXT INDEX doc_text ON :Doc(title, body)")
client.cypher(
    "CREATE INDEX IF NOT EXISTS FOR (d:Doc) ON (d.tenant_id)"
)
client.cypher(
    "CREATE INDEX IF NOT EXISTS FOR (d:Doc) ON (d.vigente)"
)

Index bodies are built by an authoritative compaction. compact() prepares the merge and the .vg / .ft bodies without holding the foreground writer mutex, then briefly re-acquires it to install the new manifest:

client.flush()
report = client.compact()
print(report["applied"], report["l0_before"], report["l0_after"])

await client.acompact() is the coroutine equivalent. Until a body is materialized, and whenever freshness cannot be proven, vector and BM25 queries automatically use their exact flat fallback.

search.vector can apply metadata eligibility before the returned k, so a selective slice does not get truncated out of an unfiltered top-k:

hits = client.cypher(
    """
    CALL search.vector({
      label: 'Doc', property: 'embedding', query: $q, k: 10,
      filter: { tenant_id: $tenant, vigente: true }
    })
    YIELD node, score
    RETURN node, score
    """,
    params={"q": [0.1] * 384, "tenant": "laboral"},
).rows()

A scalar filter value means equality, a list means IN, and different keys AND-combine. Indexed String/Boolean equality and IN clauses use native vector-ordinal postings; unsupported or range clauses retain the exact widening/flat-scan fallback. The index changes the cost, not the result.

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 over LocalFileSystem; Create-only pointer CAS uses O_CREAT|O_EXCL 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 uses the same Create-only versioned pointer family as the cloud backends, with local O_CREAT|O_EXCL providing PUT-if-absent. 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"
    "&region=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"
    "&region=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"
    "&region=us-east-1"
)

You need allow_http=true because LocalStack doesn't serve TLS by default.

Scope

  • Five storage backends: memory://, file://, s3://, gs://, az://. All four non-memory backends share the same Create-only versioned manifest-pointer protocol: each p<N>.json is written once with PUT-if-absent (If-None-Match: * on object stores, O_CREAT|O_EXCL locally), the highest N is current, and the same single-writer-per-namespace epoch fencing applies.
  • A synchronous Python API plus async coroutine APIs (acypher, acompact). Under the hood every call drives a tokio runtime owned by the Client; the first call per process pays the bootstrap cost.
  • Persistent Vamana vector and BM25 full-text indexes, including their Cypher DDL and off-writer-lock compaction lifecycle.
  • The same SST plus bloom cache the Rust read path uses (SstCache) is exposed through client.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 project README for the engine's surface and the RFCs in docs/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

Run every command below from the repository root.

  1. Set the same X.Y.Z in the root Cargo.toml (workspace.package.version and every internal workspace dependency pin) and in crates/namidb-py/pyproject.toml. Every crate, including namidb-py, inherits its Rust version from the root workspace manifest.
  2. Refresh Cargo.lock and update CHANGELOG.md.
  3. Validate the complete release metadata before creating an immutable tag:
    VERSION=X.Y.Z
    python scripts/check-release-metadata.py \
      --tag "v$VERSION" --tag-kind engine
    python scripts/check-release-metadata.py \
      --tag "py-v$VERSION" --tag-kind python
    
  4. Commit and push the release commit and wait for ci and python-wheels to pass on main. Before creating an immutable tag, manually dispatch release-binaries.yml (full OS/architecture release dry-run) and docker-image.yml with an empty version (native amd64/arm64 build-check) on that same commit, and wait for both to pass.
  5. Create both annotated tags on that exact commit:
    git tag -a "v$VERSION" -m "NamiDB $VERSION"
    git tag -a "py-v$VERSION" -m "namidb Python $VERSION"
    test "$(git rev-parse "v$VERSION^{commit}")" = \
         "$(git rev-parse "py-v$VERSION^{commit}")"
    git push --atomic origin "v$VERSION" "py-v$VERSION"
    
  6. python-wheels.yml builds 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 v* tag creates the GitHub Release, prebuilt binaries, and container images. The py-v* tag publishes the Python distribution to PyPI. The release workflows reject either tag unless it exactly matches the Cargo, lockfile, Python version, dated changelog entry, and bundled license.

Metadata

Release files for namidb 2.6.12

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for namidb 2.6.12
File Size Uploaded
namidb-2.6.12.tar.gz 1.8 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for namidb 2.6.12
File
namidb-2.6.12-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
namidb-2.6.12-cp39-abi3-manylinux_2_28_x86_64.whl CPython 3.9 abi3 Linux glibc 2.28+ x86-64 Details
namidb-2.6.12-cp39-abi3-manylinux_2_28_aarch64.whl CPython 3.9 abi3 Linux glibc 2.28+ ARM64 Details
namidb-2.6.12-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details

Total release size: 40.8 MB

Release files / namidb-2.6.12.tar.gz

Download URL namidb-2.6.12.tar.gz
Size 1.8 MB
Tags Source
SHA-256 checksum
How to use checksums
cac83a8c798d6b690749f461494ca584e2abd7b113a4d74fb2bafda094778c20
BLAKE2b-256 checksum
How to use checksums
849843d113f97a15fcc2ae77c28b51d46ddfffa93c5cc0e3aba76d79117220e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / namidb-2.6.12-cp39-abi3-win_amd64.whl

Download URL namidb-2.6.12-cp39-abi3-win_amd64.whl
Size 9.7 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
193ce2a51fd24aaf8634c15d5670ddfb5dd6c464542a00cdced1bc025827df28
BLAKE2b-256 checksum
How to use checksums
5a061addd590cb70133d36786073d4fd4718a6be5a399a1e52cec39337a0748a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / namidb-2.6.12-cp39-abi3-manylinux_2_28_x86_64.whl

Download URL namidb-2.6.12-cp39-abi3-manylinux_2_28_x86_64.whl
Size 10.6 MB
Tags CPython 3.9 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
e12f98b62f78948f92844b757b845b364de90747e28db4406d2bc36aa7c444a8
BLAKE2b-256 checksum
How to use checksums
fc7ee9eee6d6b70dbf2af93dede70b05221beb53d6600ee08a2b5ad6ab6c34ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / namidb-2.6.12-cp39-abi3-manylinux_2_28_aarch64.whl

Download URL namidb-2.6.12-cp39-abi3-manylinux_2_28_aarch64.whl
Size 9.8 MB
Tags CPython 3.9 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
d8e7e959af94c5a7782f08d4a559bbf5ce9ce29aa16b01d0fffacf7e6a48f034
BLAKE2b-256 checksum
How to use checksums
96ef29406497d4ebe1022ca24c335d7970666f5b198094694800e4e9b7d591f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / namidb-2.6.12-cp39-abi3-macosx_11_0_arm64.whl

Download URL namidb-2.6.12-cp39-abi3-macosx_11_0_arm64.whl
Size 8.9 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
fa848d3a533d735b6725176e1e9d3a4386c1b96e26e4a4261623a0775de78dbc
BLAKE2b-256 checksum
How to use checksums
fb6ead95b87f0522ad6f2a8c6abc94ea240ae7425a6838046a2ea6ab2674d71b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release history Release notifications | RSS feed

2.6.19

5 release files

2.6.18

5 release files

2.6.17

5 release files

2.6.16

5 release files

2.6.15

5 release files

2.6.14

5 release files

2.6.13

5 release files

This release

2.6.12 This release

5 release files

2.6.11

5 release files

2.6.10

5 release files

2.6.9

5 release files

2.6.8

5 release files

2.6.7

5 release files

2.6.6

5 release files

2.6.5

5 release files

2.6.4

5 release files

2.6.3

5 release files

2.6.2

5 release files

2.6.1

5 release files

2.6.0

5 release files

2.5.1

5 release files

2.5.0

5 release files

2.4.1

5 release files

2.4.0

5 release files

2.3.0

5 release files

2.2.1

5 release files

2.2.0

5 release files

2.1.4

5 release files

2.1.3

5 release files

2.1.2

5 release files

2.1.1

5 release files

2.1.0

5 release files

2.0.6

5 release files

2.0.5

5 release files

2.0.4

5 release files

2.0.3

5 release files

2.0.2

5 release files

2.0.1

5 release files

2.0.0

5 release files

1.5.0

5 release files

1.4.0

5 release files

1.3.0

5 release files

1.2.0

5 release files

1.1.0

5 release files

1.0.0

5 release files

0.18.1

5 release files

0.18.0

5 release files

0.17.1

5 release files

0.17.0

5 release files

0.16.0

5 release files

0.15.0

5 release files

0.10.0

5 release files

0.9.0

5 release files

0.8.0

5 release files

0.7.0

5 release files

0.6.0

5 release files

0.5.1

5 release files

0.4.1

5 release files

0.4.0

5 release files

0.3.0

5 release files

0.2.1

5 release files

0.1.0

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page