From 93b864557b5c8eb03249e85a47b1cee253257708 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 06:45:41 -0500 Subject: [PATCH 01/19] PYTHON-6091 Pin a consistent uv binary version in CI and locally --- .evergreen/scripts/configure-env.sh | 34 +++-------- .evergreen/scripts/install-dependencies.sh | 66 +++++++++++++++++++--- .evergreen/scripts/setup-dev-env.sh | 13 +++-- .github/workflows/test-python.yml | 16 +++--- CONTRIBUTING.md | 8 +++ justfile | 5 ++ pyproject.toml | 4 ++ 7 files changed, 99 insertions(+), 47 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 8dc328aab3..572960e304 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -19,15 +19,17 @@ UV_CACHE_DIR=$PROJECT_DIRECTORY/.local/uv/cache DRIVERS_TOOLS_BINARIES="$DRIVERS_TOOLS/.bin" MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" -# On Evergreen jobs, "CI" will be set, and we don't want to write to $HOME. +# On Evergreen jobs, "CI" will be set, and we don't want to write to $HOME. Put +# our binaries (uv, just, ...) in a task-local dir we control, independent of the +# drivers-tools tree. if [ "${CI:-}" == "true" ]; then - PYMONGO_BIN_DIR=${DRIVERS_TOOLS_BINARIES:-} + PYMONGO_BIN_DIR="${TMPDIR:-/tmp}/pymongo-bin" # We want to use a path that's already on PATH on spawn hosts. else PYMONGO_BIN_DIR=$HOME/cli_bin fi -PATH_EXT="$MONGODB_BINARIES:$DRIVERS_TOOLS_BINARIES:$PYMONGO_BIN_DIR:\$PATH" +PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin @@ -38,7 +40,9 @@ if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin UV_CACHE_DIR=$(cygpath -m "$UV_CACHE_DIR") DRIVERS_TOOLS_BINARIES=$(cygpath -m "$DRIVERS_TOOLS_BINARIES") MONGODB_BINARIES=$(cygpath -m "$MONGODB_BINARIES") - PYMONGO_BIN_DIR=$(cygpath -m "$PYMONGO_BIN_DIR") + # Keep PYMONGO_BIN_DIR in cygwin form so bash can search it on PATH; native + # uv gets the Windows form (via cygpath -m) inside install-dependencies.sh. + PYMONGO_BIN_DIR=$(cygpath -u "$PYMONGO_BIN_DIR") fi SCRIPT_DIR="$PROJECT_DIRECTORY/.evergreen/scripts" @@ -90,25 +94,3 @@ cat < expansion.yml DRIVERS_TOOLS: "$DRIVERS_TOOLS" PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" EOT - -# If the toolchain is available, symlink binaries to the bin dir. This has to be done -# after drivers-tools is cloned, since we might be using its binary dir. -_bin_path="" -if [ "Windows_NT" == "${OS:-}" ]; then - _bin_path="/cygdrive/c/Python/Current/Scripts" -elif [ "$(uname -s)" == "Darwin" ]; then - _bin_path="/Library/Frameworks/Python.Framework/Versions/Current/bin" -else - _bin_path="/opt/python/Current/bin" -fi -if [ -d "${_bin_path}" ]; then - _suffix="" - if [ "Windows_NT" == "${OS:-}" ]; then - _suffix=".exe" - fi - echo "Symlinking binaries from toolchain" - mkdir -p $PYMONGO_BIN_DIR - ln -s ${_bin_path}/just${_suffix} $PYMONGO_BIN_DIR/just${_suffix} - ln -s ${_bin_path}/uv${_suffix} $PYMONGO_BIN_DIR/uv${_suffix} - ln -s ${_bin_path}/uvx${_suffix} $PYMONGO_BIN_DIR/uvx${_suffix} -fi diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 7f943d7d00..e7b9e5f23b 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -1,6 +1,6 @@ #!/bin/bash # Install the necessary dependencies. -set -eu +set -euo pipefail HERE=$(dirname ${BASH_SOURCE:-$0}) HERE="$( cd -- "$HERE" > /dev/null 2>&1 && pwd )" @@ -15,23 +15,75 @@ fi if [ -z "${PYMONGO_BIN_DIR:-}" ]; then PYMONGO_BIN_DIR="$HOME/.local/bin" fi +# uv.exe on Windows needs Windows-style paths, while bash uses the cygwin form. +# Keep PYMONGO_BIN_DIR in the form PATH uses and give uv the Windows form. +if [ "Windows_NT" = "${OS:-}" ]; then + export UV_TOOL_BIN_DIR="$(cygpath -m "$PYMONGO_BIN_DIR")" + export UV_TOOL_DIR="$(cygpath -m "${UV_TOOL_DIR:-$(dirname "$PYMONGO_BIN_DIR")/uv-tools}")" +else + export UV_TOOL_BIN_DIR="$PYMONGO_BIN_DIR" +fi + +# Locate the Python toolchain's binary dir, so we can prefer its uv and just. +_toolchain_bin="" +if [ "Windows_NT" = "${OS:-}" ]; then + _toolchain_bin="/cygdrive/c/Python/Current/Scripts" +elif [ "$(uname -s)" = "Darwin" ]; then + _toolchain_bin="/Library/Frameworks/Python.Framework/Versions/Current/bin" +else + _toolchain_bin="/opt/python/Current/bin" +fi -# Ensure uv is installed. +# Prefer the toolchain's uv as a bootstrap when uv is not already on PATH, so we +# do not fall back to installing from astral. +if ! command -v uv &>/dev/null; then + if [ -x "$_toolchain_bin/uv" ] || [ -x "$_toolchain_bin/uv.exe" ]; then + echo "Found uv in the toolchain at $_toolchain_bin" + export PATH="$_toolchain_bin:$PATH" + fi +fi + +# Ensure uv is available (bootstrap if absent). if ! command -v uv &>/dev/null; then _BIN_DIR=$PYMONGO_BIN_DIR mkdir -p ${_BIN_DIR} - echo "Installing uv..." + echo "uv not found on PATH; installing the latest uv from astral..." curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$_BIN_DIR" INSTALLER_NO_MODIFY_PATH=1 sh if [ "Windows_NT" = "${OS:-}" ]; then chmod +x "$(cygpath -u $_BIN_DIR)/uv.exe" fi export PATH="$PYMONGO_BIN_DIR:$PATH" - echo "Installing uv... done." fi -# Ensure just is installed. -if ! command -v just &>/dev/null; then - uv tool install rust-just +# Pin the uv binary to the version in pyproject.toml's [tool.uv] required-version. +# Run the current uv directly: it writes into PYMONGO_BIN_DIR (a different +# location), so nothing running is overwritten and no temp copy is needed. +_uv_bin="$(command -v uv 2>/dev/null || true)" +if [ -n "$_uv_bin" ]; then + _uv_pin="$(awk -F'"' '/^[[:space:]]*required-version[[:space:]]*=/{print $2}' pyproject.toml)" + _uv_vers="$(uv --version 2>/dev/null | head -1 | awk '{print $2}' | sed 's/^v//')" + if [ "uv${_uv_pin}" != "uv==${_uv_vers}" ]; then + rm -f "$PYMONGO_BIN_DIR/uv" "$PYMONGO_BIN_DIR/uv.exe" \ + "$PYMONGO_BIN_DIR/uvx" "$PYMONGO_BIN_DIR/uvx.exe" + uv tool install -q --force --from "uv${_uv_pin}" uv + echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" + fi +fi + +# Use just from the toolchain if available, otherwise install it. It must live in +# our bin dir to be on PATH for callers; copying it keeps the toolchain's just. +if [ ! -x "$PYMONGO_BIN_DIR/just" ] && [ ! -x "$PYMONGO_BIN_DIR/just.exe" ]; then + if [ -x "$_toolchain_bin/just" ]; then + echo "Using just from the toolchain" + cp "$_toolchain_bin/just" "$PYMONGO_BIN_DIR/just" + chmod +x "$PYMONGO_BIN_DIR/just" + elif [ -x "$_toolchain_bin/just.exe" ]; then + echo "Using just from the toolchain" + cp "$_toolchain_bin/just.exe" "$PYMONGO_BIN_DIR/just.exe" + chmod +x "$PYMONGO_BIN_DIR/just.exe" + else + uv tool install rust-just + fi fi popd > /dev/null diff --git a/.evergreen/scripts/setup-dev-env.sh b/.evergreen/scripts/setup-dev-env.sh index e58d6210fe..5a1794ee71 100755 --- a/.evergreen/scripts/setup-dev-env.sh +++ b/.evergreen/scripts/setup-dev-env.sh @@ -16,23 +16,24 @@ if [ -f $HERE/test-env.sh ]; then . $HERE/test-env.sh fi -# Handle the value for UV_PYTHON. -. $HERE/setup-uv-python.sh - # Ensure dependencies are installed. bash $HERE/install-dependencies.sh -# Re-source env.sh: install-dependencies.sh may have appended to it, e.g. when it -# had to install Python on an image that lacks a toolchain. +# Re-source env.sh in case a dependency install updated it, e.g. on a host +# without a toolchain where uv was installed into a shared bin dir. if [ -f $HERE/env.sh ]; then . $HERE/env.sh fi -# Add the default install path to the path if needed. +# Add the default install path to the path before configuring uv: setup-uv-python.sh +# calls uv, and on a machine without env.sh the tool dir must already be on PATH. if [ -z "${PYMONGO_BIN_DIR:-}" ]; then export PATH="$PATH:$HOME/.local/bin" fi +# Handle the value for UV_PYTHON. +. $HERE/setup-uv-python.sh + # Only run the next part if not running on CI. if [ -z "${CI:-}" ]; then # Set up venv, making sure c extensions build unless disabled. diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 73d5513a16..f19a783d6f 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "3.10" # Runs before the install so a stale lock fails in seconds rather than @@ -80,7 +80,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: # The beta Python here relies on the action's prerelease default. python-version: ${{ matrix.python-version }} @@ -102,7 +102,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "3.10" - id: setup-mongodb @@ -127,7 +127,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "3.10" - name: Install dependencies @@ -149,7 +149,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "3.10" - name: Install dependencies @@ -168,7 +168,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "${{matrix.python}}" - name: Install dependencies @@ -185,7 +185,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "3.10" - name: Install dependencies @@ -271,7 +271,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 with: python-version: "3.9" - id: setup-mongodb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ef90224c2..980a18a76d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -199,6 +199,10 @@ the pages will re-render and the browser will automatically refresh. - Run `just install` to set a local virtual environment, or you can manually create a virtual environment and run `pytest` directly. If you want to use a specific version of Python, set `UV_PYTHON` before running `just install`. + + `just install` installs the pinned version of `uv` (from `[tool.uv] required-version`) into `$HOME/.local/bin`, + so make sure that directory is on your `PATH` (it usually is). If your `uv` is a different version, `uv` fails + fast and tells you how to update. - Ensure you have started the appropriate Mongo Server(s). You can run `just run-server` with optional args to set up the server. All given options will be passed to [`run-mongodb.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-mongodb.sh). Run `$DRIVERS_TOOLS/.evergreen/run-mongodb.sh start -h` @@ -419,6 +423,10 @@ tasks are host-agnostic. supported version of Python and use that. This ensures a consistent behavior across host types that do not have the Python toolchain (e.g. Azure VMs), by having a known version of Python with the build headers (`Python.h`) needed to build the C extensions. + - The uv binary version is pinned once in `[tool.uv] required-version` in `pyproject.toml`. + `.evergreen/scripts/install-dependencies.sh` installs it with `uv tool install`, uv enforces it locally, and + `astral-sh/setup-uv` reads it on GitHub. Bump it manually when a newer uv is needed. If uv cannot find the + requested Python, it installs it; if that fails, the task fails. - Regenerate the test variants and tasks using `pre-commit run --all-files generate-config`. - Make sure to add instructions for running the test suite to `CONTRIBUTING.md`. diff --git a/justfile b/justfile index df678cdbac..09f7fd6f33 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,11 @@ # See https://just.systems/man/en/ for instructions set shell := ["bash", "-c"] +# Put the dir that holds the pinned uv first on PATH for every recipe. On CI that +# is PYMONGO_BIN_DIR (so `just install`'s uv wins over a brew/system one); locally +# it is ~/.local/bin. This matters only when a different uv is earlier on PATH. +export PATH := env_var_or_default('PYMONGO_BIN_DIR', home_directory() + '/.local/bin') + ':' + env_var('PATH') + # Commonly used command segments. typing_run := "uv run --group typing --extra aws --extra encryption --with numpy --extra ocsp --extra snappy --extra test --extra zstd" docs_run := "uv run --extra docs" diff --git a/pyproject.toml b/pyproject.toml index 346df2f427..a41e230f0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,10 @@ Source = "https://github.com/mongodb/mongo-python-driver" Tracker = "https://jira.mongodb.org/projects/PYTHON/issues" [tool.uv] +# Pin the uv binary version across local dev, GitHub Actions, and Evergreen. +# uv enforces this locally and astral-sh/setup-uv reads it on GitHub; Evergreen +# pins it in setup-uv-python.sh. Bump manually when a newer uv is needed. +required-version = "==0.12.12" # boto3 dropped Python 3.9 support in 1.43, so the universal lock forks at # 3.10. Without a floor the pre-3.10 fork back-solves to boto3 1.7.84 (2018), # whose vendored six and invalid escape sequences break test collection. No From 10fc7a27c4e5941f4288e3f0654ff57d8edfb97b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 06:56:45 -0500 Subject: [PATCH 02/19] PYTHON-6091 Drop unneeded test-python action bump (uv auto-reads required-version) --- .github/workflows/test-python.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index f19a783d6f..73d5513a16 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "3.10" # Runs before the install so a stale lock fails in seconds rather than @@ -80,7 +80,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: # The beta Python here relies on the action's prerelease default. python-version: ${{ matrix.python-version }} @@ -102,7 +102,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "3.10" - id: setup-mongodb @@ -127,7 +127,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "3.10" - name: Install dependencies @@ -149,7 +149,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "3.10" - name: Install dependencies @@ -168,7 +168,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "${{matrix.python}}" - name: Install dependencies @@ -185,7 +185,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "3.10" - name: Install dependencies @@ -271,7 +271,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@f137fdd28483af14ebf466ebc5aa789fbf867218 # v3.0.5 + uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 with: python-version: "3.9" - id: setup-mongodb From 239c968c5005321e636ee7a5f784af840e1abfae Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 07:33:48 -0500 Subject: [PATCH 03/19] PYTHON-6091 Address review: always install pinned uv and fix bin dir/comment --- .evergreen/scripts/configure-env.sh | 3 ++- .evergreen/scripts/install-dependencies.sh | 15 +++++++-------- CONTRIBUTING.md | 4 ++-- pyproject.toml | 5 +++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 572960e304..adde681285 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -68,7 +68,8 @@ export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" export CARGO_HOME="$CARGO_HOME" export UV_TOOL_DIR="$UV_TOOL_DIR" export UV_CACHE_DIR="$UV_CACHE_DIR" -export UV_TOOL_BIN_DIR="$DRIVERS_TOOLS_BINARIES" +# Send uv tool installs into our own bin dir, alongside the pinned uv/just. +export UV_TOOL_BIN_DIR="$PYMONGO_BIN_DIR" export PYMONGO_BIN_DIR="$PYMONGO_BIN_DIR" export PATH="$PATH_EXT" # shellcheck disable=SC2154 diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index e7b9e5f23b..ce6f812492 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -23,6 +23,7 @@ if [ "Windows_NT" = "${OS:-}" ]; then else export UV_TOOL_BIN_DIR="$PYMONGO_BIN_DIR" fi +mkdir -p "$PYMONGO_BIN_DIR" # Locate the Python toolchain's binary dir, so we can prefer its uv and just. _toolchain_bin="" @@ -57,17 +58,15 @@ fi # Pin the uv binary to the version in pyproject.toml's [tool.uv] required-version. # Run the current uv directly: it writes into PYMONGO_BIN_DIR (a different -# location), so nothing running is overwritten and no temp copy is needed. +# location), so nothing running is overwritten and no temp copy is needed. Always +# run it (idempotent) so uv lands in our bin dir even when it already matches. _uv_bin="$(command -v uv 2>/dev/null || true)" if [ -n "$_uv_bin" ]; then _uv_pin="$(awk -F'"' '/^[[:space:]]*required-version[[:space:]]*=/{print $2}' pyproject.toml)" - _uv_vers="$(uv --version 2>/dev/null | head -1 | awk '{print $2}' | sed 's/^v//')" - if [ "uv${_uv_pin}" != "uv==${_uv_vers}" ]; then - rm -f "$PYMONGO_BIN_DIR/uv" "$PYMONGO_BIN_DIR/uv.exe" \ - "$PYMONGO_BIN_DIR/uvx" "$PYMONGO_BIN_DIR/uvx.exe" - uv tool install -q --force --from "uv${_uv_pin}" uv - echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" - fi + rm -f "$PYMONGO_BIN_DIR/uv" "$PYMONGO_BIN_DIR/uv.exe" \ + "$PYMONGO_BIN_DIR/uvx" "$PYMONGO_BIN_DIR/uvx.exe" + uv tool install -q --force --from "uv${_uv_pin}" uv + echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" fi # Use just from the toolchain if available, otherwise install it. It must live in diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 980a18a76d..e9608aaeb4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -201,8 +201,8 @@ the pages will re-render and the browser will automatically refresh. version of Python, set `UV_PYTHON` before running `just install`. `just install` installs the pinned version of `uv` (from `[tool.uv] required-version`) into `$HOME/.local/bin`, - so make sure that directory is on your `PATH` (it usually is). If your `uv` is a different version, `uv` fails - fast and tells you how to update. + so make sure that directory is on your `PATH` (it usually is). If a project `uv` command (e.g. `just test`) runs + with a different `uv` version, `uv` fails fast and tells you how to update. - Ensure you have started the appropriate Mongo Server(s). You can run `just run-server` with optional args to set up the server. All given options will be passed to [`run-mongodb.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-mongodb.sh). Run `$DRIVERS_TOOLS/.evergreen/run-mongodb.sh start -h` diff --git a/pyproject.toml b/pyproject.toml index a41e230f0c..0323e044c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,8 +48,9 @@ Tracker = "https://jira.mongodb.org/projects/PYTHON/issues" [tool.uv] # Pin the uv binary version across local dev, GitHub Actions, and Evergreen. -# uv enforces this locally and astral-sh/setup-uv reads it on GitHub; Evergreen -# pins it in setup-uv-python.sh. Bump manually when a newer uv is needed. +# uv enforces this locally, astral-sh/setup-uv reads it on GitHub, and +# install-dependencies.sh installs it in Evergreen. Bump manually when a newer uv +# is needed. required-version = "==0.12.12" # boto3 dropped Python 3.9 support in 1.43, so the universal lock forks at # 3.10. Without a floor the pre-3.10 fork back-solves to boto3 1.7.84 (2018), From 097964a38de8a2521f768b04d229088bf99817c5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 07:58:18 -0500 Subject: [PATCH 04/19] PYTHON-6091 Fix shellcheck SC2155 in Windows uv path handling --- .evergreen/scripts/install-dependencies.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index ce6f812492..24a2578d49 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -18,8 +18,10 @@ fi # uv.exe on Windows needs Windows-style paths, while bash uses the cygwin form. # Keep PYMONGO_BIN_DIR in the form PATH uses and give uv the Windows form. if [ "Windows_NT" = "${OS:-}" ]; then - export UV_TOOL_BIN_DIR="$(cygpath -m "$PYMONGO_BIN_DIR")" - export UV_TOOL_DIR="$(cygpath -m "${UV_TOOL_DIR:-$(dirname "$PYMONGO_BIN_DIR")/uv-tools}")" + _uv_tool_bin="$(cygpath -m "$PYMONGO_BIN_DIR")" + _uv_tool_dir="$(cygpath -m "${UV_TOOL_DIR:-$(dirname "$PYMONGO_BIN_DIR")/uv-tools}")" + export UV_TOOL_BIN_DIR="$_uv_tool_bin" + export UV_TOOL_DIR="$_uv_tool_dir" else export UV_TOOL_BIN_DIR="$PYMONGO_BIN_DIR" fi From e484fd15777e6e175228c49cd6bfb8f8a22b9945 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 12:41:31 -0500 Subject: [PATCH 05/19] PYTHON-6091 Source PATH from env.sh; only prepend local bin dir when it is absent --- justfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/justfile b/justfile index 09f7fd6f33..004ae38995 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,10 @@ # See https://just.systems/man/en/ for instructions set shell := ["bash", "-c"] -# Put the dir that holds the pinned uv first on PATH for every recipe. On CI that -# is PYMONGO_BIN_DIR (so `just install`'s uv wins over a brew/system one); locally -# it is ~/.local/bin. This matters only when a different uv is earlier on PATH. -export PATH := env_var_or_default('PYMONGO_BIN_DIR', home_directory() + '/.local/bin') + ':' + env_var('PATH') +# On CI, env.sh (sourced by just.sh) already puts the bin dir that holds the +# pinned uv first on PATH, so only prepend ~/.local/bin locally, where there is +# no env.sh. This lets `just` find the pinned uv after `just install`. +export PATH := if env_var_or_default('PYMONGO_BIN_DIR', '') != '' { env_var('PATH') } else { home_directory() + '/.local/bin:' + env_var('PATH') } # Commonly used command segments. typing_run := "uv run --group typing --extra aws --extra encryption --with numpy --extra ocsp --extra snappy --extra test --extra zstd" From 17d616b032c9976b9ed43bdf00373bffe057769d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 12:58:56 -0500 Subject: [PATCH 06/19] PYTHON-6091 Use env.sh on CI and ~/.local/bin on spawn/local hosts --- .evergreen/scripts/configure-env.sh | 5 +++-- .evergreen/scripts/setup-system.sh | 10 ++++++++++ justfile | 5 ----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index adde681285..398447207a 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -24,9 +24,10 @@ MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" # drivers-tools tree. if [ "${CI:-}" == "true" ]; then PYMONGO_BIN_DIR="${TMPDIR:-/tmp}/pymongo-bin" -# We want to use a path that's already on PATH on spawn hosts. +# On spawn hosts and local dev, use the conventional ~/.local/bin which tools on +# the PATH (or the shell rc) can find. else - PYMONGO_BIN_DIR=$HOME/cli_bin + PYMONGO_BIN_DIR=$HOME/.local/bin fi PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" diff --git a/.evergreen/scripts/setup-system.sh b/.evergreen/scripts/setup-system.sh index bd7e2dd4bc..2ba8bba706 100755 --- a/.evergreen/scripts/setup-system.sh +++ b/.evergreen/scripts/setup-system.sh @@ -15,6 +15,16 @@ if [ -z "${CI:-}" ]; then bash $HERE/setup-dev-env.sh fi +# On spawn hosts / local dev (no Evergreen CI or GitHub Actions) the pinned uv and +# just live in ~/.local/bin, so make sure that is on PATH, adding it to .bashrc if +# it is not already. +if [ "${CI:-}" != "true" ] && [ "${GITHUB_ACTIONS:-}" != "true" ]; then + case ":$PATH:" in + *":$HOME/.local/bin:"*) ;; + *) echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" ;; + esac +fi + # Enable core dumps if enabled on the machine # Copied from https://github.com/mongodb/mongo/blob/master/etc/evergreen.yml if [ -f /proc/self/coredump_filter ]; then diff --git a/justfile b/justfile index 004ae38995..df678cdbac 100644 --- a/justfile +++ b/justfile @@ -1,11 +1,6 @@ # See https://just.systems/man/en/ for instructions set shell := ["bash", "-c"] -# On CI, env.sh (sourced by just.sh) already puts the bin dir that holds the -# pinned uv first on PATH, so only prepend ~/.local/bin locally, where there is -# no env.sh. This lets `just` find the pinned uv after `just install`. -export PATH := if env_var_or_default('PYMONGO_BIN_DIR', '') != '' { env_var('PATH') } else { home_directory() + '/.local/bin:' + env_var('PATH') } - # Commonly used command segments. typing_run := "uv run --group typing --extra aws --extra encryption --with numpy --extra ocsp --extra snappy --extra test --extra zstd" docs_run := "uv run --extra docs" From 81abd31a3f5a61e5a1dad8aa14f82d80a840c9fb Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 13:08:49 -0500 Subject: [PATCH 07/19] PYTHON-6091 Note non-CI hosts include VMs in bin dir comment --- .evergreen/scripts/configure-env.sh | 4 ++-- .evergreen/scripts/setup-system.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 398447207a..e68907175d 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -24,8 +24,8 @@ MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" # drivers-tools tree. if [ "${CI:-}" == "true" ]; then PYMONGO_BIN_DIR="${TMPDIR:-/tmp}/pymongo-bin" -# On spawn hosts and local dev, use the conventional ~/.local/bin which tools on -# the PATH (or the shell rc) can find. +# On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev), use the +# conventional ~/.local/bin which tools on the PATH (or the shell rc) can find. else PYMONGO_BIN_DIR=$HOME/.local/bin fi diff --git a/.evergreen/scripts/setup-system.sh b/.evergreen/scripts/setup-system.sh index 2ba8bba706..eca8375a53 100755 --- a/.evergreen/scripts/setup-system.sh +++ b/.evergreen/scripts/setup-system.sh @@ -15,9 +15,9 @@ if [ -z "${CI:-}" ]; then bash $HERE/setup-dev-env.sh fi -# On spawn hosts / local dev (no Evergreen CI or GitHub Actions) the pinned uv and -# just live in ~/.local/bin, so make sure that is on PATH, adding it to .bashrc if -# it is not already. +# On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev) the pinned +# uv and just live in ~/.local/bin, so make sure that is on PATH, adding it to +# .bashrc if it is not already. if [ "${CI:-}" != "true" ] && [ "${GITHUB_ACTIONS:-}" != "true" ]; then case ":$PATH:" in *":$HOME/.local/bin:"*) ;; From 04c1ef2c6e88e5bcb5070b803b82e565f79bd22c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 13:44:19 -0500 Subject: [PATCH 08/19] PYTHON-6091 Fix uv pin on no-toolchain hosts; avoid overwriting running uv --- .evergreen/scripts/install-dependencies.sh | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 24a2578d49..ac6b54dc7c 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -60,15 +60,26 @@ fi # Pin the uv binary to the version in pyproject.toml's [tool.uv] required-version. # Run the current uv directly: it writes into PYMONGO_BIN_DIR (a different -# location), so nothing running is overwritten and no temp copy is needed. Always -# run it (idempotent) so uv lands in our bin dir even when it already matches. +# location), so nothing running is overwritten. If the running uv is already our +# pinned bin-dir uv, skip the install to avoid overwriting it (Windows refuses to +# overwrite a running executable); otherwise install so the pin lands in the bin +# dir even when the discovered uv already matches. _uv_bin="$(command -v uv 2>/dev/null || true)" if [ -n "$_uv_bin" ]; then _uv_pin="$(awk -F'"' '/^[[:space:]]*required-version[[:space:]]*=/{print $2}' pyproject.toml)" - rm -f "$PYMONGO_BIN_DIR/uv" "$PYMONGO_BIN_DIR/uv.exe" \ - "$PYMONGO_BIN_DIR/uvx" "$PYMONGO_BIN_DIR/uvx.exe" - uv tool install -q --force --from "uv${_uv_pin}" uv - echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" + case "$_uv_bin" in + "$PYMONGO_BIN_DIR"/*) + _uv_vers="$(uv --version 2>/dev/null | head -1 | awk '{print $2}' | sed 's/^v//')" + if [ "uv${_uv_pin}" != "uv==${_uv_vers}" ]; then + uv tool install -q --force --from "uv${_uv_pin}" uv + echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" + fi + ;; + *) + uv tool install -q --force --from "uv${_uv_pin}" uv + echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" + ;; + esac fi # Use just from the toolchain if available, otherwise install it. It must live in From 15760ac7f756fdbe89d2f93ee67d3fd5de68e825 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 16:54:32 -0500 Subject: [PATCH 09/19] PYTHON-6091 Address Copilot: prepend PATH, persist .bashrc entry, avoid Windows uv self-overwrite --- .evergreen/scripts/install-dependencies.sh | 7 ++++++- .evergreen/scripts/setup-dev-env.sh | 3 ++- .evergreen/scripts/setup-system.sh | 11 +++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index ac6b54dc7c..6fcad678dc 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -71,7 +71,12 @@ if [ -n "$_uv_bin" ]; then "$PYMONGO_BIN_DIR"/*) _uv_vers="$(uv --version 2>/dev/null | head -1 | awk '{print $2}' | sed 's/^v//')" if [ "uv${_uv_pin}" != "uv==${_uv_vers}" ]; then - uv tool install -q --force --from "uv${_uv_pin}" uv + # The running uv lives in our bin dir and is not the pin, so run the + # install from a copy: Windows will not overwrite a running executable. + _uv_tmp="$(mktemp -d)/$(basename "$_uv_bin")" + cp "$_uv_bin" "$_uv_tmp" && chmod +x "$_uv_tmp" + "$_uv_tmp" tool install -q --force --from "uv${_uv_pin}" uv + rm -rf "$(dirname "$_uv_tmp")" echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" fi ;; diff --git a/.evergreen/scripts/setup-dev-env.sh b/.evergreen/scripts/setup-dev-env.sh index 5a1794ee71..fecee90890 100755 --- a/.evergreen/scripts/setup-dev-env.sh +++ b/.evergreen/scripts/setup-dev-env.sh @@ -27,8 +27,9 @@ fi # Add the default install path to the path before configuring uv: setup-uv-python.sh # calls uv, and on a machine without env.sh the tool dir must already be on PATH. +# Prepend it so the pinned uv/just win over a different install earlier on PATH. if [ -z "${PYMONGO_BIN_DIR:-}" ]; then - export PATH="$PATH:$HOME/.local/bin" + export PATH="$HOME/.local/bin:$PATH" fi # Handle the value for UV_PYTHON. diff --git a/.evergreen/scripts/setup-system.sh b/.evergreen/scripts/setup-system.sh index eca8375a53..d3271b495a 100755 --- a/.evergreen/scripts/setup-system.sh +++ b/.evergreen/scripts/setup-system.sh @@ -16,13 +16,12 @@ if [ -z "${CI:-}" ]; then fi # On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev) the pinned -# uv and just live in ~/.local/bin, so make sure that is on PATH, adding it to -# .bashrc if it is not already. +# uv and just live in ~/.local/bin, so make sure a login shell finds them by +# adding the entry to .bashrc if it is not already there. env.sh (sourced earlier) +# already puts it on PATH, but that PATH does not persist past this SSH session. if [ "${CI:-}" != "true" ] && [ "${GITHUB_ACTIONS:-}" != "true" ]; then - case ":$PATH:" in - *":$HOME/.local/bin:"*) ;; - *) echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" ;; - esac + grep -q 'HOME/.local/bin' "$HOME/.bashrc" 2>/dev/null || \ + echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" fi # Enable core dumps if enabled on the machine From 5feaee4f86a6c8c2fbe516a7997466fab185c0a0 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Fri, 11 Sep 2026 18:44:32 -0400 Subject: [PATCH 10/19] PYTHON-6075 Pin exact drivers-github-tools versions ahead of v4 (#3048) --- .github/workflows/codeql.yml | 2 +- .github/workflows/create-release-branch.yml | 4 ++-- .github/workflows/release-python.yml | 12 ++++++------ .github/workflows/test-python.yml | 16 ++++++++-------- .github/workflows/uv-lock-update.yml | 2 +- .github/zizmor.yml | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a44b49d52f..a33731399b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: - language: actions build-mode: none steps: - - uses: mongodb-labs/drivers-github-tools/codeql@89904229eb55a063655af02f5838bd7fe76505e5 # v3 + - uses: mongodb-labs/drivers-github-tools/codeql@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: language: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml index 98883d5a55..1cb4cbafa0 100644 --- a/.github/workflows/create-release-branch.yml +++ b/.github/workflows/create-release-branch.yml @@ -57,7 +57,7 @@ jobs: run: | auth_header=$(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 -w0) git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${auth_header}" - - uses: mongodb-labs/drivers-github-tools/setup@v3 + - uses: mongodb-labs/drivers-github-tools/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} @@ -65,7 +65,7 @@ jobs: artifactory_username: ${{ vars.ARTIFACTORY_USERNAME }} - name: Get hatch run: pip install hatch - - uses: mongodb-labs/drivers-github-tools/create-branch@v3 + - uses: mongodb-labs/drivers-github-tools/create-branch@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 id: create-branch with: branch_name: ${{ inputs.branch_name }} diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 5e1222b82b..3bf2cccc3c 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -38,16 +38,16 @@ jobs: outputs: version: ${{ steps.pre-publish.outputs.version }} steps: - - uses: mongodb-labs/drivers-github-tools/secure-checkout@v3 + - uses: mongodb-labs/drivers-github-tools/secure-checkout@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: mongodb-labs/drivers-github-tools/setup@v3 + - uses: mongodb-labs/drivers-github-tools/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} - - uses: mongodb-labs/drivers-github-tools/python/pre-publish@v3 + - uses: mongodb-labs/drivers-github-tools/python/pre-publish@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 id: pre-publish with: dry_run: ${{ env.DRY_RUN }} @@ -93,16 +93,16 @@ jobs: attestations: write security-events: write steps: - - uses: mongodb-labs/drivers-github-tools/secure-checkout@v3 + - uses: mongodb-labs/drivers-github-tools/secure-checkout@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: mongodb-labs/drivers-github-tools/setup@v3 + - uses: mongodb-labs/drivers-github-tools/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} - - uses: mongodb-labs/drivers-github-tools/python/post-publish@v3 + - uses: mongodb-labs/drivers-github-tools/python/post-publish@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: following_version: ${{ env.FOLLOWING_VERSION }} product_name: ${{ env.PRODUCT_NAME }} diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 73d5513a16..252c9972de 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "3.10" # Runs before the install so a stale lock fails in seconds rather than @@ -80,7 +80,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: # The beta Python here relies on the action's prerelease default. python-version: ${{ matrix.python-version }} @@ -102,7 +102,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "3.10" - id: setup-mongodb @@ -127,7 +127,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "3.10" - name: Install dependencies @@ -149,7 +149,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "3.10" - name: Install dependencies @@ -168,7 +168,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "${{matrix.python}}" - name: Install dependencies @@ -185,7 +185,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "3.10" - name: Install dependencies @@ -271,7 +271,7 @@ jobs: with: persist-credentials: false - name: Install Python tooling - uses: mongodb-labs/drivers-github-tools/python/setup@d518d2c7d04fdec10266c4218c36791a4fcf98d8 # v3.0.2 + uses: mongodb-labs/drivers-github-tools/python/setup@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: python-version: "3.9" - id: setup-mongodb diff --git a/.github/workflows/uv-lock-update.yml b/.github/workflows/uv-lock-update.yml index 436fa863fe..4bcad0ee90 100644 --- a/.github/workflows/uv-lock-update.yml +++ b/.github/workflows/uv-lock-update.yml @@ -35,7 +35,7 @@ jobs: uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: python-version: "3.10" - - uses: mongodb-labs/drivers-github-tools/python/uv-lock-update@89904229eb55a063655af02f5838bd7fe76505e5 # v3 + - uses: mongodb-labs/drivers-github-tools/python/uv-lock-update@1a2d86bd5fed4b0090453bcd510e4c2ba3b92356 # v3.0.4 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 10fd4cdfcf..af9f715dd4 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -3,5 +3,5 @@ rules: config: policies: actions/*: ref-pin - mongodb-labs/drivers-github-tools/*: ref-pin + mongodb-labs/drivers-github-tools/*: hash-pin mongodb-labs/drivers-evergreen-tools: ref-pin From c39418071a1f841570746019a156b5de605da77d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 18:10:28 -0500 Subject: [PATCH 11/19] PYTHON-6091 Address Copilot: no-config bootstrap installs and always prepend bin dir --- .evergreen/scripts/install-dependencies.sh | 6 +++--- .evergreen/scripts/setup-dev-env.sh | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 6fcad678dc..7c225a6297 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -75,13 +75,13 @@ if [ -n "$_uv_bin" ]; then # install from a copy: Windows will not overwrite a running executable. _uv_tmp="$(mktemp -d)/$(basename "$_uv_bin")" cp "$_uv_bin" "$_uv_tmp" && chmod +x "$_uv_tmp" - "$_uv_tmp" tool install -q --force --from "uv${_uv_pin}" uv + "$_uv_tmp" tool install --no-config -q --force --from "uv${_uv_pin}" uv rm -rf "$(dirname "$_uv_tmp")" echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" fi ;; *) - uv tool install -q --force --from "uv${_uv_pin}" uv + uv tool install --no-config -q --force --from "uv${_uv_pin}" uv echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" ;; esac @@ -99,7 +99,7 @@ if [ ! -x "$PYMONGO_BIN_DIR/just" ] && [ ! -x "$PYMONGO_BIN_DIR/just.exe" ]; the cp "$_toolchain_bin/just.exe" "$PYMONGO_BIN_DIR/just.exe" chmod +x "$PYMONGO_BIN_DIR/just.exe" else - uv tool install rust-just + uv tool install --no-config rust-just fi fi diff --git a/.evergreen/scripts/setup-dev-env.sh b/.evergreen/scripts/setup-dev-env.sh index fecee90890..60c1295fb4 100755 --- a/.evergreen/scripts/setup-dev-env.sh +++ b/.evergreen/scripts/setup-dev-env.sh @@ -25,12 +25,11 @@ if [ -f $HERE/env.sh ]; then . $HERE/env.sh fi -# Add the default install path to the path before configuring uv: setup-uv-python.sh -# calls uv, and on a machine without env.sh the tool dir must already be on PATH. -# Prepend it so the pinned uv/just win over a different install earlier on PATH. -if [ -z "${PYMONGO_BIN_DIR:-}" ]; then - export PATH="$HOME/.local/bin:$PATH" -fi +# Add the install dir to the path before configuring uv, so the pinned uv/just +# win over a different install earlier on PATH (in this parent shell, since the +# dependency installer runs as a child process and its PATH change does not +# propagate here). +export PATH="${PYMONGO_BIN_DIR:-$HOME/.local/bin}:$PATH" # Handle the value for UV_PYTHON. . $HERE/setup-uv-python.sh From 50b804f15926a2de05c3813d0906651f75c5e37c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Sep 2026 18:24:03 -0500 Subject: [PATCH 12/19] PYTHON-6091 Use checkout-local bin dir and persist actual install dir --- .evergreen/scripts/configure-env.sh | 2 +- .evergreen/scripts/setup-system.sh | 11 ++++++----- .gitignore | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index e68907175d..3143a76c4d 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -23,7 +23,7 @@ MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" # our binaries (uv, just, ...) in a task-local dir we control, independent of the # drivers-tools tree. if [ "${CI:-}" == "true" ]; then - PYMONGO_BIN_DIR="${TMPDIR:-/tmp}/pymongo-bin" + PYMONGO_BIN_DIR="$PROJECT_DIRECTORY/.local/bin" # On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev), use the # conventional ~/.local/bin which tools on the PATH (or the shell rc) can find. else diff --git a/.evergreen/scripts/setup-system.sh b/.evergreen/scripts/setup-system.sh index d3271b495a..6aafcd31c0 100755 --- a/.evergreen/scripts/setup-system.sh +++ b/.evergreen/scripts/setup-system.sh @@ -16,12 +16,13 @@ if [ -z "${CI:-}" ]; then fi # On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev) the pinned -# uv and just live in ~/.local/bin, so make sure a login shell finds them by -# adding the entry to .bashrc if it is not already there. env.sh (sourced earlier) -# already puts it on PATH, but that PATH does not persist past this SSH session. +# uv and just live in the sourced install dir (env.sh's PYMONGO_BIN_DIR), so make +# sure a login shell finds them by adding it to .bashrc if it is not already +# there. env.sh's PATH does not persist past this SSH session. if [ "${CI:-}" != "true" ] && [ "${GITHUB_ACTIONS:-}" != "true" ]; then - grep -q 'HOME/.local/bin' "$HOME/.bashrc" 2>/dev/null || \ - echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" + _bin="${PYMONGO_BIN_DIR:-$HOME/.local/bin}" + grep -qF 'export PATH="'"$_bin"':$PATH"' "$HOME/.bashrc" 2>/dev/null || \ + printf 'export PATH="%s:$PATH"\n' "$_bin" >> "$HOME/.bashrc" fi # Enable core dumps if enabled on the machine diff --git a/.gitignore b/.gitignore index 6c4a512018..8546aeabb7 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ mongocryptd.pid .nova/ .temp/ venv/ +.local/ secrets-export.sh libmongocrypt.tar.gz libmongocrypt/ From 10ff9d6087fd7e59c5a34dfbb904627e6f5ac6ca Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 05:22:37 -0500 Subject: [PATCH 13/19] more cleanup --- .evergreen/scripts/configure-env.sh | 21 +--- .evergreen/scripts/install-dependencies.sh | 114 ++++++------------- .evergreen/scripts/setup-dev-env.sh | 25 ++++- .evergreen/scripts/setup-system.sh | 10 -- .evergreen/scripts/setup-uv.py | 125 +++++++++++++++++++++ 5 files changed, 185 insertions(+), 110 deletions(-) create mode 100755 .evergreen/scripts/setup-uv.py diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 3143a76c4d..839c43d31d 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -14,18 +14,15 @@ fi PROJECT_DIRECTORY="$(pwd)" DRIVERS_TOOLS="$(dirname $PROJECT_DIRECTORY)/drivers-tools" CARGO_HOME=${CARGO_HOME:-${DRIVERS_TOOLS}/.cargo} -UV_TOOL_DIR=$PROJECT_DIRECTORY/.local/uv/tools -UV_CACHE_DIR=$PROJECT_DIRECTORY/.local/uv/cache DRIVERS_TOOLS_BINARIES="$DRIVERS_TOOLS/.bin" MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" -# On Evergreen jobs, "CI" will be set, and we don't want to write to $HOME. Put -# our binaries (uv, just, ...) in a task-local dir we control, independent of the -# drivers-tools tree. +# On Evergreen jobs, "CI" will be set, and we don't want to write to $HOME or +# have binaries shared across tasks, so use a TMPDIR. On non-CI hosts +# (spawn hosts, VMs such as GCP/Azure, and local dev), use the conventional +# ~/.local/bin which tools on the PATH (or the shell rc) can find. if [ "${CI:-}" == "true" ]; then - PYMONGO_BIN_DIR="$PROJECT_DIRECTORY/.local/bin" -# On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev), use the -# conventional ~/.local/bin which tools on the PATH (or the shell rc) can find. + PYMONGO_BIN_DIR="${TMPDIR:-/tmp}"/pymongo_bin else PYMONGO_BIN_DIR=$HOME/.local/bin fi @@ -37,12 +34,8 @@ if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY) CARGO_HOME=$(cygpath -m $CARGO_HOME) - UV_TOOL_DIR=$(cygpath -m "$UV_TOOL_DIR") - UV_CACHE_DIR=$(cygpath -m "$UV_CACHE_DIR") DRIVERS_TOOLS_BINARIES=$(cygpath -m "$DRIVERS_TOOLS_BINARIES") MONGODB_BINARIES=$(cygpath -m "$MONGODB_BINARIES") - # Keep PYMONGO_BIN_DIR in cygwin form so bash can search it on PATH; native - # uv gets the Windows form (via cygpath -m) inside install-dependencies.sh. PYMONGO_BIN_DIR=$(cygpath -u "$PYMONGO_BIN_DIR") fi @@ -67,10 +60,6 @@ export DRIVERS_TOOLS_BINARIES="$DRIVERS_TOOLS_BINARIES" export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" export CARGO_HOME="$CARGO_HOME" -export UV_TOOL_DIR="$UV_TOOL_DIR" -export UV_CACHE_DIR="$UV_CACHE_DIR" -# Send uv tool installs into our own bin dir, alongside the pinned uv/just. -export UV_TOOL_BIN_DIR="$PYMONGO_BIN_DIR" export PYMONGO_BIN_DIR="$PYMONGO_BIN_DIR" export PATH="$PATH_EXT" # shellcheck disable=SC2154 diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 7c225a6297..47d6eca045 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -11,96 +11,54 @@ if [ -f $HERE/env.sh ]; then . $HERE/env.sh fi -# Set up the default bin directory. -if [ -z "${PYMONGO_BIN_DIR:-}" ]; then - PYMONGO_BIN_DIR="$HOME/.local/bin" -fi -# uv.exe on Windows needs Windows-style paths, while bash uses the cygwin form. -# Keep PYMONGO_BIN_DIR in the form PATH uses and give uv the Windows form. +# PYMONGO_BIN_DIR is set by setup-system.sh/env.sh (or setup-dev-env.sh); default +# it for robustness. UV_TOOL_BIN_DIR is uv's name for the same dir (setup-uv.py +# reads both). UV_TOOL_DIR is left to ensure_uv.sh. +export PYMONGO_BIN_DIR="${PYMONGO_BIN_DIR:-$HOME/.local/bin}" +export UV_TOOL_BIN_DIR="${UV_TOOL_BIN_DIR:-$PYMONGO_BIN_DIR}" +# uv is a native Windows binary: give it a Windows path on cygwin. if [ "Windows_NT" = "${OS:-}" ]; then _uv_tool_bin="$(cygpath -m "$PYMONGO_BIN_DIR")" - _uv_tool_dir="$(cygpath -m "${UV_TOOL_DIR:-$(dirname "$PYMONGO_BIN_DIR")/uv-tools}")" export UV_TOOL_BIN_DIR="$_uv_tool_bin" - export UV_TOOL_DIR="$_uv_tool_dir" -else - export UV_TOOL_BIN_DIR="$PYMONGO_BIN_DIR" -fi -mkdir -p "$PYMONGO_BIN_DIR" - -# Locate the Python toolchain's binary dir, so we can prefer its uv and just. -_toolchain_bin="" -if [ "Windows_NT" = "${OS:-}" ]; then - _toolchain_bin="/cygdrive/c/Python/Current/Scripts" -elif [ "$(uname -s)" = "Darwin" ]; then - _toolchain_bin="/Library/Frameworks/Python.Framework/Versions/Current/bin" -else - _toolchain_bin="/opt/python/Current/bin" fi -# Prefer the toolchain's uv as a bootstrap when uv is not already on PATH, so we -# do not fall back to installing from astral. -if ! command -v uv &>/dev/null; then - if [ -x "$_toolchain_bin/uv" ] || [ -x "$_toolchain_bin/uv.exe" ]; then - echo "Found uv in the toolchain at $_toolchain_bin" - export PATH="$_toolchain_bin:$PATH" +# If uv is on PATH, check it via `uv sync`, which fails fast if it is not the +# pinned version (from pyproject.toml's [tool.uv] required-version). If that +# succeeds, the environment is already correct and there is nothing to set up; +# otherwise fall through to the setup below. +# +# On CI we also require UV_CACHE_DIR to be set: ensure_uv.sh scopes uv's cache +# to a task-local dir, so an unset UV_CACHE_DIR means the uv setup has not run +# yet in this task and we must do the setup phase. +_need_setup=1 +if command -v uv >/dev/null 2>&1 && uv sync >/dev/null 2>&1; then + if [ "${CI:-}" != "true" ] || [ -n "${UV_CACHE_DIR:-}" ]; then + echo "uv is already set up; skipping uv setup." + _need_setup=0 fi fi -# Ensure uv is available (bootstrap if absent). -if ! command -v uv &>/dev/null; then - _BIN_DIR=$PYMONGO_BIN_DIR - mkdir -p ${_BIN_DIR} - echo "uv not found on PATH; installing the latest uv from astral..." - curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$_BIN_DIR" INSTALLER_NO_MODIFY_PATH=1 sh - if [ "Windows_NT" = "${OS:-}" ]; then - chmod +x "$(cygpath -u $_BIN_DIR)/uv.exe" +# Set up uv if needed. +if [ "$_need_setup" = "1" ]; then + # ensure-uv.sh (drivers-evergreen-tools) finds or installs uv and scopes its env. + if [ -n "${DRIVERS_TOOLS:-}" ] && [ -f "$DRIVERS_TOOLS/.evergreen/ensure-uv.sh" ]; then + . "$DRIVERS_TOOLS/.evergreen/ensure-uv.sh" + ensure_uv || exit 1 fi - export PATH="$PYMONGO_BIN_DIR:$PATH" -fi -# Pin the uv binary to the version in pyproject.toml's [tool.uv] required-version. -# Run the current uv directly: it writes into PYMONGO_BIN_DIR (a different -# location), so nothing running is overwritten. If the running uv is already our -# pinned bin-dir uv, skip the install to avoid overwriting it (Windows refuses to -# overwrite a running executable); otherwise install so the pin lands in the bin -# dir even when the discovered uv already matches. -_uv_bin="$(command -v uv 2>/dev/null || true)" -if [ -n "$_uv_bin" ]; then - _uv_pin="$(awk -F'"' '/^[[:space:]]*required-version[[:space:]]*=/{print $2}' pyproject.toml)" - case "$_uv_bin" in - "$PYMONGO_BIN_DIR"/*) - _uv_vers="$(uv --version 2>/dev/null | head -1 | awk '{print $2}' | sed 's/^v//')" - if [ "uv${_uv_pin}" != "uv==${_uv_vers}" ]; then - # The running uv lives in our bin dir and is not the pin, so run the - # install from a copy: Windows will not overwrite a running executable. - _uv_tmp="$(mktemp -d)/$(basename "$_uv_bin")" - cp "$_uv_bin" "$_uv_tmp" && chmod +x "$_uv_tmp" - "$_uv_tmp" tool install --no-config -q --force --from "uv${_uv_pin}" uv - rm -rf "$(dirname "$_uv_tmp")" - echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" - fi - ;; - *) - uv tool install --no-config -q --force --from "uv${_uv_pin}" uv - echo "Using uv at $PYMONGO_BIN_DIR/uv ($("$PYMONGO_BIN_DIR/uv" --version 2>/dev/null | head -1 | awk '{print $2}'))" - ;; - esac -fi + # Do the uv setup (bin dir, pinning, env.sh) under uv's own interpreter. + uv run "$HERE/setup-uv.py" -# Use just from the toolchain if available, otherwise install it. It must live in -# our bin dir to be on PATH for callers; copying it keeps the toolchain's just. -if [ ! -x "$PYMONGO_BIN_DIR/just" ] && [ ! -x "$PYMONGO_BIN_DIR/just.exe" ]; then - if [ -x "$_toolchain_bin/just" ]; then - echo "Using just from the toolchain" - cp "$_toolchain_bin/just" "$PYMONGO_BIN_DIR/just" - chmod +x "$PYMONGO_BIN_DIR/just" - elif [ -x "$_toolchain_bin/just.exe" ]; then - echo "Using just from the toolchain" - cp "$_toolchain_bin/just.exe" "$PYMONGO_BIN_DIR/just.exe" - chmod +x "$PYMONGO_BIN_DIR/just.exe" - else - uv tool install --no-config rust-just + # Re-source env.sh so the values setup-uv.py wrote are available. + if [ -f $HERE/env.sh ]; then + . $HERE/env.sh fi fi +# Make just available. It has no version constraint, so if it is already on PATH +# there is nothing to do; otherwise install it into the bin dir via uv. +if ! command -v just >/dev/null 2>&1; then + uv tool install --no-config rust-just +fi + popd > /dev/null diff --git a/.evergreen/scripts/setup-dev-env.sh b/.evergreen/scripts/setup-dev-env.sh index 60c1295fb4..18b6cd5b5a 100755 --- a/.evergreen/scripts/setup-dev-env.sh +++ b/.evergreen/scripts/setup-dev-env.sh @@ -16,6 +16,25 @@ if [ -f $HERE/test-env.sh ]; then . $HERE/test-env.sh fi +# The bin dir for the pinned uv/just. setup-system.sh sets it on evergreen hosts; +# default it here so local dev (without setup-system.sh) also has a usable value. +export PYMONGO_BIN_DIR="${PYMONGO_BIN_DIR:-$HOME/.local/bin}" + +# Make sure a login shell can find the bin dir by adding it to the rc file, so +# local dev (which may never run setup-system.sh) still has it on PATH. env.sh's +# PATH does not persist past this session. Prefer .zshrc when the shell is zsh. +if [ "${CI:-}" != "true" ] && [ "${GITHUB_ACTIONS:-}" != "true" ]; then + if [ -f "$HOME/.zshrc" ]; then + _rc="$HOME/.zshrc" + else + _rc="$HOME/.bashrc" + fi + if [ -f "$_rc" ]; then + grep -qF 'export PATH="'"$PYMONGO_BIN_DIR"':$PATH"' "$_rc" 2>/dev/null || \ + printf 'export PATH="%s:$PATH"\n' "$PYMONGO_BIN_DIR" >> "$_rc" + fi +fi + # Ensure dependencies are installed. bash $HERE/install-dependencies.sh @@ -25,12 +44,6 @@ if [ -f $HERE/env.sh ]; then . $HERE/env.sh fi -# Add the install dir to the path before configuring uv, so the pinned uv/just -# win over a different install earlier on PATH (in this parent shell, since the -# dependency installer runs as a child process and its PATH change does not -# propagate here). -export PATH="${PYMONGO_BIN_DIR:-$HOME/.local/bin}:$PATH" - # Handle the value for UV_PYTHON. . $HERE/setup-uv-python.sh diff --git a/.evergreen/scripts/setup-system.sh b/.evergreen/scripts/setup-system.sh index 6aafcd31c0..bd7e2dd4bc 100755 --- a/.evergreen/scripts/setup-system.sh +++ b/.evergreen/scripts/setup-system.sh @@ -15,16 +15,6 @@ if [ -z "${CI:-}" ]; then bash $HERE/setup-dev-env.sh fi -# On non-CI hosts (spawn hosts, VMs such as GCP/Azure, and local dev) the pinned -# uv and just live in the sourced install dir (env.sh's PYMONGO_BIN_DIR), so make -# sure a login shell finds them by adding it to .bashrc if it is not already -# there. env.sh's PATH does not persist past this SSH session. -if [ "${CI:-}" != "true" ] && [ "${GITHUB_ACTIONS:-}" != "true" ]; then - _bin="${PYMONGO_BIN_DIR:-$HOME/.local/bin}" - grep -qF 'export PATH="'"$_bin"':$PATH"' "$HOME/.bashrc" 2>/dev/null || \ - printf 'export PATH="%s:$PATH"\n' "$_bin" >> "$HOME/.bashrc" -fi - # Enable core dumps if enabled on the machine # Copied from https://github.com/mongodb/mongo/blob/master/etc/evergreen.yml if [ -f /proc/self/coredump_filter ]; then diff --git a/.evergreen/scripts/setup-uv.py b/.evergreen/scripts/setup-uv.py new file mode 100755 index 0000000000..3ec9a7eeba --- /dev/null +++ b/.evergreen/scripts/setup-uv.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Bootstrap the pinned uv/just for the test environment. + +install-dependencies.sh bails out if the pinned uv is already on PATH, sources +ensure-uv.sh (which finds or installs uv), then runs this script for the rest. +Only the standard library is used. +""" + +from __future__ import annotations + +import os +import re +import shutil +import subprocess +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parent.parent +ENV_SH = HERE / "env.sh" +ASTURL_INSTALL_URL = "https://astral.sh/uv/install.sh" + + +def required_uv_pin() -> str: + """Return [tool.uv] required-version, e.g. '==0.12.12' ('' if absent).""" + pattern = re.compile(r"required-version\s*=\s*['\"]?([^'\"\s]+)['\"]?") + in_uv = False + for line in (ROOT / "pyproject.toml").read_text().splitlines(): + stripped = line.strip() + if stripped.startswith("["): + in_uv = stripped == "[tool.uv]" + continue + if in_uv: + match = pattern.search(stripped) + if match: + return match.group(1) + return "" + + +def _add_path(dir_: str) -> None: + os.environ["PATH"] = dir_ + os.pathsep + os.environ.get("PATH", "") + + +def _install_uv_astral() -> None: + """Install uv from astral when ensure-uv.sh was not available. + + UV_TOOL_BIN_DIR is set (in native form on Windows) by install-dependencies.sh. + """ + print("uv not found; installing the latest uv from astral...") + env = { + **os.environ, + "UV_INSTALL_DIR": os.environ["UV_TOOL_BIN_DIR"], + "INSTALLER_NO_MODIFY_PATH": "1", + } + curl = shutil.which("curl") + sh = shutil.which("sh") + proc = subprocess.run( # noqa: S603 + [curl, "-LsSf", ASTURL_INSTALL_URL], capture_output=True, env=env, check=True + ) + subprocess.run([sh], input=proc.stdout, env=env, check=True) # noqa: S603 + _add_path(os.environ["UV_TOOL_BIN_DIR"]) + + +def _pin_uv(uv_pin: str) -> None: + """Install the pinned uv via uv tool install --force. + + UV_TOOL_BIN_DIR and UV_TOOL_DIR are inherited from the environment (set by + install-dependencies.sh / ensure-uv.sh, in native form on Windows). uv tool + install writes the binary into UV_TOOL_BIN_DIR and the tool venv into + UV_TOOL_DIR; --force lets it overwrite an existing install. + """ + subprocess.run( # noqa: S603 + [ + shutil.which("uv"), + "tool", + "install", + "--no-config", + "-q", + "--force", + "--from", + f"uv{uv_pin}", + "uv", + ], + check=True, + ) + + +def _write_env() -> None: + """Write every UV_* env var into env.sh, replacing existing UV_* entries.""" + values = {k: v for k, v in os.environ.items() if k.startswith("UV_")} + if not values: + return + existing = ENV_SH.read_text() if ENV_SH.exists() else "" + keep = [] + for line in existing.splitlines(): + stripped = line.strip() + if stripped.startswith("export "): + var, _, _ = stripped[len("export ") :].partition("=") + if var.startswith("UV_"): + continue + keep.append(line) + keep.append("") + keep.extend(f'export {name}="{value}"' for name, value in sorted(values.items())) + ENV_SH.write_text("\n".join(keep) + "\n") + + +def main() -> int: + bin_dir = os.environ["UV_TOOL_BIN_DIR"] + Path(bin_dir).mkdir(parents=True, exist_ok=True) + _add_path(bin_dir) + + # Bootstrap a uv from astral if ensure-uv.sh didn't run and uv isn't on PATH. + if shutil.which("uv") is None: + _install_uv_astral() + + uv_pin = required_uv_pin() + if uv_pin: + _pin_uv(uv_pin) + + _write_env() + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 2a2212dd1fa9741b6e36a629c19db7ad37f9129d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 05:47:34 -0500 Subject: [PATCH 14/19] PYTHON-6091 Use toolchain python3 for setup-uv.py --- .evergreen/scripts/configure-env.sh | 17 ++++++++++++++++- .evergreen/scripts/install-dependencies.sh | 5 +++-- .evergreen/scripts/setup-uv.py | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 839c43d31d..64252d2002 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -27,7 +27,22 @@ else PYMONGO_BIN_DIR=$HOME/.local/bin fi -PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" +# Add the latest MongoDB toolchain bin dir to PATH if it exists, so that hosts +# with old system Python3 still get a modern interpreter for setup scripts. +# It goes after PYMONGO_BIN_DIR so the pinned uv (installed there by setup-uv.py) +# takes precedence over the toolchain's uv. +if [ "Windows_NT" = "${OS:-}" ]; then + _toolchain_bin="/cygdrive/c/Python/Current/Scripts" +elif [ "$(uname -s)" == "Darwin" ]; then + _toolchain_bin="/Library/Frameworks/Python.Framework/Versions/Current/bin" +else + _toolchain_bin="/opt/python/Current/bin" +fi +if [ -d "$_toolchain_bin" ]; then + PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$_toolchain_bin:$DRIVERS_TOOLS_BINARIES:\$PATH" +else + PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" +fi # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 47d6eca045..9e7ef8499e 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -46,8 +46,9 @@ if [ "$_need_setup" = "1" ]; then ensure_uv || exit 1 fi - # Do the uv setup (bin dir, pinning, env.sh) under uv's own interpreter. - uv run "$HERE/setup-uv.py" + # Do the uv setup (bin dir, pinning, env.sh). Uses the toolchain python3 + # (added to PATH by configure-env.sh) to avoid uv's required-version check. + python3 "$HERE/setup-uv.py" # Re-source env.sh so the values setup-uv.py wrote are available. if [ -f $HERE/env.sh ]; then diff --git a/.evergreen/scripts/setup-uv.py b/.evergreen/scripts/setup-uv.py index 3ec9a7eeba..057eb858f0 100755 --- a/.evergreen/scripts/setup-uv.py +++ b/.evergreen/scripts/setup-uv.py @@ -18,7 +18,7 @@ HERE = Path(__file__).resolve().parent ROOT = HERE.parent.parent ENV_SH = HERE / "env.sh" -ASTURL_INSTALL_URL = "https://astral.sh/uv/install.sh" +ASTRAL_INSTALL_URL = "https://astral.sh/uv/install.sh" def required_uv_pin() -> str: @@ -55,7 +55,7 @@ def _install_uv_astral() -> None: curl = shutil.which("curl") sh = shutil.which("sh") proc = subprocess.run( # noqa: S603 - [curl, "-LsSf", ASTURL_INSTALL_URL], capture_output=True, env=env, check=True + [curl, "-LsSf", ASTRAL_INSTALL_URL], capture_output=True, env=env, check=True ) subprocess.run([sh], input=proc.stdout, env=env, check=True) # noqa: S603 _add_path(os.environ["UV_TOOL_BIN_DIR"]) From 22a9b9adfa920bf862a6771c1258888b31bd82a7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 05:59:09 -0500 Subject: [PATCH 15/19] PYTHON-6091 Convert setup-uv.py path for Windows python3 --- .evergreen/scripts/install-dependencies.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 9e7ef8499e..99cfde8568 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -48,7 +48,12 @@ if [ "$_need_setup" = "1" ]; then # Do the uv setup (bin dir, pinning, env.sh). Uses the toolchain python3 # (added to PATH by configure-env.sh) to avoid uv's required-version check. - python3 "$HERE/setup-uv.py" + # On Windows the script path must be a native Windows path for python3. + _uv_setup_script="$HERE/setup-uv.py" + if [ "Windows_NT" = "${OS:-}" ]; then + _uv_setup_script="$(cygpath -m "$_uv_setup_script")" + fi + python3 "$_uv_setup_script" # Re-source env.sh so the values setup-uv.py wrote are available. if [ -f $HERE/env.sh ]; then From c3daaf9e6f1e1022a0ccbebb09cd73866fc903b7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 06:04:09 -0500 Subject: [PATCH 16/19] PYTHON-6091 Bypass required-version via uv run --no-config --- .evergreen/scripts/configure-env.sh | 17 +---------------- .evergreen/scripts/install-dependencies.sh | 12 ++++-------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 64252d2002..839c43d31d 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -27,22 +27,7 @@ else PYMONGO_BIN_DIR=$HOME/.local/bin fi -# Add the latest MongoDB toolchain bin dir to PATH if it exists, so that hosts -# with old system Python3 still get a modern interpreter for setup scripts. -# It goes after PYMONGO_BIN_DIR so the pinned uv (installed there by setup-uv.py) -# takes precedence over the toolchain's uv. -if [ "Windows_NT" = "${OS:-}" ]; then - _toolchain_bin="/cygdrive/c/Python/Current/Scripts" -elif [ "$(uname -s)" == "Darwin" ]; then - _toolchain_bin="/Library/Frameworks/Python.Framework/Versions/Current/bin" -else - _toolchain_bin="/opt/python/Current/bin" -fi -if [ -d "$_toolchain_bin" ]; then - PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$_toolchain_bin:$DRIVERS_TOOLS_BINARIES:\$PATH" -else - PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" -fi +PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 99cfde8568..d0a10275a8 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -46,14 +46,10 @@ if [ "$_need_setup" = "1" ]; then ensure_uv || exit 1 fi - # Do the uv setup (bin dir, pinning, env.sh). Uses the toolchain python3 - # (added to PATH by configure-env.sh) to avoid uv's required-version check. - # On Windows the script path must be a native Windows path for python3. - _uv_setup_script="$HERE/setup-uv.py" - if [ "Windows_NT" = "${OS:-}" ]; then - _uv_setup_script="$(cygpath -m "$_uv_setup_script")" - fi - python3 "$_uv_setup_script" + # Do the uv setup (bin dir, pinning, env.sh). `--no-config` skips the + # required-version check from pyproject.toml, letting any bootstrapped uv + # install the pinned version in setup-uv.py. + uv run --no-config "$HERE/setup-uv.py" # Re-source env.sh so the values setup-uv.py wrote are available. if [ -f $HERE/env.sh ]; then From 905d3bf93edd17534a04448b2d29a45e560414af Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 06:41:40 -0500 Subject: [PATCH 17/19] PYTHON-6091 Add toolchain python to PATH and fix Windows script path --- .evergreen/scripts/configure-env.sh | 18 +++++++++++++++++- .evergreen/scripts/install-dependencies.sh | 9 +++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.evergreen/scripts/configure-env.sh b/.evergreen/scripts/configure-env.sh index 839c43d31d..a92c0b023a 100755 --- a/.evergreen/scripts/configure-env.sh +++ b/.evergreen/scripts/configure-env.sh @@ -27,7 +27,23 @@ else PYMONGO_BIN_DIR=$HOME/.local/bin fi -PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" +# Add the latest MongoDB toolchain bin dir to PATH if it exists, so that hosts +# with an old system Python (e.g. RHEL8's 3.6) still get a modern interpreter +# for tool installs like `uv tool install rust-just`. It goes after +# PYMONGO_BIN_DIR so the pinned uv (installed there by setup-uv.py) takes +# precedence over the toolchain's uv. +if [ "Windows_NT" = "${OS:-}" ]; then + _toolchain_bin="/cygdrive/c/Python/Current/Scripts" +elif [ "$(uname -s)" == "Darwin" ]; then + _toolchain_bin="/Library/Frameworks/Python.Framework/Versions/Current/bin" +else + _toolchain_bin="/opt/python/Current/bin" +fi +if [ -d "$_toolchain_bin" ]; then + PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$_toolchain_bin:$DRIVERS_TOOLS_BINARIES:\$PATH" +else + PATH_EXT="$MONGODB_BINARIES:$PYMONGO_BIN_DIR:$DRIVERS_TOOLS_BINARIES:\$PATH" +fi # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index d0a10275a8..860c44e7a1 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -48,8 +48,13 @@ if [ "$_need_setup" = "1" ]; then # Do the uv setup (bin dir, pinning, env.sh). `--no-config` skips the # required-version check from pyproject.toml, letting any bootstrapped uv - # install the pinned version in setup-uv.py. - uv run --no-config "$HERE/setup-uv.py" + # install the pinned version in setup-uv.py. On Windows the script path must + # be a native Windows path for uv. + _uv_setup_script="$HERE/setup-uv.py" + if [ "Windows_NT" = "${OS:-}" ]; then + _uv_setup_script="$(cygpath -m "$_uv_setup_script")" + fi + uv run --no-config "$_uv_setup_script" # Re-source env.sh so the values setup-uv.py wrote are available. if [ -f $HERE/env.sh ]; then From cc4fea506990bf63b1724745848b58a63f0ace16 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 09:30:14 -0500 Subject: [PATCH 18/19] PYTHON-6091 Use toolchain python3 for setup-uv and write LF env.sh --- .evergreen/scripts/install-dependencies.sh | 10 +++++----- .evergreen/scripts/setup-uv.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 860c44e7a1..42f5893079 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -46,15 +46,15 @@ if [ "$_need_setup" = "1" ]; then ensure_uv || exit 1 fi - # Do the uv setup (bin dir, pinning, env.sh). `--no-config` skips the - # required-version check from pyproject.toml, letting any bootstrapped uv - # install the pinned version in setup-uv.py. On Windows the script path must - # be a native Windows path for uv. + # Do the uv setup (bin dir, pinning, env.sh). Uses the toolchain python3 + # (added to PATH by configure-env.sh) so no project .venv is created here, + # and no required-version check is triggered. On Windows the script path must + # be a native Windows path for python3. _uv_setup_script="$HERE/setup-uv.py" if [ "Windows_NT" = "${OS:-}" ]; then _uv_setup_script="$(cygpath -m "$_uv_setup_script")" fi - uv run --no-config "$_uv_setup_script" + python3 "$_uv_setup_script" # Re-source env.sh so the values setup-uv.py wrote are available. if [ -f $HERE/env.sh ]; then diff --git a/.evergreen/scripts/setup-uv.py b/.evergreen/scripts/setup-uv.py index 057eb858f0..dedc67b5be 100755 --- a/.evergreen/scripts/setup-uv.py +++ b/.evergreen/scripts/setup-uv.py @@ -101,7 +101,7 @@ def _write_env() -> None: keep.append(line) keep.append("") keep.extend(f'export {name}="{value}"' for name, value in sorted(values.items())) - ENV_SH.write_text("\n".join(keep) + "\n") + ENV_SH.write_text("\n".join(keep) + "\n", newline="\n") def main() -> int: From 79bd6a6b4fa3717e0792337ee48e8444d4db849f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Sep 2026 12:19:12 -0500 Subject: [PATCH 19/19] PYTHON-6091 Fix setup-uv on Windows: LF env.sh and avoid uv self-overwrite --- .evergreen/scripts/setup-uv.py | 47 +++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/.evergreen/scripts/setup-uv.py b/.evergreen/scripts/setup-uv.py index dedc67b5be..7089275be3 100755 --- a/.evergreen/scripts/setup-uv.py +++ b/.evergreen/scripts/setup-uv.py @@ -13,6 +13,7 @@ import shutil import subprocess import sys +import tempfile from pathlib import Path HERE = Path(__file__).resolve().parent @@ -68,21 +69,35 @@ def _pin_uv(uv_pin: str) -> None: install-dependencies.sh / ensure-uv.sh, in native form on Windows). uv tool install writes the binary into UV_TOOL_BIN_DIR and the tool venv into UV_TOOL_DIR; --force lets it overwrite an existing install. + + Windows will not overwrite a running executable, so when the current uv is + already the target bin dir, run the install from a copy of the binary. """ - subprocess.run( # noqa: S603 - [ - shutil.which("uv"), - "tool", - "install", - "--no-config", - "-q", - "--force", - "--from", - f"uv{uv_pin}", - "uv", - ], - check=True, - ) + uv_path = shutil.which("uv") + tool = uv_path + tmp_uv = None + if os.name == "nt": + tmp_uv = Path(tempfile.mkdtemp()) / Path(uv_path).name + shutil.copy2(uv_path, tmp_uv) + tool = str(tmp_uv) + try: + subprocess.run( # noqa: S603 + [ + tool, + "tool", + "install", + "--no-config", + "-q", + "--force", + "--from", + f"uv{uv_pin}", + "uv", + ], + check=True, + ) + finally: + if tmp_uv: + shutil.rmtree(tmp_uv.parent, ignore_errors=True) def _write_env() -> None: @@ -101,7 +116,9 @@ def _write_env() -> None: keep.append(line) keep.append("") keep.extend(f'export {name}="{value}"' for name, value in sorted(values.items())) - ENV_SH.write_text("\n".join(keep) + "\n", newline="\n") + # Write LF bytes directly: on Windows text mode translates \n to \r\n, which + # breaks bash sourcing env.sh, and `newline=` isn't available on all Pythons. + ENV_SH.write_bytes(("\n".join(keep) + "\n").encode()) def main() -> int: