- Rust, through rustup.
rust-toolchain.tomlat the repository root pins the version, withclippyandrustfmt; rustup installs it on the first build. protocfor Lance's build:brew install protobufon macOS,sudo apt-get install -y protobuf-compileron Debian and Ubuntu.- Python 3.12 or newer, and uv.
Dependencies are managed with uv and pinned in uv.lock. The package is built with
maturin: pyproject.toml points it at the bindings crate in
crates/lance-csr-python/, so run these commands from this python/ directory.
cd python
uv sync --locked --no-install-project # locked dependencies and the dev group into .venv
uv run --no-sync maturin develop # debug build of the extension, installed in editable modemake install runs both. The dev dependency group in pyproject.toml holds maturin, pytest, pandas, ruff,
pyright and PyArrow's separate stubs, and uv sync installs it by default.
--no-install-projectleaves the package tomaturin develop. Without it, uv builds the package through maturin's build backend, in release mode.--no-synconuv runuses.venvas it is. A plainuv runoruv syncre-syncs exactly to the lockfile and uninstalls thematurin developbuild, which the lockfile doesn't list. To re-sync and keep the build, add--inexact(asmake syncdoes).- To change dependencies, use
uv addanduv remove(uv add --group dev <tool>for development tools), and commit the updateduv.lock.
maturin develop installs the package in editable mode: changes to python/lance_csr/ take effect immediately,
while Rust changes need another maturin develop.
Debug builds compile quickly but run queries much more slowly. To time queries, build optimized code with
uv run --no-sync maturin develop --release. Release builds use thin LTO and one codegen unit
(.cargo/config.toml), so they take several minutes. Alternating maturin develop and maturin build invalidates
each other's build cache, so pick one per environment.
make test # every Python test
uv run --no-sync pytest python/tests/test_engine_csr.py -k undirected # a subsetThe Rust tests run from the repository root:
cargo test --workspaceThe PyO3 crate is excluded from that workspace. Its private Rust tests cover engine snapshot and cache
lifecycles. On macOS, extension-module leaves Python symbols for the interpreter to provide, so a standalone
test executable needs an explicit link to the active Python library. From python/, after dependency setup:
lance_python_library=$(uv run --no-sync python -c 'import pathlib, sysconfig; print(pathlib.Path(sysconfig.get_config_var("LIBDIR")) / sysconfig.get_config_var("LDLIBRARY"))')
lance_python_home=$(uv run --no-sync python -c 'import sys; print(sys.base_prefix)')
lance_test_artifacts=$(mktemp)
cargo rustc --manifest-path ../crates/lance-csr-python/Cargo.toml --tests --message-format=json -- -C "link-arg=$lance_python_library" > "$lance_test_artifacts"
lance_test_binary=$(uv run --no-sync python -c 'import json, sys; artifacts = [json.loads(line) for line in open(sys.argv[1])]; print(next(a["executable"] for a in artifacts if a.get("reason") == "compiler-artifact" and a.get("profile", {}).get("test") and a.get("executable")))' "$lance_test_artifacts")
PYTHONHOME="$lance_python_home" "$lance_test_binary"
rm "$lance_test_artifacts"PYTHONHOME lets tests that initialize the interpreter find its standard library, including with a
relocatable Python installation. This uses Cargo's emitted executable path rather than selecting an older binary from target/. It does not
change extension packaging or replace the Python tests. Use a separate CARGO_TARGET_DIR for each worktree
when validating multiple revisions, so each test binary is built from that worktree's sources.
make format # ruff format
make lint # ruff check, ruff format --check, pyrightThe public Python API is typed in python/lance_csr/_internal/graph.pyi, matching the compiled
_internal.graph module. _internal/__init__.pyi describes its parent module, and py.typed marks the
package as typed. Update the stubs, the hand-written API reference and the relevant guide whenever a
binding changes. Exceptions remain the built-in Python exceptions listed in the API reference.
make lint checks python/tests/typing/public_api.py in strict mode: it exercises every class, checks
return types, and confirms invalid calls remain rejected. PyArrow's separate stubs are a development
dependency because Arrow tables and readers are part of this API. They are not a runtime dependency.
After building the extension, check public members, enum variants, properties, constructors, argument names and defaults against the stubs:
uv run --no-sync pytest python/tests/test_typing.py -qThese tests fail when an exported class or public method has no stub. They also check that each top-level export refers to the class declared by the extension. The wheel smoke check verifies that the stubs and marker survive packaging; run it against the installed wheel as described below.
For Rust, from the repository root (CI runs the same checks):
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warningspython/benchmarks/ holds scripts for the engine's own costs: catalog reuse, bounded-memory streaming, CSR expansion
and incremental CSR maintenance. Build with uv run --no-sync maturin develop --release before running them. The
Rust Criterion benchmarks live in the lance-csr-benches crate:
cargo bench -p lance-csr-benches --bench graph_execution -- --warm-up-time 1The Graph Data Council (GDC), formerly known as the Linked Data Benchmark Council (LDBC), maintains the Social Network Benchmark (SNB). For comparisons with other graph engines on GDC SNB SF1, see GDC SF1 benchmark repository.
The published wheels are built by .github/workflows/python-publish.yml: Linux x86-64 for glibc 2.28
(manylinux_2_28) and native Apple Silicon, plus an sdist. Each wheel is tested in a clean environment without Rust,
by ci/check_wheel.py and the full test suite.
To build a wheel for your own machine:
make wheel # uv run --no-sync maturin build --release --out dist --sdistTo build the Linux x86-64 wheel the way CI does, on macOS or Linux, add the target and run maturin with its zig
extra, which brings the zig linker. Use uv run --with rather than uvx: maturin finds zig through python3 on
PATH, which uv run sets up and uvx does not.
rustup target add x86_64-unknown-linux-gnu
uv run --no-project --with 'maturin[zig]>=1.9,<2' maturin build --release --locked --strip --zig \
--target x86_64-unknown-linux-gnu \
--compatibility manylinux_2_28 \
--out dist