From 08b7f97f9d85563f77f7bcd1af5870aa7e8e1c8d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 1 Oct 2026 05:06:34 -0500 Subject: [PATCH] PYTHON-6114 Install mod_wsgi on demand instead of as a dependency group Min-deps tasks re-resolve the whole project with uv --resolution=lowest-direct. The universal lock must resolve every dependency group on every platform, and uv can only resolve mod_wsgi by building its sdist, which fails on Windows without an Apache installation. Move the constraint to requirements/mod_wsgi.txt (managed by Dependabot) and install it on demand in the mod_wsgi workflows. --- .evergreen/scripts/mod_wsgi.sh | 13 +++++++------ .github/dependabot.yml | 17 +++++++++++++++++ .github/workflows/sbom.yml | 1 + .github/workflows/test-mod-wsgi.yml | 15 +++++++++------ CONTRIBUTING.md | 2 +- pyproject.toml | 3 --- requirements/mod_wsgi.txt | 3 +++ test/mod_wsgi_test/README.rst | 7 ++++--- tools/generate_sbom_requirements.py | 4 ++-- uv.lock | 25 ------------------------- 10 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 requirements/mod_wsgi.txt diff --git a/.evergreen/scripts/mod_wsgi.sh b/.evergreen/scripts/mod_wsgi.sh index 61aed24b73..78233a4ff2 100755 --- a/.evergreen/scripts/mod_wsgi.sh +++ b/.evergreen/scripts/mod_wsgi.sh @@ -73,12 +73,11 @@ if [ "$(uname -s)" = "Linux" ] && [ -z "${MOD_WSGI_DOCKER:-}" ]; then # "just run-server" or the CI workflow. Set MOD_WSGI_DOCKER to force the # container path on Linux. if [ "$CMD" != "teardown" ]; then - # Other recipes' exact uv syncs — including run-tests' resync — prune the - # mod_wsgi group; restore it before setup or test when missing. Checking - # first keeps the sync from reconciling dependencies that a caller - # resolved differently, like the min-deps job's lowest-direct install. + # Other recipes' exact uv syncs prune the on-demand mod_wsgi install; + # restore it when missing. Checking first avoids reconciling a caller's + # differently-resolved dependencies. if ! uv run --no-sync python -c "import mod_wsgi" 2>/dev/null; then - uv sync --group mod_wsgi --quiet + uv pip install -r requirements/mod_wsgi.txt --quiet fi fi if [ "$CMD" = "setup" ]; then @@ -190,7 +189,9 @@ bootstrap() { # Rebuild the in-place C extensions on every bootstrap; the source # refresh may carry artifacts built by a different interpreter. export PYMONGO_C_EXT_MUST_BUILD=1 - uv sync --group mod_wsgi --refresh-package pymongo + uv sync --refresh-package pymongo + # The sync above prunes mod_wsgi, so install it after. + VIRTUAL_ENV="$VENV" uv pip install -r requirements/mod_wsgi.txt --quiet ' ensure_mongod docker exec --user smoke -w "$HOME_SRC" "$CONTAINER" bash -c 'set -euo pipefail diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 72d1e1e084..d9f19ad27e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,3 +19,20 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 0 + # Python (pip). Manages requirements/mod_wsgi.txt only; the other + # requirements files are resolved in uv.lock. + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + exclude-paths: + - "/pyproject.toml" + - "/requirements.txt" + - "/requirements/aws.txt" + - "/requirements/docs.txt" + - "/requirements/encryption.txt" + - "/requirements/gssapi.txt" + - "/requirements/ocsp.txt" + - "/requirements/snappy.txt" + - "/requirements/test.txt" + - "/requirements/zstd.txt" diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 91af8d840c..62b0ca3ceb 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -14,6 +14,7 @@ on: - 'requirements/**.txt' - '!requirements/docs.txt' - '!requirements/test.txt' + - '!requirements/mod_wsgi.txt' permissions: contents: write diff --git a/.github/workflows/test-mod-wsgi.yml b/.github/workflows/test-mod-wsgi.yml index 4985161986..2ae2310ab6 100644 --- a/.github/workflows/test-mod-wsgi.yml +++ b/.github/workflows/test-mod-wsgi.yml @@ -16,6 +16,7 @@ on: - "justfile" - "pyproject.toml" - "uv.lock" + - "requirements/mod_wsgi.txt" pull_request: paths: - ".evergreen/scripts/mod_wsgi_*" @@ -30,6 +31,7 @@ on: - "justfile" - "pyproject.toml" - "uv.lock" + - "requirements/mod_wsgi.txt" workflow_dispatch: concurrency: @@ -87,12 +89,11 @@ jobs: run: | export PYMONGO_C_EXT_MUST_BUILD=1 just install - uv sync --group mod_wsgi --refresh-package pymongo + # just install skips the sync when uv is already set up. + uv sync --refresh-package pymongo uv run --no-sync python tools/fail_if_no_c.py - # Re-resolve at the lowest direct versions, including the mod_wsgi - # group: uv sync above installed the locked version, but this job - # must exercise the group's mod_wsgi>=4.9.4 lower bound. - uv pip install -e ".[test]" --group mod_wsgi --resolution=lowest-direct --force-reinstall + # Exercise the mod_wsgi>=4.9.4 lower bound. + uv pip install -e ".[test]" -r requirements/mod_wsgi.txt --resolution=lowest-direct --force-reinstall - name: Start MongoDB # Fedora is not a supported MongoDB platform, so install the RHEL build # directly instead of using the drivers-evergreen-tools action, which @@ -153,7 +154,9 @@ jobs: run: | export PYMONGO_C_EXT_MUST_BUILD=1 just install - uv sync --group mod_wsgi --refresh-package pymongo + # just install skips the sync when uv is already set up. + uv sync --refresh-package pymongo + uv pip install -r requirements/mod_wsgi.txt uv run --no-sync python tools/fail_if_no_c.py - id: setup-mongodb uses: mongodb-labs/drivers-evergreen-tools@49da51cebc0060b4d699d57bdef30ee7e071de44 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14e3ddfd26..e1b0c88db6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -361,7 +361,7 @@ Continuous integration runs the tests on pull requests that change mod_wsgi-relevant files, in the `test-mod-wsgi.yml` workflow. To run the tests by hand, install Apache and mod_wsgi (`sudo apt-get install -y apache2 -apache2-dev` and `uv sync --group mod_wsgi` on Ubuntu), then: +apache2-dev` and `uv pip install -r requirements/mod_wsgi.txt` on Ubuntu), then: - On Linux, run `TOPOLOGY=replica_set just run-server`. - Run `just setup-tests mod_wsgi `. diff --git a/pyproject.toml b/pyproject.toml index 8083b03b46..2f7a68c837 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,9 +70,6 @@ unasync = [ ] pip = ["pip>=20.2"] gevent = ["gevent>=21.12"] -# mod_wsgi is built against the interpreter it is installed with and is used -# only by the mod_wsgi test suite. -mod_wsgi = ["mod_wsgi>=4.9.4"] coverage = [ "coverage[toml]>=5,<=7.15.0" ] diff --git a/requirements/mod_wsgi.txt b/requirements/mod_wsgi.txt new file mode 100644 index 0000000000..31bb5dfda8 --- /dev/null +++ b/requirements/mod_wsgi.txt @@ -0,0 +1,3 @@ +# Test-only; kept out of pyproject.toml so the uv lock never has to +# resolve it (building its sdist needs Apache). +mod_wsgi>=4.9.4 diff --git a/test/mod_wsgi_test/README.rst b/test/mod_wsgi_test/README.rst index 3dddb3e5f7..5a9348feb2 100644 --- a/test/mod_wsgi_test/README.rst +++ b/test/mod_wsgi_test/README.rst @@ -41,10 +41,11 @@ with the matching config file (``apache24ubuntu.conf`` or Install mod_wsgi ................ -The project defines a ``mod_wsgi`` dependency group used for testing. pip -builds mod_wsgi against the interpreter it is installed with:: +mod_wsgi is only needed by these tests, so it is not a project dependency. +Its constraint lives in ``requirements/mod_wsgi.txt``. pip builds mod_wsgi +against the interpreter it is installed with:: - uv sync --group mod_wsgi + uv pip install -r requirements/mod_wsgi.txt Start mongod ............ diff --git a/tools/generate_sbom_requirements.py b/tools/generate_sbom_requirements.py index 59389bee5a..aa8d5579ca 100644 --- a/tools/generate_sbom_requirements.py +++ b/tools/generate_sbom_requirements.py @@ -3,7 +3,7 @@ - Includes repo_root/requirements.txt if present - Includes all files matching repo_root/requirements/**/*.txt -- Excludes docs.txt and test.txt in the requirements folder +- Excludes docs.txt, test.txt, and mod_wsgi.txt in the requirements folder - Writes output to sbom-requirements.txt (overwrites) """ @@ -11,7 +11,7 @@ from pathlib import Path -EXCLUDED_NAMES = {"docs.txt", "test.txt"} +EXCLUDED_NAMES = {"docs.txt", "test.txt", "mod_wsgi.txt"} def collect_files(root: Path) -> list[Path]: diff --git a/uv.lock b/uv.lock index f7fe85a6ca..ee008e88d4 100644 --- a/uv.lock +++ b/uv.lock @@ -2225,26 +2225,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/c0/bc0c3e82bf91993bf2337b125c4d49c3cadae39f5a4c642baada958f976e/mockupdb-1.8.1-py2.py3-none-any.whl", hash = "sha256:8eb76212963bad5df7f3ea8f26af916087c2dcc69a0457c75328abad44956dbb", size = 27578, upload-time = "2021-10-15T19:02:55.489Z" }, ] -[[package]] -name = "mod-wsgi" -version = "5.0.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version > '3.9' and python_full_version < '3.10'", - "python_full_version <= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/4d/275853efa4d2a4efe1614aa0df06295e71f2082cebccc4d40d0f0a551c24/mod_wsgi-5.0.2.tar.gz", hash = "sha256:6fe5557c3ef25c184c2c994905fe52be1c06ae5c46235ecafe558e84789e2159", size = 498627, upload-time = "2024-11-22T08:53:22.752Z" } - -[[package]] -name = "mod-wsgi" -version = "6.0.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version == '3.10.*'", -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/ce/fdbfe51f596e37c657176ea64d637737074214bfcd21c2177aa6b1f1da14/mod_wsgi-6.0.6.tar.gz", hash = "sha256:897f58ee961be7dcfe527074dd64d43d18ca108cf919d4c3bcf10fa6a4c12921", size = 607559, upload-time = "2026-08-19T09:51:18.52Z" } - [[package]] name = "mypy" version = "1.19.1" @@ -2483,10 +2463,6 @@ lint = [ mockupdb = [ { name = "mockupdb" }, ] -mod-wsgi = [ - { name = "mod-wsgi", version = "5.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mod-wsgi", version = "6.0.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] perf = [ { name = "simplejson" }, ] @@ -2540,7 +2516,6 @@ dev = [] gevent = [{ name = "gevent", specifier = ">=21.12" }] lint = [{ name = "ruff", specifier = "==0.15.20" }] mockupdb = [{ name = "mockupdb", specifier = ">=1.8.1" }] -mod-wsgi = [{ name = "mod-wsgi", specifier = ">=4.9.4" }] perf = [{ name = "simplejson", specifier = ">=3.17.0" }] pip = [{ name = "pip", specifier = ">=20.2" }] typing = [