diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..a5972c8 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,76 @@ +name: Python Publish + +on: + release: + types: + - released + pull_request: + paths: + - .github/workflows/python-publish.yml + workflow_dispatch: + inputs: + mode: + description: "dry_run: build & test only, release: build & publish to PyPI" + required: true + default: dry_run + type: choice + options: + - dry_run + - release + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-C debuginfo=1" + RUST_BACKTRACE: "1" + CARGO_INCREMENTAL: "0" + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + cache: false + + - name: Install system dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler libssl-dev + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install maturin + run: | + pip install maturin + + - name: Build distributions + working-directory: python + run: | + uv build --sdist --wheel --out-dir dist + + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: python-dist + path: python/dist + + - 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 + run: | + uv publish --trusted-publishing always diff --git a/crates/lance-context-core/Cargo.toml b/crates/lance-context-core/Cargo.toml index 4395383..44b7181 100644 --- a/crates/lance-context-core/Cargo.toml +++ b/crates/lance-context-core/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "Apache-2.0" authors = ["Lance Devs "] repository = "https://github.com/lancedb/lance-context" -readme = "../../README.md" +readme = "README.md" description = "Multimodal, versioned context storage for agentic workflows" keywords = ["context", "multimodal", "lance", "agents", "storage"] categories = ["database", "data-structures", "science"] diff --git a/crates/lance-context/Cargo.toml b/crates/lance-context/Cargo.toml index 6ecfa4c..781ba07 100644 --- a/crates/lance-context/Cargo.toml +++ b/crates/lance-context/Cargo.toml @@ -6,7 +6,7 @@ license = "Apache-2.0" authors = ["Lance Devs "] repository = "https://github.com/lancedb/lance-context" description = "Public re-export crate for lance-context bindings" -readme = "../../README.md" +readme = "README.md" [dependencies] lance-context-core = { path = "../lance-context-core" } diff --git a/crates/lance-context/README.md b/crates/lance-context/README.md new file mode 100644 index 0000000..70cbb80 --- /dev/null +++ b/crates/lance-context/README.md @@ -0,0 +1,3 @@ +# lance-context + +This crate is a light wrapper around `lance-context-core` that re-exports the public API for downstream Rust consumers and the Python bindings build. It carries no additional logic beyond the re-export. diff --git a/examples/pypi-basic/.gitignore b/examples/pypi-basic/.gitignore new file mode 100644 index 0000000..9543993 --- /dev/null +++ b/examples/pypi-basic/.gitignore @@ -0,0 +1 @@ +.artifacts/ diff --git a/examples/pypi-basic/README.md b/examples/pypi-basic/README.md new file mode 100644 index 0000000..c922dc7 --- /dev/null +++ b/examples/pypi-basic/README.md @@ -0,0 +1,35 @@ +# Lance Context PyPI Example + +This example project demonstrates how to install the `lance-context` package from PyPI and work with a context store using the Python API. + +## Setup + +Ensure Python 3.11 or newer is available locally. + +### Using uv + +```bash +cd examples/pypi-basic +uv venv +source .venv/bin/activate +uv pip install -e . +``` + +### Using pip + +```bash +cd examples/pypi-basic +python -m venv .venv +source .venv/bin/activate +pip install -e . +``` + +## Run the demo + +```bash +uv run context-demo +# or +python -m context_example.main +``` + +The script creates a Lance dataset under `.artifacts/` (ignored by git) and appends a short travel-planning conversation. It prints the current version, demonstrates time-travel by checking out an earlier version, and shows how you can reopen the dataset path to continue appending in future runs. diff --git a/examples/pypi-basic/pyproject.toml b/examples/pypi-basic/pyproject.toml new file mode 100644 index 0000000..1676ea7 --- /dev/null +++ b/examples/pypi-basic/pyproject.toml @@ -0,0 +1,14 @@ +[build-system] +requires = ["setuptools>=64"] +build-backend = "setuptools.build_meta" + +[project] +name = "lance-context-example" +version = "0.1.0" +description = "Example project demonstrating the lance-context PyPI package." +readme = "README.md" +requires-python = ">=3.11" +dependencies = ["lance-context>=0.1.0"] + +[project.scripts] +context-demo = "context_example.main:main" diff --git a/examples/pypi-basic/src/context_example/__init__.py b/examples/pypi-basic/src/context_example/__init__.py new file mode 100644 index 0000000..21db616 --- /dev/null +++ b/examples/pypi-basic/src/context_example/__init__.py @@ -0,0 +1,3 @@ +from .main import main + +__all__ = ["main"] diff --git a/examples/pypi-basic/src/context_example/main.py b/examples/pypi-basic/src/context_example/main.py new file mode 100644 index 0000000..e997bc1 --- /dev/null +++ b/examples/pypi-basic/src/context_example/main.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from pathlib import Path +from uuid import uuid4 + +from lance_context import Context + + +def main() -> None: + project_root = Path(__file__).resolve().parent.parent.parent + artifacts_dir = project_root / ".artifacts" + artifacts_dir.mkdir(exist_ok=True) + + dataset_path = artifacts_dir / f"travel_context_{uuid4().hex}.lance" + ctx = Context.create(dataset_path.as_posix()) + print(f"Created context store at {dataset_path}") + + ctx.add("system", "You are a friendly travel agent who compares destinations.") + ctx.add("user", "Where should I travel in spring if I love street food?") + ctx.add( + "assistant", + "Consider Osaka, Japan for cherry blossoms and the Kuromon market.", + ) + + first_version = ctx.version() + + ctx.add("user", "Any budget-friendly option in Europe?") + ctx.add( + "assistant", + "Porto, Portugal offers great food, coastal views, and reasonable prices.", + ) + + print(f"Entries stored: {ctx.entries()}") + print(f"Current version: {ctx.version()}") + + ctx.checkout(first_version) + print(f"Rolled back to version {first_version}; entries now {ctx.entries()}") + + print( + "Re-run this script to create a fresh dataset, or reuse the printed path to " + "append more entries from another process." + ) + + +if __name__ == "__main__": # pragma: no cover - manual invocation + main()