Skip to content

Commit 9dce296

Browse files
geruhXuanwo
andauthored
chore(python): update pyproject, docs, and cleanup makefile (#6809)
While working in the python bindings directory I kept hitting some issues while developing, so I cleaned up a few things that I was able to figure out. The main problem was uv sync kept re-resolving the lockfile since our optional deps had unpinned packages, even if I was just running the tests. Fixed that by pinning the dev dependencies but ultimately can revert if im lacking context as to why they weren't pinned before. But now `uv sync` is stable out the box. While I was in there I also, did an overhaul of the `MakeFile` so that install was just one command, leveraged a bit more uv there, and added help to document everything. Also, updated the docs that referenced the old logic to use new changes. Although there are a few more places that need this. i.e. direct `maturin` references. After this PR: ``` cd python && make install # once uv run make test # every day make build # after Rust changes uv sync # after pulling commits with dep changes ``` --------- Co-authored-by: Xuanwo <github@xuanwo.io>
1 parent 46e9efe commit 9dce296

11 files changed

Lines changed: 180 additions & 60 deletions

File tree

.github/workflows/ci-benchmarks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ jobs:
4747
run: |
4848
python -m venv venv
4949
source venv/bin/activate
50-
pip install maturin duckdb requests pytest pytest-benchmark datasets
51-
maturin develop --locked --release --features datagen
50+
pip install maturin==1.13.3 uv duckdb requests pytest pytest-benchmark datasets
51+
maturin develop --uv --locked --release --features datagen
5252
- name: Build memtest
5353
run: |
5454
source venv/bin/activate

.github/workflows/codex-backport-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ jobs:
142142
10. Run ONLY the tests related to the changes in these PRs:
143143
- Use "git diff --name-only ${RELEASE_BRANCH}...HEAD" to see all files changed across all cherry-picked commits.
144144
- For Rust changes: Run tests for the affected crates only (e.g., "cargo test -p lance-core" if lance-core files changed).
145-
- For Python changes (python/** files): Build with "cd python && maturin develop" then run "pytest" on the specific test files that were modified, or related test files.
145+
- For Python changes (python/** files): Build with "cd python && make build" then run "uv run pytest" on the specific test files that were modified, or related test files.
146146
- For Java changes (java/** files): Run "cd java && mvn test" for the affected modules.
147147
- If test files themselves were modified, run those specific tests.
148148
- Do NOT run the full test suite - only run tests related to the changed files.

.github/workflows/codex-fix-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ jobs:
144144
- Run "cargo clippy --workspace --tests --benches -- -D warnings" to check for issues
145145
- Run ONLY the specific failing tests to confirm they pass now:
146146
- For Rust test failures: Run the specific test with "cargo test -p <crate> <test_name>"
147-
- For Python test failures: Build with "cd python && maturin develop" then run "pytest <specific_test_file>::<test_name>"
147+
- For Python test failures: Build with "cd python && make build" then run "uv run pytest <specific_test_file>::<test_name>"
148148
- For Java test failures: Run "cd java && mvn test -Dtest=<TestClass>#<testMethod>"
149149
- Do NOT run the full test suite - only run the tests that were failing
150150

.github/workflows/file_verification.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ jobs:
5151
run: |
5252
python -m venv venv
5353
source venv/bin/activate
54-
pip install pyarrow maturin
55-
maturin develop --release
54+
pip install pyarrow maturin==1.13.3 uv
55+
maturin develop --uv --release
5656
5757
- name: Test Lance File Write Read Round Trip
5858
run: |

.github/workflows/python.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ jobs:
8787
python -m venv venv
8888
source venv/bin/activate
8989
pip install torch tqdm --index-url https://download.pytorch.org/whl/cpu
90-
pip install maturin
91-
maturin develop --profile ci --locked --extras tests,ray
90+
pip install maturin==1.13.3 uv
91+
maturin develop --uv --profile ci --locked --extras tests,ray
9292
- name: Run doctest
9393
run: |
9494
source venv/bin/activate

python/AGENTS.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ Also see [root AGENTS.md](../AGENTS.md) for cross-language standards.
55
## Commands
66

77
* Environment: use `uv` for all local Python environment setup in this repository.
8-
* First step in every new worktree or fresh checkout: run `uv sync --extra tests --extra dev` from `python/` before any Python command. Add other extras such as `benchmarks`, `torch`, or `geo` only when needed.
8+
* First step in every new worktree or fresh checkout: run `make install` from `python/` before any Python command. This runs `uv sync` (dev and test dependencies are included by default via `[tool.uv] default-groups`) and sets up pre-commit hooks. Add `--group benchmarks`, `--extra torch`, or `--extra geo` only when needed.
99
* `uv sync` builds the local `pylance` Rust extension as part of environment setup. This can take a long time. Start it early, let it finish, and do not interrupt it or switch to a different setup path just because the build is slow.
10+
* After the initial `make install`, use `uv run make test` to run tests.
11+
* Only run `uv sync` again when dependencies change (e.g., after pulling new commits that update `pyproject.toml` or `uv.lock`).
1012
* Command execution: always use `uv run ...` for Python-related repository commands. Do not rely on a globally activated environment.
1113
* Never invoke bare `python`, `pytest`, `pip`, `maturin`, `make test`, `make doctest`, `make lint`, or `make format` for repository work.
1214
* If a Python command fails outside `uv run`, that does not count as a dependency or test failure. Fix the environment usage first and rerun correctly.
13-
* Build time expectations: `uv sync` and `uv run maturin develop` build the local `pylance` Rust extension as part of the environment workflow. This can be slow, especially on the first run or after Rust dependency changes; treat that as expected and do not switch to a different environment manager or shortcut around the build just because it takes time.
14-
* Build: `uv run maturin develop` (required after Rust changes)
15+
* Build time expectations: `make install` and `make build` build the local `pylance` Rust extension as part of the environment workflow. This can be slow, especially on the first run or after Rust dependency changes; treat that as expected and do not switch to a different environment manager or shortcut around the build just because it takes time.
16+
* Build: `make build` (required after Rust changes)
1517
* Test: `uv run make test`
1618
* Run single test: `uv run pytest python/tests/<test_file>.py::<test_name>`
1719
* Doctest: `uv run make doctest`
@@ -34,4 +36,4 @@ Also see [root AGENTS.md](../AGENTS.md) for cross-language standards.
3436
## Common Failure Mode
3537

3638
- A missing module or missing command error from bare `python`, `pytest`, `pip`, `maturin`, or `make` is usually an environment usage mistake, not a repository issue.
37-
- Before reporting a Python dependency as unavailable, verify that `uv sync --extra tests --extra dev` has been run in the current worktree and that the failing command was executed with `uv run ...`.
39+
- Before reporting a Python dependency as unavailable, verify that `uv sync` has been run in the current worktree and that the failing command was executed with `uv run ...`.

python/CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ The python integration is done via pyo3 + custom python code:
1010
To build the Python bindings, first install requirements:
1111

1212
```bash
13-
pip install maturin
13+
cd python
14+
make install
1415
```
1516

1617
To make a dev install:
1718

1819
```bash
19-
cd python
20-
maturin develop
20+
make build
2121
```
2222

2323
After installing, you can run `import lance` in a Python shell within the virtual environment.

python/DEVELOPMENT.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
For local development, prefer [uv](https://docs.astral.sh/uv/) to create and manage the Python environment:
44

55
```shell
6-
uv sync --extra tests --extra dev
6+
uv sync
77
```
88

9-
Add extras such as `benchmarks`, `torch`, or `geo` only when you need them. After the environment is initialized, either activate it or use `uv run ...` for commands.
9+
Add `--group benchmarks` for benchmarks, or `--extra torch` / `--extra geo` for those optional features. After the environment is initialized, either activate it or use `uv run ...` for commands.
1010

1111
`uv sync` is not just downloading Python packages here. It also builds the local `pylance` Rust extension as part of the editable environment, so the first run, cache misses, or Rust dependency changes can make it noticeably slow. This is expected; let the build finish instead of interrupting it and switching to a different environment setup.
1212

@@ -17,7 +17,7 @@ This project is built with [maturin](https://github.com/PyO3/maturin).
1717
It can be built in development mode with:
1818

1919
```shell
20-
uv run maturin develop
20+
uv run maturin develop --uv
2121
```
2222

2323
This builds the Rust native module in place. You will need to re-run this
@@ -104,8 +104,8 @@ benchmarks added there should run in less than 5 seconds.
104104
Before running benchmarks, you should build pylance in release mode:
105105

106106
```shell
107-
uv sync --extra tests --extra dev --extra benchmarks
108-
uv run maturin develop --profile release-with-debug --extras benchmarks --features datagen
107+
uv sync --group benchmarks
108+
uv run maturin develop --uv --profile release-with-debug --extras benchmarks --features datagen
109109
```
110110

111111
(You can also use `--release` or `--profile release`, but `--profile release-with-debug`
@@ -172,13 +172,13 @@ the benchmarks again with `--benchmark-compare`.
172172
```shell
173173
CURRENT_BRANCH=$(git branch --show-current)
174174
git checkout main
175-
uv sync --extra tests --extra dev --extra benchmarks
176-
uv run maturin develop --profile release-with-debug --features datagen
175+
uv sync --group benchmarks
176+
uv run maturin develop --uv --profile release-with-debug --features datagen
177177
uv run pytest --benchmark-save=baseline python/benchmarks -m "not slow"
178178
COMPARE_ID=$(ls .benchmarks/*/ | tail -1 | cut -c1-4)
179179
git checkout $CURRENT_BRANCH
180-
uv sync --extra tests --extra dev --extra benchmarks
181-
uv run maturin develop --profile release-with-debug --features datagen
180+
uv sync --group benchmarks
181+
uv run maturin develop --uv --profile release-with-debug --features datagen
182182
uv run pytest --benchmark-compare=$COMPARE_ID python/benchmarks -m "not slow"
183183
```
184184

python/Makefile

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,74 @@
1+
.DEFAULT_GOAL := help
2+
.PHONY: help install build test integtest doctest compattest format format-python lint lint-python lint-rust clean
3+
PYTHON ?=
4+
PYTEST_ARGS ?= -vvv -s -m "not recurring"
5+
KEEP_COMPOSE ?= 0
6+
COMPOSE_FILE ?= ../docker-compose.yml
7+
UV_SYNC = uv sync
8+
UV_RUN = uv run --frozen
9+
10+
# Set PYTHON=3.12 to use a specific Python version with uv.
11+
ifneq ($(strip $(PYTHON)),)
12+
UV_PYTHON_ARG = --python $(PYTHON)
13+
UV_SYNC += $(UV_PYTHON_ARG)
14+
UV_RUN += $(UV_PYTHON_ARG)
15+
endif
16+
117
ifeq ($(CI), true)
2-
PYTEST_ARGS = -vvv -s --durations=30 -m "not recurring"
3-
else
4-
PYTEST_ARGS = -vvv -s -m "not recurring"
18+
PYTEST_ARGS += --durations=30
519
endif
620

7-
test:
21+
help: ## Show this help
22+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
23+
24+
install: ## Sync dependencies and set up local development tools
25+
@if ! command -v uv > /dev/null 2>&1; then \
26+
echo "uv not found. Install it from https://docs.astral.sh/uv/getting-started/installation/"; \
27+
exit 1; \
28+
fi
29+
$(UV_SYNC)
30+
uv tool run pre-commit install
31+
32+
build: ## Build the local Rust extension with maturin
33+
$(UV_RUN) maturin develop --uv
34+
35+
test: ## Run Python tests except recurring tests
836
pytest $(PYTEST_ARGS) python/tests
9-
.PHONY: test
1037

11-
integtest:
12-
pytest --run-integration $(PYTEST_ARGS) python/tests/test_s3_ddb.py python/tests/test_namespace_integration.py
13-
.PHONY: integtest
38+
integtest: ## Start LocalStack and run integration tests
39+
@if [ "$(KEEP_COMPOSE)" = "1" ]; then \
40+
docker compose -f $(COMPOSE_FILE) up -d --wait && \
41+
pytest --run-integration $(PYTEST_ARGS) python/tests/test_s3_ddb.py python/tests/test_namespace_integration.py; \
42+
else \
43+
trap 'docker compose -f $(COMPOSE_FILE) down -v --remove-orphans --timeout 0 >/dev/null 2>&1 || true' EXIT; \
44+
docker compose -f $(COMPOSE_FILE) up -d --wait && \
45+
pytest --run-integration $(PYTEST_ARGS) python/tests/test_s3_ddb.py python/tests/test_namespace_integration.py; \
46+
fi
1447

15-
doctest:
48+
doctest: ## Run Python doctests
1649
pytest --doctest-modules $(PYTEST_ARGS) python/lance
17-
.PHONY: doctest
1850

19-
compattest:
20-
pytest --run-compat $(PYTEST_ARGS) python/tests/compat
21-
.PHONY: compattest
51+
compattest: ## Run upgrade/downgrade compatibility tests
52+
pytest --run-compat $(PYTEST_ARGS) python/tests/compat
2253

23-
format: format-python
54+
format: format-python ## Auto-format Python and Rust
2455
cargo fmt
25-
.PHONY: format
26-
27-
build:
28-
maturin develop
29-
.PHONY: build
3056

31-
format-python:
57+
format-python: ## Auto-format Python (ruff)
3258
ruff format python
3359
ruff check --fix python
34-
.PHONY: format-python
3560

36-
lint: lint-python lint-rust
37-
.PHONY: lint
61+
lint: lint-python lint-rust ## Check Python and Rust formatting/lints
3862

39-
lint-python:
63+
lint-python: ## Check Python formatting, linting, and types
4064
ruff format --check --diff python
4165
ruff check python
4266
pyright
43-
.PHONY: lint-python
4467

45-
lint-rust:
68+
lint-rust: ## Check Rust formatting and clippy lints
4669
cargo fmt -- --check
4770
cargo clippy -- -D warnings
48-
.PHONY: lint-rust
4971

50-
clean:
72+
clean: ## Remove build artifacts
5173
cargo clean
5274
rm -rf target
53-
.PHONY: clean

python/pyproject.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,33 @@ geo = [
7171
"geoarrow-rust-io",
7272
]
7373

74+
[dependency-groups]
75+
tests = [
76+
"boto3==1.40.43",
77+
"datasets==4.1.1; python_version >= '3.10'",
78+
"duckdb==1.4.0",
79+
"ml_dtypes==0.5.3",
80+
"pillow==11.3.0",
81+
"pandas==2.3.3",
82+
"polars[pyarrow,pandas]==1.34.0",
83+
"psutil==7.1.0",
84+
"pytest==8.4.2",
85+
"tensorflow==2.20.0; sys_platform == 'linux' and python_version >= '3.10'",
86+
"tqdm==4.67.1",
87+
"datafusion==53.0.0; python_version >= '3.10'",
88+
]
89+
dev = [
90+
"maturin==1.13.3",
91+
"pyright==1.1.406",
92+
"ruff==0.11.2",
93+
]
94+
benchmarks = [
95+
"pytest-benchmark==5.1.0",
96+
]
97+
98+
[tool.uv]
99+
default-groups = ["dev", "tests"]
100+
74101
[tool.ruff]
75102
lint.select = ["F", "E", "W", "I", "G", "TCH", "PERF", "B019"]
76103

0 commit comments

Comments
 (0)