Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 83 additions & 11 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
pull_request:
paths:
- .github/workflows/python-publish.yml
- ci/check_wheel.py
workflow_dispatch:
inputs:
mode:
Expand All @@ -18,12 +19,13 @@ on:
- dry_run
- release

permissions:
contents: read

jobs:
publish:
build-linux:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
timeout-minutes: 60
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-C debuginfo=1"
Expand Down Expand Up @@ -64,13 +66,83 @@ jobs:
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: python-dist
name: python-dist-linux
path: python/dist
if-no-files-found: error

- name: Publish to PyPI
if: >
(github.event_name == 'release' && github.event.action == 'released') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'release')
working-directory: python
build-macos:
# Same native Apple Silicon runner used by pylance.
runs-on: warp-macos-14-arm64-6x
timeout-minutes: 60
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0"
MACOSX_DEPLOYMENT_TARGET: "11.0"
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install system dependencies
run: brew install protobuf

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Build Apple Silicon wheel
# Match pylance's native macOS build action.
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1
with:
command: build
rust-toolchain: stable
args: --release --strip --target aarch64-apple-darwin --interpreter python --out dist
working-directory: python

- name: Test installed wheel without Rust
shell: bash
run: |
uv publish --trusted-publishing always
# Match the test-data writer to the Rust reader; constrain CI only.
UV_BIN="$(command -v uv)"
for version in 3.11 3.12; do
environment="$RUNNER_TEMP/wheel-test-$version"
"$UV_BIN" venv --python "$version" "$environment"
PATH="$environment/bin:/usr/bin:/bin" "$UV_BIN" pip install \
--python "$environment/bin/python" --only-binary=:all: \
python/dist/*.whl "pylance>=1.0.0,<2"
PATH="$environment/bin:/usr/bin:/bin" "$environment/bin/python" \
-I ci/check_wheel.py
done

- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: python-dist-macos-aarch64
path: python/dist/*.whl
if-no-files-found: error

publish:
needs: [build-linux, build-macos]
if: >
(github.event_name == 'release' && github.event.action == 'released') ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'release')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
pattern: python-dist-*
path: dist
merge-multiple: true

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Publish to PyPI
run: uv publish --trusted-publishing always
3 changes: 2 additions & 1 deletion .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ jobs:
source .venv/bin/activate
uv pip install maturin[patchelf]
# Install test dependencies only (skip editable install to avoid double-building Rust)
uv pip install pytest pyarrow pandas ruff
# Match test-data writers to the Rust Lance 1.x reader.
uv pip install pytest pyarrow pandas ruff "pylance>=1.0.0,<2"
- name: Build Python extension
working-directory: python
run: |
Expand Down
53 changes: 53 additions & 0 deletions ci/check_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright The Lance Authors

"""Exercise the installed binary wheel outside the source tree, without Rust."""

import importlib.metadata
import shutil
import sys
from pathlib import Path
from tempfile import TemporaryDirectory

import pyarrow as pa
from lance import write_dataset
from lance_graph import CypherQuery, DirNamespace, GraphConfig, SqlQuery, _internal


def main():
assert sys.prefix != sys.base_prefix, "Use a clean virtual environment"
assert shutil.which("cargo") is None, "Remove Cargo from the smoke-test PATH"
assert shutil.which("rustc") is None, "Remove rustc from the smoke-test PATH"
extension = Path(_internal.__file__).resolve()
assert extension.is_relative_to(Path(sys.prefix).resolve()), extension
assert extension.suffix == ".so", "Expected the installed native extension"

people = pa.table({"id": [1, 2, 3], "name": ["Alice", "Bob", "Carol"]})
knows = pa.table({"source": [1, 2], "target": [2, 3]})
config = (
GraphConfig.builder()
.with_node_label("Person", "id")
.with_relationship("KNOWS", "source", "target")
.build()
)
query = CypherQuery(
"MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person) "
"WHERE a.id = 1 RETURN b.name AS friend, c.name AS friend_of_friend"
).with_config(config)
expected = [{"friend": "Bob", "friend_of_friend": "Carol"}]
assert query.execute({"Person": people, "KNOWS": knows}).to_pylist() == expected
with TemporaryDirectory() as root:
write_dataset(people, Path(root) / "Person.lance")
write_dataset(knows, Path(root) / "KNOWS.lance")
assert query.execute_with_namespace(DirNamespace(root)).to_pylist() == expected
assert SqlQuery("SELECT COUNT(*) AS count FROM people").execute(
{"people": people}
).to_pylist() == [{"count": 3}]
print(
f"Installed lance-graph {importlib.metadata.version('lance-graph')} wheel "
f"passed Cypher and SQL checks on Python {sys.version.split()[0]} without Rust"
)


if __name__ == "__main__":
main()
20 changes: 20 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ Lance Graph combines a Rust-powered Cypher query engine with Python APIs for:
pip install lance-graph
```

The Python release workflow builds Linux x86-64 and native Apple Silicon wheels.
Installing a version with a wheel for your platform does not require Rust:

```bash
pip install --only-binary=:all: lance-graph
```

The binary-only option fails if that version has no compatible wheel, rather than
compiling during installation. Earlier releases may not include the Apple Silicon
wheel; check the selected release's files on PyPI. Rust and maturin are needed
only when building from source.

Release-workflow pull requests and manual `dry_run` builds produce downloadable
workflow artifacts without publishing. Release runs publish the Linux distributions
and the tested Apple Silicon wheel together after both builds succeed. The macOS wheel is tested
in clean Python 3.11 and 3.12 environments with Rust absent from PATH, using actual
in-memory and disk-backed Cypher traversal and SQL execution from the installed
extension. CI uses a Lance 1.x test-data writer to match the embedded Rust reader;
this test constraint does not change the package runtime requirements.

## Quick Start

### 1. Simple Cypher Query
Expand Down
Loading