This release is a pre-release and may not be stable for production use.
Zova Python Bindings
This package contains the source-first Python bindings for Zova.
It is a PyO3/maturin extension backed by the safe Rust zova binding. It does
not wrap the C ABI directly with ctypes or cffi. The native build still uses
Zova's C ABI underneath through the Rust zova-sys crate, so Python gets the
same records/objects/vectors foundation without reimplementing the ABI ownership
rules.
Contents
- How It Fits
- Install
- Local Development
- What It Covers
- Savepoints
- Operational Safety
- Extensions
- App Events
- Objects
- Vectors
- Graphs
- Bound Stores
- SQL-Native Vector Search
How It Fits
Python users import zova. The extension is built with PyO3 and reuses the
safe Rust binding, which in turn links Zova's C ABI.
flowchart LR
App["Python app"]
PyPkg["zova Python package"]
PyO3["PyO3 extension"]
Rust["Rust zova crate"]
CABI["libzova_c.a"]
File["local .zova file"]
App --> PyPkg
PyPkg --> PyO3
PyO3 --> Rust
Rust --> CABI
CABI --> File
Install
After the Python package is published to PyPI:
python -m pip install zova
The Python package is backed by a PyO3 extension built through maturin and Cargo. Users need Python 3.13 or newer; CPython 3.13 and 3.14 are the tested runtime matrix. Linux wheels target glibc 2.28 or newer. Published stable-ABI wheels do not require Rust, Cargo, Zig, or a local C compiler. Zova does not publish a Python source distribution, so an unsupported platform or interpreter fails without attempting a native build.
The release workflow builds the documented Linux/macOS x86_64/arm64 wheel matrix.
The current development build uses .zova format 11 and does not migrate older
format databases in place. Format-9 and format-10 databases can be probed and
migrated forward with the module-level probe_format(path) and
migrate_database(source, destination, *, verify=True) functions: migration is
explicit, copy-forward, publishes a separately validated format-11 destination,
and never mutates the source.
Opaque-key graph, edge-payload, topology-scan, prepared-build, and generic
fresh-build session APIs are supported low-level C/raw zova-sys surfaces.
The Python package deliberately exposes the established graph CRUD and
traversal API instead of those specialized publication surfaces.
Local Development
From bindings/python:
uv run --isolated --with maturin --with pytest maturin develop
uv run --isolated --with pytest python -m pytest
The native build uses maturin, Cargo, and the Rust zova crate. Users do not
need to locate a shared C library manually.
The Python API is part of the candidate 1.x contract described in
../../API_STABILITY.md.
What It Covers
The Python package exposes database lifecycle, conversion, prepared SQL statements, transactions, explicit vacuum, backup/compact/restore, objects, streaming object writes, vectors, SQL-native vector search, graphs, bundled extension lifecycle, same-process app events, context managers, and Zova status exceptions.
One Python Database object owns one native handle. The native C ABI serializes
calls on that handle, so one handle is safe but not parallel. Open additional
database handles when an application needs independent concurrent connections;
SQLite locking rules still apply across handles. PyO3 classes remain unsendable
in this release even though the native C ABI serializes its own calls.
Use Database.open(path, read_only=True) for read-only handles, and
Database.set_busy_timeout(milliseconds) when an application wants SQLite to
wait briefly on cross-handle contention. No nonzero timeout is installed by
default.
Use Database.last_insert_rowid(), Database.changes(),
Database.total_changes(), and Statement.column_name(index) for normal
application SQL record helpers. They do not expose or stabilize Zova's private
_zova_* tables.
Savepoints
Use explicit savepoints for partial rollback inside one database connection:
with zova.Database.open("app.zova") as db:
db.begin_immediate()
db.savepoint("attach_file")
db.exec("insert into attachments(filename) values ('draft.txt')")
db.rollback_to_savepoint("attach_file")
db.release_savepoint("attach_file")
db.commit()
Savepoint names are strict ASCII identifiers: 1-64 bytes, first byte
[A-Za-z_], remaining bytes [A-Za-z0-9_], and no case-insensitive _zova_
prefix. rollback_to_savepoint() keeps the savepoint active;
release_savepoint() removes it.
An inner released savepoint can still be undone by rolling back an outer
transaction or savepoint.
Use savepoint_context() when you want rollback cleanup tied to a with block:
with db.savepoint_context("attach_file") as scoped_db:
scoped_db.exec("insert into attachments(filename) values ('draft.txt')")
On normal exit the context releases the savepoint. On exception it rolls back, releases, and then re-raises the original exception when cleanup succeeds.
Operational Safety
Use backup_to() for a faithful snapshot, compact_to() for a
space-reclaiming copy, and restore_backup() to copy a backup into a new
destination file. Destinations must be .zova paths and are never overwritten.
with zova.Database.open("app.zova") as db:
db.backup_to("app.backup.zova")
db.compact_to("app.compact.zova")
zova.restore_backup("app.backup.zova", "app.restored.zova")
By default, each operation verifies the destination after copying. Pass
verify=False only when you will verify separately, for example with
zova check --deep.
Diagnostic recovery commands such as zova doctor, zova salvage --dry-run,
and zova salvage <source> <destination> are CLI-first. In v0.25, CLI salvage
copies valid core data and uses extension salvage hooks for extension-owned
storage. The Python package does not expose typed doctor/salvage report APIs
yet, and library code should not parse the human text output as a stable
binding contract.
Extensions
Python exposes lifecycle methods for extensions already present in the current
process registry. The default registry includes bundled extensions such as
trgm, so Python applications can install, list, check, and drop trgm
directly. App-registered extension authoring and dynamic .zovaext loading
are not exposed as Python APIs yet.
The v0.25 C ABI has scalar SQL callback registration and trusted .zovaext
bundle loading, but Python callback conversion, GIL behavior, and callback
lifetime rules are deferred to a later binding slice. Use bundled extensions,
trusted extension bundles through the CLI/C layer, or a host-owned bridge when
SQL functions must run on Zova-owned connections.
See ../../docs/extensions.md for the current host
contract and trust model. A fuller records/objects/vectors/graphs example lives
in examples/extensions.py.
with zova.Database.open("app.zova") as db:
db.install_extension("trgm")
info = db.extension_info("trgm")
assert info.storage_prefix == "_zova_ext_trgm_"
db.check_extensions()
with db.prepare(
"select zova_trgm_put('messages', ?1, 'record', 'messages', ?2, ?3)"
) as stmt:
stmt.bind_text(1, "message:123")
stmt.bind_text(2, "123")
stmt.bind_text(3, "attachment upload failed")
assert stmt.step() == zova.Step.ROW
with db.prepare(
"""
select document_id, score
from zova_trgm_search
where index_name = 'messages'
and query = ?1
and "limit" = 10
order by rank
"""
) as stmt:
stmt.bind_text(1, "attachement failed")
App Events
Use listen / notify for same-process storage workflow notifications. They
are explicit, in-memory, local to one open Database handle, and delivered only
after the surrounding Zova transaction commits. Rollback discards pending
notifications.
with zova.Database.open("app.zova") as db:
with db.listen("message:123:attachments") as sub:
db.begin_immediate()
db.exec("insert into attachments(message_id, name) values (123, 'photo.jpg')")
db.notify("message:123:attachments", "changed")
db.commit()
note = sub.try_receive()
if note is not None:
print(note.channel, note.payload)
SQL zova_notify(...) follows the same transaction rules when the surrounding
transaction/savepoint was opened through Zova helpers; raw SQL transaction
scopes are rejected because Zova cannot track their notification lifetime.
Event delivery is queue-only in v0.18: no callbacks, no blocking receive, no cross-process delivery, no replay after restart, and no automatic logging of SQL, object, vector, or graph mutations. Each subscription queue holds 1024 notifications and drops the oldest entries on overflow; the next received notification reports how many were dropped before it.
Objects
The Python binding exposes Zova objects as content-addressed byte values while keeping application metadata in normal SQL tables.
import zova
with zova.Database.create("app.zova") as db:
db.exec(
"create table attachments("
"id integer primary key, "
"filename text not null, "
"object_id blob not null)"
)
object_id = db.put_object(b"hello from Zova")
with db.prepare("insert into attachments(filename, object_id) values (?1, ?2)") as stmt:
stmt.bind_text(1, "hello.txt")
stmt.bind_blob(2, bytes(object_id))
stmt.step()
assert db.read_object_range(object_id, 0, 5) == b"hello"
For large inputs, use ObjectWriter so the full object does not have to be held
in memory by the caller:
with db.object_writer() as writer:
writer.write(b"chunk one")
writer.write(b"chunk two")
object_id = writer.finish()
If a writer leaves the context without finish(), it is cancelled and any
unreferenced chunks written by that writer are cleaned up. Writer operations
follow Zova's object transaction policy in single-file mode. When an object
store is bound, object writes participate in the same Zova transaction/savepoint
scope through SQLite ATTACH.
Loose chunks and assembly are also exposed for receive-side workflows:
applications track transfer state in their own SQL tables, call
put_object_chunk() for verified chunks, then call
assemble_object_from_chunks() when the manifest is complete.
Vectors
The Python binding exposes Zova vectors with the same model as the Rust binding: Zova stores numeric vectors in named collections, while application metadata stays in normal SQL tables.
import zova
with zova.Database.create("vectors.zova") as db:
db.exec(
"create table chunks("
"id text primary key, "
"document_id text not null, "
"text text not null, "
"vector_id text not null)"
)
db.create_vector_collection(
"chunks",
zova.VectorCollectionOptions(2, zova.VectorMetric.L2, zova.VectorElementType.F32),
)
db.put_vectors(
"chunks",
[
zova.VectorInput("chunk:1", zova.VectorElementType.F32, [0.0, 0.0]),
zova.VectorInput("chunk:2", zova.VectorElementType.F32, [1.0, 0.0]),
],
)
db.exec(
"insert into chunks(id, document_id, text, vector_id) values "
"('c1', 'doc-a', 'first chunk', 'chunk:1'), "
"('c2', 'doc-a', 'near chunk', 'chunk:2')"
)
results = db.search_vectors_in(
"chunks",
zova.VectorElementType.F32,
[0.0, 0.0],
["chunk:1", "chunk:2"],
2,
)
for result in results:
with db.prepare("select text from chunks where vector_id = ?1") as stmt:
stmt.bind_text(1, result.id)
stmt.step()
print(result.id, result.distance, stmt.column_text(0))
Vector APIs are typed by default. F32 uses Python floats, F16 values are
raw IEEE 754 binary16 bits carried as integer uint16 bit patterns, and I8
values are raw signed byte values with no quantization metadata.
db.create_vector_collection(
"scores_i8",
zova.VectorCollectionOptions(2, zova.VectorMetric.L2, zova.VectorElementType.I8),
)
db.put_vector("scores_i8", "near", zova.VectorElementType.I8, [1, -1])
db.create_vector_collection(
"halves",
zova.VectorCollectionOptions(2, zova.VectorMetric.L2, zova.VectorElementType.F16),
)
db.put_vector("halves", "one", zova.VectorElementType.F16, [0x3C00, 0x0000])
assert db.get_vector("halves", "one").values == [0x3C00, 0x0000]
Search is exact and lower distance is better. Candidate-filtered searches skip
missing ids and deduplicate duplicate candidates. Search-by-id excludes the
source vector. Threshold variants are inclusive, and dot-product thresholds may
be negative because dot distance is -dot_product.
Deleting a vector collection removes Zova's private vector rows only. User SQL metadata rows that reference vector ids are application-owned and remain in place.
Graphs
Graphs store relationship topology while application metadata stays in SQL tables, objects, and vectors. Apps provide stable node IDs and can point nodes at records, objects, object chunks, vectors, entities, facts, concepts, or external references.
db.create_graph(zova.DEFAULT_GRAPH_NAME)
db.put_graph_node(
zova.GraphNodeInput(
zova.DEFAULT_GRAPH_NAME,
"message:1",
"message",
zova.GraphTargetType.RECORD,
"messages",
"1",
)
)
db.put_graph_node(
zova.GraphNodeInput(
zova.DEFAULT_GRAPH_NAME,
"entity:zova",
"entity",
zova.GraphTargetType.ENTITY,
None,
"zova",
)
)
db.put_graph_edge(
zova.GraphEdgeInput(
zova.DEFAULT_GRAPH_NAME,
"message:1",
"mentions",
"entity:zova",
)
)
Use graph_neighbors() for one-hop expansion and graph_walk() for bounded
directed walks. Zova validates object, chunk, and vector targets it owns, but
arbitrary SQL row existence remains the application's job. Node IDs may contain
sensitive app identifiers, so choose export-safe IDs when files may leave the
app.
SQL-native graph helpers are available through ordinary prepared statements:
with db.prepare(
"select m.body "
"from zova_graph_neighbors as g "
"join messages as m on m.graph_node_id = g.node_id "
"where g.graph_name = 'default' "
"and g.source_node_id = 'message:1' "
'and g."limit" = 20 '
"order by g.rank"
) as stmt:
...
Use zova_graph_neighbors for one-hop joins and zova_graph_walk for bounded
directed walks with depth, predecessor_node_id, and edge_type columns.
Bound Stores
In v0.25, a .zova file may be bound to one object store, one vector store,
and one graph store through the native Zig API or CLI. The Python object,
vector, and graph methods above transparently use those stores after
Database.open. Store
create/bind/unbind/split management is not exposed as a Python API yet.
SQL-Native Vector Search
Zova registers SQL vector functions and the zova_vector_search virtual table
on Zova database connections. Bind query vectors as little-endian f32 blobs
with encode_f32_le():
query = zova.encode_f32_le([0.0, 0.0])
with db.prepare(
"select c.id, zova_vector_distance('chunks', c.vector_id, ?1) as distance "
"from chunks as c "
"where c.document_id = 'doc-a' "
"order by distance "
"limit 10"
) as stmt:
stmt.bind_blob(1, query)
Row-to-row distances use zova_vector_distance_by_id(collection, vector_id, source_vector_id). Collection-wide SQL search uses zova_vector_search:
with db.prepare(
"select c.text, s.distance "
"from zova_vector_search as s "
"join chunks as c on c.vector_id = s.vector_id "
"where s.collection = 'chunks' "
"and s.query_vector = ?1 "
"and s.top_k = 10 "
"order by s.rank"
) as stmt:
stmt.bind_blob(1, query)
Python's built-in sqlite3 module opens an ordinary SQLite connection and does
not automatically register Zova's SQL functions or virtual table. Use
zova.Database for SQL-native vector search in this binding. A future SQLite
loadable extension may make the SQL surface available to external SQLite
connections.
Metadata
Release files for zova 1.0.0rc2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| zova-1.0.0rc2-cp313-abi3-manylinux_2_28_x86_64.whl | CPython 3.13 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| zova-1.0.0rc2-cp313-abi3-manylinux_2_28_aarch64.whl | CPython 3.13 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| zova-1.0.0rc2-cp313-abi3-macosx_11_0_arm64.whl | CPython 3.13 | abi3 | macOS 11.0+ ARM64 | Details |
| zova-1.0.0rc2-cp313-abi3-macosx_10_12_x86_64.whl | CPython 3.13 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 6.9 MB
Release files / zova-1.0.0rc2-cp313-abi3-manylinux_2_28_x86_64.whl
| Download URL | zova-1.0.0rc2-cp313-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | CPython 3.13 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
186e2c77b20eb1165815c5d472544db8b13283448d2d4a6ca9d098df7f46e86c
|
|
BLAKE2b-256 checksum How to use checksums |
9d911ca042e2c63bedeac9085a9f7df114c0bfb14ca5c48b2f9993dead04d3a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / zova-1.0.0rc2-cp313-abi3-manylinux_2_28_aarch64.whl
| Download URL | zova-1.0.0rc2-cp313-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | CPython 3.13 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
e44c06f3139492c85fd9935ab7a4c1b43256f5a75a56990d672be6c6e8ecc342
|
|
BLAKE2b-256 checksum How to use checksums |
b2c2c7bc5a6b270d099be315e5277ae6b7681f650f64b937706354a25900072e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / zova-1.0.0rc2-cp313-abi3-macosx_11_0_arm64.whl
| Download URL | zova-1.0.0rc2-cp313-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.13 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
64bf1b9b4229ad389c87fadce2ef10b80398514634b31e44f2f3b1143900a1f9
|
|
BLAKE2b-256 checksum How to use checksums |
59e48038788a9028f5b4d7efb4707a8cf1879657a1e8b5047ef02108f68e77d9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / zova-1.0.0rc2-cp313-abi3-macosx_10_12_x86_64.whl
| Download URL | zova-1.0.0rc2-cp313-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.13 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
c10581084f924174cc38c797d39dfc4179621060b78a7f73939aff79de29e917
|
|
BLAKE2b-256 checksum How to use checksums |
2be40622aa6be3452fc6d0dea5ff891d03e434be8cb95b9bd410a5f89d9f11c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|