From 336b3cddc414553d09c5e0f754ac6e51a44e969a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Fri, 27 Feb 2026 12:55:24 +0100 Subject: [PATCH 1/5] ISSUE #3032: chore(uv): pin uv version to fix CI check-generated-code failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Context The check-generated-code CI job is failing on main and all PRs because astral-sh/setup-uv@v7 installs version: "latest" (currently uv 0.10.6), which produces different pylock.toml output than the committed files. The root cause is https://github.com/astral-sh/uv/pull/18081 (released in uv 0.10.5, 2026-02-23) which added wheel filtering to pylock.toml even in --universal mode. See https://github.com/opendatahub-io/notebooks/issues/3032. Fix: pin uv version in uv.toml using required-version, remove version: "latest" from all workflows so setup-uv auto-detects the pin, and regenerate pylocks. # Changes 1. Create uv.toml at repo root required-version = "==0.10.6" setup-uv@v7 auto-detects this and installs the pinned version. Locally, uv errors if the running version doesn't match. 2. Update .github/workflows/code-quality.yaml Two setup-uv blocks (lines 17-24 and 46-53): - Remove version: "latest" line - Rename step from "Install the latest version of uv" to "Install uv" 3. Update .github/workflows/docs.yaml One setup-uv block (lines 21-28): - Remove version: "latest" line - Rename step 4. Update .github/workflows/security.yaml One setup-uv block (lines 19-26): - Remove version: "latest" line - Rename step 5. Update .github/workflows/build-notebooks-TEMPLATE.yaml One setup-uv block (lines 304-311): - Remove version: "latest" line - Rename step 6. Update ci/generate_code.sh Line 4 has a fallback pip install "uv==0.9.6" — update to "uv==0.10.6" to match the pinned version. 7. Regenerate pylocks bash ci/generate_code.sh This regenerates the 6 affected pylock.toml files with uv 0.10.6 filtering. # Files modified - uv.toml (new) - .github/workflows/code-quality.yaml - .github/workflows/docs.yaml - .github/workflows/security.yaml - .github/workflows/build-notebooks-TEMPLATE.yaml - ci/generate_code.sh - 6 pylock.*.toml files (regenerated) # Verification # Check that uv reads the required-version and doesn't error uv version # Regenerate and verify no diff bash ci/generate_code.sh git diff --stat # should show no changes after regeneration --- .github/workflows/build-notebooks-TEMPLATE.yaml | 3 +-- .github/workflows/code-quality.yaml | 6 ++---- .github/workflows/docs.yaml | 3 +-- .github/workflows/piplock-renewal.yaml | 2 +- .github/workflows/security.yaml | 3 +-- ci/generate_code.sh | 2 +- scripts/pylocks_generator.sh | 4 +++- uv.toml | 1 + 8 files changed, 11 insertions(+), 13 deletions(-) create mode 100644 uv.toml diff --git a/.github/workflows/build-notebooks-TEMPLATE.yaml b/.github/workflows/build-notebooks-TEMPLATE.yaml index 3417a36a0c..6d8ecd2142 100644 --- a/.github/workflows/build-notebooks-TEMPLATE.yaml +++ b/.github/workflows/build-notebooks-TEMPLATE.yaml @@ -302,10 +302,9 @@ jobs: # region Pytest image tests # https://github.com/astral-sh/setup-uv - - name: Install the latest version of uv + - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "latest" python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index e102083039..6e4e9e40b7 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -15,10 +15,9 @@ jobs: - uses: actions/checkout@v6 # https://github.com/astral-sh/setup-uv - - name: Install the latest version of uv + - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "latest" python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" @@ -44,10 +43,9 @@ jobs: - uses: actions/checkout@v6 # https://github.com/astral-sh/setup-uv - - name: Install the latest version of uv + - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "latest" python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 4519bc4b70..e93437fad3 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -19,10 +19,9 @@ jobs: - uses: actions/checkout@v6 # https://github.com/astral-sh/setup-uv - - name: Install the latest version of uv + - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "latest" python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/piplock-renewal.yaml b/.github/workflows/piplock-renewal.yaml index f99ef2e0dc..0a788bdefc 100644 --- a/.github/workflows/piplock-renewal.yaml +++ b/.github/workflows/piplock-renewal.yaml @@ -75,7 +75,7 @@ jobs: python-version: '3.12' - name: Install uv - run: pip install "uv==0.9.27" + uses: astral-sh/setup-uv@v7 - name: Run make refresh-lock-files run: | diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 3b73e67544..c767d0060b 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -17,10 +17,9 @@ jobs: steps: # https://github.com/astral-sh/setup-uv - - name: Install the latest version of uv + - name: Install uv uses: astral-sh/setup-uv@v7 with: - version: "latest" activate-environment: false ignore-empty-workdir: true enable-cache: false diff --git a/ci/generate_code.sh b/ci/generate_code.sh index f9b63b23af..1a7dcc6d83 100755 --- a/ci/generate_code.sh +++ b/ci/generate_code.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -Eeuxo pipefail -uv --version || pip install "uv==0.9.6" +uv --version || pip install "uv==0.10.6" uv run scripts/dockerfile_fragments.py uv run manifests/tools/generate_kustomization.py diff --git a/scripts/pylocks_generator.sh b/scripts/pylocks_generator.sh index fe892978f7..cbcd36da7a 100755 --- a/scripts/pylocks_generator.sh +++ b/scripts/pylocks_generator.sh @@ -273,7 +273,9 @@ for TARGET_DIR in "${TARGET_DIRS[@]}"; do echo "➡️ Generating $(uppercase "$flavor") lock file..." fi - # The behavior has changed in uv 0.9.17 (https://github.com/astral-sh/uv/pull/16956) + # Tag filtering was added in uv 0.9.16 (https://github.com/astral-sh/uv/pull/16956) + # but bypassed in --universal mode. uv 0.10.5 (https://github.com/astral-sh/uv/pull/18081) + # now filters wheels by requires-python and marker disjointness even in --universal mode. # Documentation at https://docs.astral.sh/uv/reference/cli/#uv-pip-compile--python-platform says that # `--python-platform linux` is alias for `x86_64-unknown-linux-gnu`; we cannot use this to get a multiarch pylock # Let's use --universal temporarily, and in the future we can switch to using uv.lock diff --git a/uv.toml b/uv.toml new file mode 100644 index 0000000000..05eb4364ab --- /dev/null +++ b/uv.toml @@ -0,0 +1 @@ +required-version = "==0.10.6" From b3493a012c8fe41d864734f3ce69ed06822612d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Fri, 27 Feb 2026 13:28:20 +0100 Subject: [PATCH 2/5] ISSUE #3032: chore(uv): create a ./uv wrapper to run the correct version of uv --- .pre-commit-config.yaml | 16 +++++++++++----- README.md | 41 ++++++++++++++++++++++++++++++++++++----- uv | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 10 deletions(-) create mode 100755 uv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57a9698aca..712ee933c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,15 +2,21 @@ # https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#hooks-available repos: # https://docs.astral.sh/uv/guides/integration/pre-commit/ - - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.9.29 + # Using a local hook instead of uv-pre-commit so it goes through ./uv, + # which handles version mismatches without requiring the exact system uv. + - repo: local hooks: - id: uv-lock + name: uv-lock + entry: ./uv lock --locked + language: system + files: '(^uv\.lock$|^pyproject\.toml$|^uv\.toml$)' + pass_filenames: false # https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.0 + rev: v0.15.4 hooks: - - id: ruff + - id: ruff-check types_or: [python, pyi] args: [--fix] files: 'ci/.*|tests/.*' @@ -25,7 +31,7 @@ repos: - id: pyright name: Run Pyright on all files # entry: /bin/bash -c 'find. -name "*.py" | xargs pyright --pythonversion 3.12' - entry: uv run pyright --pythonversion 3.14 + entry: ./uv run pyright --pythonversion 3.14 pass_filenames: true types_or: [python, pyi] language: system diff --git a/README.md b/README.md index 4aa2e0c2df..4f4aaa29f0 100644 --- a/README.md +++ b/README.md @@ -62,17 +62,48 @@ Note: To ensure the GitHub Action runs successfully, users must add a `GH_ACCESS #### Prepare Python + uv + pytest env +This project pins its uv version in `uv.toml` (`required-version`). +Use the `./uv` wrapper script at the repo root — it reads the pinned +version and runs it via `uvx`, so your system uv version doesn't matter: + ```shell # Linux sudo dnf install python3.14 pip install --user uv -# MacOS +# macOS brew install python@3.14 uv -uv venv --python $(which python3.14) -uv sync --locked +./uv venv --python $(which python3.14) +./uv sync --locked ``` +
+Alternatives to ./uv + +The `./uv` wrapper is the recommended way, but you can also: + +- **Use `uvx` directly** with an explicit version: + ```shell + uvx uv@0.10.6 sync --locked + ``` +- **Use `uv tool run`** (equivalent, longer form): + ```shell + uv tool run uv@0.10.6 sync --locked + ``` +- **Install the exact version** so `uv` works directly: + ```shell + # Standalone installer (any OS) + curl -LsSf https://astral.sh/uv/0.10.6/install.sh | sh + # Or with pip + pip install uv==0.10.6 + ``` + +If your system uv matches the pinned version, you can use `uv` directly — +`required-version` in `uv.toml` will let it through. If it doesn't match, +uv exits with a clear error telling you which version is required. + +
+ #### Running Python selftests in Pytest By completing configuration in previous section, you are able to run any tests that don't need to start a container using following command: @@ -106,7 +137,7 @@ sudo dnf install podman systemctl --user start podman.service systemctl --user status podman.service systemctl --user status podman.socket -DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock uv run pytest tests/containers -m 'not openshift and not cuda and not rocm' --image quay.io/opendatahub/workbench-images@sha256:e98d19df346e7abb1fa3053f6d41f0d1fa9bab39e49b4cb90b510ca33452c2e4 +DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock ./uv run pytest tests/containers -m 'not openshift and not cuda and not rocm' --image quay.io/opendatahub/workbench-images@sha256:e98d19df346e7abb1fa3053f6d41f0d1fa9bab39e49b4cb90b510ca33452c2e4 # Mac OS brew install podman @@ -114,7 +145,7 @@ podman machine init podman machine set --rootful=false sudo podman-mac-helper install podman machine start -uv run pytest tests/containers -m 'not openshift' --image quay.io/opendatahub/workbench-images@sha256:e98d19df346e7abb1fa3053f6d41f0d1fa9bab39e49b4cb90b510ca33452c2e4 +./uv run pytest tests/containers -m 'not openshift' --image quay.io/opendatahub/workbench-images@sha256:e98d19df346e7abb1fa3053f6d41f0d1fa9bab39e49b4cb90b510ca33452c2e4 ``` When using lima on macOS, it might be useful to give yourself access to rootful podman socket diff --git a/uv b/uv new file mode 100755 index 0000000000..d65af6f179 --- /dev/null +++ b/uv @@ -0,0 +1,40 @@ +#!/usr/bin/env -S bash --norc --noprofile +# ./uv — run the project-pinned version of uv. +# +# Reads required-version from uv.toml and delegates via `uv tool run`. +# This avoids version mismatch errors when your system uv (e.g. Homebrew) +# differs from the version pinned for this project. +# +# Usage: +# ./uv sync +# ./uv run pytest +# ./uv pip compile ... +# +# The pinned version is cached by uvx after the first run. +# Parsing uses bash =~ instead of forking sed to avoid a subprocess. +# +# Bash with built-in regex is ~3x faster than Python for this task (hyperfine, +# 50 runs): bash+builtin 18.7ms, bash+sed 25.0ms, python 55.4ms. +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +while IFS= read -r line; do + if [[ "$line" =~ ^required-version\ *=\ *\"==([^\"]*)\" ]]; then + version="${BASH_REMATCH[1]}" + break + fi +done < "${SCRIPT_DIR}/uv.toml" + +if [[ -z "${version:-}" ]]; then + echo "error: could not read required-version from ${SCRIPT_DIR}/uv.toml" >&2 + exit 1 +fi + +# Fast path: use the system uv directly if it already matches the pinned version +if current=$(uv --version 2>/dev/null) && [[ "$current" == "uv $version "* ]]; then + exec uv "$@" +fi + +# Slow path: run the pinned version via uvx (downloaded and cached on first use) +exec uv tool run "uv@${version}" "$@" From eff53b5463b52b790392f185931236bc3c261688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Fri, 27 Feb 2026 13:56:43 +0100 Subject: [PATCH 3/5] fixes from coderabbitai review --- .github/workflows/build-notebooks-TEMPLATE.yaml | 1 + .github/workflows/code-quality.yaml | 2 ++ .github/workflows/docs.yaml | 1 + .github/workflows/piplock-renewal.yaml | 2 ++ .github/workflows/security.yaml | 7 ++++--- README.md | 3 ++- ci/generate_code.sh | 7 +++++-- uv | 2 +- 8 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-notebooks-TEMPLATE.yaml b/.github/workflows/build-notebooks-TEMPLATE.yaml index 6d8ecd2142..8f415a704a 100644 --- a/.github/workflows/build-notebooks-TEMPLATE.yaml +++ b/.github/workflows/build-notebooks-TEMPLATE.yaml @@ -305,6 +305,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: + version-file: uv.toml python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index 6e4e9e40b7..7e06063a1a 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -18,6 +18,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: + version-file: uv.toml python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" @@ -46,6 +47,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: + version-file: uv.toml python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index e93437fad3..9c370a12cc 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -22,6 +22,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: + version-file: uv.toml python-version: "3.14" enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/piplock-renewal.yaml b/.github/workflows/piplock-renewal.yaml index 0a788bdefc..fcef598933 100644 --- a/.github/workflows/piplock-renewal.yaml +++ b/.github/workflows/piplock-renewal.yaml @@ -76,6 +76,8 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 + with: + version-file: uv.toml - name: Run make refresh-lock-files run: | diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index c767d0060b..0d3dd5ff2f 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -16,17 +16,18 @@ jobs: security-events: write steps: + - name: Checkout code + uses: actions/checkout@v6 + # https://github.com/astral-sh/setup-uv - name: Install uv uses: astral-sh/setup-uv@v7 with: + version-file: uv.toml activate-environment: false ignore-empty-workdir: true enable-cache: false - - name: Checkout code - uses: actions/checkout@v6 - # Trivy does not support pylock.toml https://github.com/aquasecurity/trivy/discussions/9408 - run: find . -name pyproject.toml -execdir uv lock \; diff --git a/README.md b/README.md index 4f4aaa29f0..450d0b567c 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,8 @@ brew install python@3.14 uv
Alternatives to ./uv -The `./uv` wrapper is the recommended way, but you can also: +The `./uv` wrapper is the recommended way, but you can also +(replace `0.10.6` below with the version from `uv.toml`): - **Use `uvx` directly** with an explicit version: ```shell diff --git a/ci/generate_code.sh b/ci/generate_code.sh index 1a7dcc6d83..fb7234c0c5 100755 --- a/ci/generate_code.sh +++ b/ci/generate_code.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash set -Eeuxo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + uv --version || pip install "uv==0.10.6" -uv run scripts/dockerfile_fragments.py -uv run manifests/tools/generate_kustomization.py +"${REPO_ROOT}/uv" run scripts/dockerfile_fragments.py +"${REPO_ROOT}/uv" run manifests/tools/generate_kustomization.py bash scripts/pylocks_generator.sh diff --git a/uv b/uv index d65af6f179..145dfea0a7 100755 --- a/uv +++ b/uv @@ -32,7 +32,7 @@ if [[ -z "${version:-}" ]]; then fi # Fast path: use the system uv directly if it already matches the pinned version -if current=$(uv --version 2>/dev/null) && [[ "$current" == "uv $version "* ]]; then +if current=$(uv --version 2>/dev/null) && [[ "$current" == "uv $version" || "$current" == "uv $version "* ]]; then exec uv "$@" fi From 013a25c5416dba37702f3907fe537d114dae31f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Fri, 27 Feb 2026 15:28:04 +0100 Subject: [PATCH 4/5] fixes from coderabbitai review --- scripts/pylocks_generator.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/pylocks_generator.sh b/scripts/pylocks_generator.sh index cbcd36da7a..742f56c532 100755 --- a/scripts/pylocks_generator.sh +++ b/scripts/pylocks_generator.sh @@ -52,6 +52,7 @@ MAIN_DIRS=("jupyter" "runtimes" "rstudio" "codeserver") # CVE constraints file - applied to all lock file generations SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" +UV="${ROOT_DIR}/uv" CVE_CONSTRAINTS_FILE="$ROOT_DIR/dependencies/cve-constraints.txt" # ---------------------------- @@ -95,7 +96,7 @@ if ! command -v uv &>/dev/null; then fi UV_MIN_VERSION="0.4.0" -UV_VERSION=$(uv --version 2>/dev/null | awk '{print $2}' || echo "0.0.0") +UV_VERSION=$("$UV" --version 2>/dev/null | awk '{print $2}' || echo "0.0.0") version_ge() { [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ] @@ -297,7 +298,7 @@ for TARGET_DIR in "${TARGET_DIRS[@]}"; do set +e # shellcheck disable=SC2086 - uv pip compile pyproject.toml \ + "$UV" pip compile pyproject.toml \ --output-file "$output" \ --format pylock.toml \ --generate-hashes \ From 781d4d3ee6d38dfac2f14abbc7c045c712add491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Fri, 27 Feb 2026 15:34:47 +0100 Subject: [PATCH 5/5] fixes from coderabbitai review 1. Pre-flight check (lines 93-100): Added explicit validation that $UV wrapper exists and is executable before the existing command -v uv check. This prevents a misleading version error if the wrapper is missing. 2. Constraints flag (lines 291-316): Changed constraints_flag from a plain string to a bash array (local -a constraints_flag=()), and expanded it as "${constraints_flag[@]}" in the pip compile invocation. This avoids word-splitting on paths containing spaces. --- scripts/pylocks_generator.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/pylocks_generator.sh b/scripts/pylocks_generator.sh index 742f56c532..91492b8398 100755 --- a/scripts/pylocks_generator.sh +++ b/scripts/pylocks_generator.sh @@ -90,6 +90,11 @@ read_conf_value() { # ---------------------------- # PRE-FLIGHT CHECK # ---------------------------- +if [[ ! -x "$UV" ]]; then + error "Expected uv wrapper at '$UV' but it is missing or not executable." + exit 1 +fi + if ! command -v uv &>/dev/null; then error "uv command not found. Please install uv: https://github.com/astral-sh/uv" exit 1 @@ -288,12 +293,12 @@ for TARGET_DIR in "${TARGET_DIRS[@]}"; do # Build constraints flag if CVE constraints file exists # Use relative path to avoid absolute paths in pylock.toml headers # (which would differ between CI and local environments) - local constraints_flag="" + local -a constraints_flag=() if [[ -f "$CVE_CONSTRAINTS_FILE" ]]; then local relative_constraints # Use Python for cross-platform relative path computation (realpath --relative-to is GNU-only) relative_constraints=$(python3 -c "import os; print(os.path.relpath('$CVE_CONSTRAINTS_FILE', '$PWD'))") - constraints_flag="--constraints=$relative_constraints" + constraints_flag=(--constraints "$relative_constraints") fi set +e @@ -312,7 +317,7 @@ for TARGET_DIR in "${TARGET_DIRS[@]}"; do --no-emit-package odh-notebooks-meta-runtime-datascience-deps \ --no-emit-package odh-notebooks-meta-workbench-datascience-deps \ $UPGRADE_FLAG \ - $constraints_flag \ + "${constraints_flag[@]}" \ $index local status=$? set -e