Skip to content

Commit b0da0cf

Browse files
Python(chore): add uv for dev (#581)
1 parent 02f88c4 commit b0da0cf

10 files changed

Lines changed: 5731 additions & 143 deletions

File tree

.githooks/pre-push

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ echo "→ Python files changed:"
2020
if [[ -n "$python_changed_files" ]]; then
2121
echo " ${python_changed_files[*]}"
2222
echo
23-
echo " [1/3] Formatting and linting..."
23+
echo " [1/4] Formatting and linting..."
2424
bash "$GITHOOKS_DIR/pre-push-python/fmt-lint.sh" || FAILED=1
25-
26-
echo " [2/3] Stub generation..."
25+
26+
echo " [2/4] Stub generation..."
2727
bash "$GITHOOKS_DIR/pre-push-python/stubs.sh" || FAILED=1
28-
29-
echo " [3/3] Extras validation..."
28+
29+
echo " [3/4] Extras validation..."
3030
bash "$GITHOOKS_DIR/pre-push-python/extras.sh" || FAILED=1
31+
32+
echo " [4/4] Lockfile check..."
33+
bash "$GITHOOKS_DIR/pre-push-python/lock.sh" || FAILED=1
3134
echo
3235
else
3336
echo " (none)"

.githooks/pre-push-python/extras.sh

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# ensure generated pyproject.toml extras are up-to-date
22

3+
# Clear git env vars set by the parent hook so git commands resolve the work tree normally
4+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX
5+
36
# Store the root directory of the repository
47
REPO_ROOT="$(git rev-parse --show-toplevel)"
58
PYTHON_DIR="$REPO_ROOT/python"
69
PYPROJECT_FILE="$PYTHON_DIR/pyproject.toml"
710

811
# Function to check if pyproject.toml has changed
912
check_extras_changes() {
10-
local target_path="$1"
11-
local changed_files=$(git status --porcelain "$target_path" || true)
13+
local changed_files=$(git status --porcelain "$PYPROJECT_FILE" || true)
1214

1315
if [ -n "$changed_files" ]; then
1416
echo " ❌ ERROR: Generated extras are not up-to-date:"
@@ -23,13 +25,11 @@ generate_python_extras() {
2325
echo " → Generating extras..."
2426
cd "$PYTHON_DIR"
2527

26-
if [[ ! -d "$PYTHON_DIR/venv" ]]; then
27-
echo " → Running bootstrap script..."
28-
bash ./scripts/dev bootstrap
29-
fi
28+
# Idempotent: fast on a warm cache, recreates .venv on a cold checkout.
29+
uv sync --extra dev-all --quiet
3030

3131
bash ./scripts/dev gen-extras
32-
check_extras_changes "$PYPROJECT_FILE"
32+
check_extras_changes
3333
}
3434

3535
generate_python_extras

.githooks/pre-push-python/fmt-lint.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
set -e
44

5+
# Clear git env vars set by the parent hook so git commands resolve the work tree normally
6+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX
7+
58
# Store the root directory of the repository
69
REPO_ROOT="$(git rev-parse --show-toplevel)"
710
PYTHON_DIR="$REPO_ROOT/python"
811

912
# Change to Python directory
1013
cd "$PYTHON_DIR"
1114

15+
# Ensure the dev environment is in place. Idempotent: fast on a warm
16+
# cache, recreates .venv on a cold checkout. Without this, a fresh
17+
# clone's first push fails with "ruff: command not found".
18+
uv sync --extra dev-all --quiet
19+
1220
# Run ruff format (formatter)
1321
echo " → Running ruff format..."
1422
bash ./scripts/dev fmt

.githooks/pre-push-python/lock.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# ensure uv.lock is up-to-date with pyproject.toml
2+
3+
# Clear git env vars set by the parent hook so git commands resolve the work tree normally
4+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX
5+
6+
REPO_ROOT="$(git rev-parse --show-toplevel)"
7+
PYTHON_DIR="$REPO_ROOT/python"
8+
9+
echo " → Checking uv.lock..."
10+
cd "$PYTHON_DIR"
11+
12+
if ! uv lock --check; then
13+
echo " ❌ ERROR: uv.lock is out of date with pyproject.toml."
14+
echo " Run 'uv lock' and commit the result before pushing."
15+
exit 1
16+
fi
17+
18+
echo " ✓ uv.lock is up-to-date"

.githooks/pre-push-python/stubs.sh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# ensure generated python stubs are up-to-date, from sync clients
22

3+
# Clear git env vars set by the parent hook so git commands resolve the work tree normally
4+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX
5+
36
# Store the root directory of the repository
47
REPO_ROOT="$(git rev-parse --show-toplevel)"
58
PYTHON_DIR="$REPO_ROOT/python"
@@ -23,10 +26,8 @@ generate_python_stubs() {
2326
echo " → Generating stubs..."
2427
cd "$PYTHON_DIR"
2528

26-
if [[ ! -d "$PYTHON_DIR/venv" ]]; then
27-
echo " → Running bootstrap script..."
28-
bash ./scripts/dev bootstrap
29-
fi
29+
# Idempotent: fast on a warm cache, recreates .venv on a cold checkout.
30+
uv sync --extra dev-all --quiet
3031

3132
bash ./scripts/dev gen-stubs
3233
check_stub_changes "$STUBS_DIR"

.github/workflows/python_ci.yaml

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- 'rust/crates/sift_stream_bindings/**'
3535
- '.github/workflows/python_ci.yaml'
3636
37-
test-python:
37+
static-checks:
3838
needs: [changes]
3939
if: |
4040
always() &&
@@ -49,53 +49,86 @@ jobs:
4949
with:
5050
ref: ${{ github.event.pull_request.head.sha }}
5151

52-
- name: Set up Python
53-
uses: actions/setup-python@v2
52+
- name: Set up uv
53+
uses: astral-sh/setup-uv@v6
5454
with:
55-
python-version: "3.8"
55+
enable-cache: true
5656

57-
- name: Pip install
58-
id: install
59-
run: |
60-
python -m pip install --upgrade pip
61-
pip install '.[dev-all]'
57+
- name: Verify lockfile is up to date
58+
run: uv lock --check
59+
60+
- name: Install dependencies
61+
run: uv sync --extra dev-all
6262

6363
- name: Lint
64-
run: |
65-
ruff check
64+
run: uv run ruff check
6665

6766
- name: Format
68-
run: |
69-
ruff format --check
67+
run: uv run ruff format --check
7068

7169
- name: MyPy
72-
run: |
73-
mypy lib
70+
run: uv run mypy lib
7471

7572
# Re-run mypy with --platform=win32 so typeshed evaluates platform-gated
7673
# stubs (e.g. fcntl) as if we were on Windows. Catches imports that
7774
# would only fail at runtime on Windows.
7875
- name: MyPy (Windows platform)
79-
run: |
80-
mypy --platform=win32 lib
76+
run: uv run mypy --platform=win32 lib
8177

8278
- name: Pyright
83-
run: |
84-
pyright lib
79+
run: uv run pyright lib
8580

8681
- name: Check Stubs Generation
8782
working-directory: .
88-
run: |
89-
bash .githooks/pre-push-python/stubs.sh
83+
run: bash .githooks/pre-push-python/stubs.sh
9084

9185
- name: Check Extras Generation
9286
working-directory: .
93-
run: |
94-
bash .githooks/pre-push-python/extras.sh
87+
run: bash .githooks/pre-push-python/extras.sh
9588

96-
- name: Pytest Unit Tests
89+
- name: Sync Stubs Mypy
90+
working-directory: python/lib
9791
run: |
98-
pytest -m "not integration"
92+
uv run stubtest \
93+
--mypy-config-file ../pyproject.toml \
94+
sift_client.resources.sync_stubs
95+
96+
test-python:
97+
needs: [changes]
98+
if: |
99+
always() &&
100+
(github.event_name != 'pull_request' || needs.changes.outputs.python == 'true')
101+
runs-on: ubuntu-latest
102+
defaults:
103+
run:
104+
working-directory: python
105+
strategy:
106+
fail-fast: false
107+
matrix:
108+
# Floor (3.8, per `requires-python`). This is the bug class local
109+
# checks miss (devs run a newer Python; modern syntax slips into
110+
# code that runs on 3.8). Ceiling testing is rarely useful in
111+
# practice — Python's deprecation cycle is long and the project's
112+
# stdlib usage is conservative — so it stays available locally as
113+
# `./scripts/dev test-ceiling` rather than running on every PR.
114+
# The full 3.8-3.14 install matrix lives in `python_build.yaml`.
115+
python-version: ["3.8"]
116+
steps:
117+
- name: Checkout code
118+
uses: actions/checkout@v4
119+
with:
120+
ref: ${{ github.event.pull_request.head.sha }}
121+
122+
- name: Set up uv
123+
uses: astral-sh/setup-uv@v6
124+
with:
125+
enable-cache: true
126+
127+
- name: Install dependencies (Python ${{ matrix.python-version }})
128+
run: uv sync --python ${{ matrix.python-version }} --extra dev-all
129+
130+
- name: Pytest Unit Tests (Python ${{ matrix.python-version }})
131+
run: uv run --python ${{ matrix.python-version }} pytest -m "not integration"
99132

100133
# Disabling integration tests that interact with Sift until a better solution is implemented
101134
# - name: Pytest Integration Tests
@@ -104,26 +137,20 @@ jobs:
104137
# SIFT_REST_URI: ${{ vars.SIFT_REST_URI }}
105138
# SIFT_API_KEY: ${{ secrets.SIFT_API_KEY }}
106139
# run: |
107-
# pytest -m "integration"
108-
109-
- name: Sync Stubs Mypy
110-
working-directory: python/lib
111-
run: |
112-
stubtest \
113-
--mypy-config-file ../pyproject.toml \
114-
sift_client.resources.sync_stubs
140+
# uv run --python ${{ matrix.python-version }} --no-project --with-editable '.[dev-all]' pytest -m "integration"
115141

116142
python-ci-status:
117143
if: always()
118-
needs: [changes, test-python]
144+
needs: [changes, static-checks, test-python]
119145
runs-on: ubuntu-latest
120146
steps:
121147
- name: Check result
122148
run: |
123-
result="${{ needs.test-python.result }}"
124-
if [[ "$result" == "success" || "$result" == "skipped" ]]; then
125-
echo "python-ci passed (test-python: $result)"
149+
static="${{ needs.static-checks.result }}"
150+
tests="${{ needs.test-python.result }}"
151+
if [[ ("$static" == "success" || "$static" == "skipped") && ("$tests" == "success" || "$tests" == "skipped") ]]; then
152+
echo "python-ci passed (static-checks: $static, test-python: $tests)"
126153
else
127-
echo "python-ci failed (test-python: $result)"
154+
echo "python-ci failed (static-checks: $static, test-python: $tests)"
128155
exit 1
129156
fi

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ ipython_config.py
301301
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
302302
#poetry.lock
303303

304+
# uv
305+
# uv.lock is committed for reproducible dev/test/CI runs.
306+
#uv.lock
307+
304308
# pdm
305309
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
306310
#pdm.lock

0 commit comments

Comments
 (0)