LatticeDB
Python bindings for LatticeDB, an embedded single-file graph database with native vector and BM25 full-text search.
Repository Health
Technical Analysis
LatticeDB’s Python package wraps a Zig-built embedded graph database in a ctypes binding, giving Python code a single Database/Transaction API for traversing relationships, running HNSW vector similarity search, and running BM25 full-text search over the same on-disk file. There is no server process to run: the library loads a native liblattice shared object (bundled in the wheel, or discoverable via LATTICE_LIB_PATH/LATTICE_PREFIX/pkg-config) and opens a single portable database file directly from the calling process.
Queries are written in a Cypher-like language that mixes graph pattern matching (MATCH, multi-hop and variable-length traversal), a vector distance operator (<=>) for nearest-neighbor search, and a full-text operator (@@) for BM25-ranked lookups, so a single query can join a semantic search, a keyword search, and a graph traversal without shipping data between separate systems. The package also exposes durable named streams and a graph changefeed through the same transaction/WAL path used for writes, which is useful for keeping downstream consumers in sync with graph changes without polling.
It targets single-process, single-machine workloads — the embedded model is single-writer, so it is a fit for local agent memory, retrieval-augmented generation pipelines, and knowledge-graph-backed applications that would otherwise wire together a separate graph database, vector database, and search index.
What You Get
- A
Databaseclass that opens a single portable.latticefile (or an in-memory:memory:database) with no separate server process - A
TransactionAPI for creating nodes/edges with labels and properties, setting vectors, and committing or rolling back with ACID guarantees - A Cypher-like query method supporting
MATCH/WHERE/RETURN/MERGE/WITH/UNWIND, the<=>vector-distance operator, and the@@full-text operator in the same query - Built-in
hash_embeddeterministic embeddings for examples/testing, plus an HTTP embedding client for calling Ollama or OpenAI-compatible endpoints - Typed exception hierarchy (
LatticeIOError,LatticeNotFoundError,LatticeTxnAbortedError, etc.) mapped from the native library’s error codes - Durable named streams and a graph changefeed reachable through the same transaction path as graph writes, for building change-driven consumers
Common Use Cases
- Embedding a local knowledge graph (documents, chunks, authors, citations) directly inside a Python application with no database server to operate
- Building agent-memory or RAG pipelines that need graph traversal, vector similarity, and keyword search over the same underlying data
- Prototyping graph-plus-retrieval workloads locally before considering a client-server graph or vector database
- Shipping a small, portable per-user or per-tenant database file, including serializing to/from bytes for storage in object storage
Under The Hood
Architecture
The Python package is a thin ctypes layer over the native liblattice shared library: _bindings.py loads the library (via LATTICE_LIB_PATH, LATTICE_PREFIX, pkg-config, or a bundled copy under latticedb/lib/), declares the C function signatures, and converts between Python values and the C LatticeValue/LatticeNodeId structs; database.py wraps the raw handle in a Database class exposing write()/read() context managers, and transaction.py implements node/edge/vector/property operations against an open transaction handle, with types.py defining the result dataclasses (QueryResult, VectorSearchResult, FtsSearchResult, StreamRecord) returned to callers. Because all durability, indexing, and query execution live in the native Zig engine, the Python layer’s job is marshaling and lifecycle management rather than the database logic itself — a change to the core engine’s C ABI is the one thing that would ripple through this whole binding.
Tech Stack
The binding depends only on numpy at runtime (for vector arguments) and ships mypy, pytest/pytest-cov, and ruff as dev dependencies; it builds with setuptools and packages a prebuilt native library under latticedb/lib/*.so|*.dylib|*.dll, discovered via ctypes.CDLL. The underlying engine itself is written in Zig with no external dependencies, built via zig build, and the wheel build can either bundle a prebuilt liblattice from LATTICE_BUNDLE_LIB_DIR or compile it during the build.
Code Quality
The binding has a substantial test suite (test_basic.py and test_integration.py, together on the order of 2,000+ lines) run across Python 3.9–3.13 in CI, with a coverage gate (pytest --cov-fail-under=85) enforced on at least one CI matrix leg, plus mypy --strict and ruff linting as separate CI jobs. Errors from the native layer are mapped to a typed exception hierarchy (LatticeError subclasses per native error code) rather than being swallowed, and the public API carries full type hints with a py.typed marker for downstream type checking.
What Makes It Unique
Most embedded Python database bindings pick one access pattern — key-value, relational, or vector — and this one deliberately puts graph traversal, HNSW vector search, and BM25 full-text search behind a single Cypher-like query and a single transaction/WAL path, so a query can combine MATCH graph patterns with <=> vector distance and @@ text matching without moving data between separate engines. It also exposes durable named streams and a graph changefeed through that same transaction log, which is a less common feature for an embedded, single-writer database binding aimed at local, single-machine applications.