From faa77eab43b16ba5ee70c10430948a1502f94e8a Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 21 Oct 2025 13:06:41 -0400 Subject: [PATCH 001/129] [Task] PYTHON-5561: Add support for PyPy 3.11 (#2596) --- .evergreen/generated_configs/tasks.yml | 168 ++++++++++---------- .evergreen/scripts/generate_config_utils.py | 2 +- .github/workflows/test-python.yml | 2 +- doc/changelog.rst | 4 +- 4 files changed, 88 insertions(+), 88 deletions(-) diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index 855cbefef8..9a7f16f543 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -319,13 +319,13 @@ tasks: vars: PYTHON_VERSION: "3.14" tags: [test-no-orchestration, python-3.14] - - name: test-no-orchestration-pypy3.10 + - name: test-no-orchestration-pypy3.11 commands: - func: assume ec2 role - func: run tests vars: - PYTHON_VERSION: pypy3.10 - tags: [test-no-orchestration, python-pypy3.10] + PYTHON_VERSION: pypy3.11 + tags: [test-no-orchestration, python-pypy3.11] # No toolchain tests - name: test-no-toolchain-sync-noauth-nossl-standalone @@ -2515,7 +2515,7 @@ tasks: - replica_set-noauth-nossl - sync - pr - - name: test-server-version-pypy3.10-async-noauth-ssl-replica-set + - name: test-server-version-pypy3.11-async-noauth-ssl-replica-set commands: - func: run server vars: @@ -2527,11 +2527,11 @@ tasks: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_async tags: - server-version - - python-pypy3.10 + - python-pypy3.11 - replica_set-noauth-ssl - async - name: test-server-version-python3.14-sync-noauth-ssl-replica-set-cov @@ -2722,7 +2722,7 @@ tasks: - sharded_cluster-auth-ssl - async - free-threaded - - name: test-server-version-pypy3.10-async-auth-ssl-sharded-cluster + - name: test-server-version-pypy3.11-async-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -2734,11 +2734,11 @@ tasks: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_async tags: - server-version - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - async - name: test-server-version-python3.10-sync-auth-ssl-sharded-cluster-cov @@ -2867,7 +2867,7 @@ tasks: - sharded_cluster-auth-ssl - sync - free-threaded - - name: test-server-version-pypy3.10-sync-auth-ssl-sharded-cluster + - name: test-server-version-pypy3.11-sync-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -2879,11 +2879,11 @@ tasks: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_sync tags: - server-version - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - sync - name: test-server-version-python3.12-async-noauth-nossl-sharded-cluster-cov @@ -2949,7 +2949,7 @@ tasks: - python-3.10 - sharded_cluster-noauth-ssl - async - - name: test-server-version-pypy3.10-sync-noauth-ssl-sharded-cluster + - name: test-server-version-pypy3.11-sync-noauth-ssl-sharded-cluster commands: - func: run server vars: @@ -2961,11 +2961,11 @@ tasks: AUTH: noauth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_sync tags: - server-version - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-noauth-ssl - sync - name: test-server-version-python3.13-async-auth-nossl-standalone-cov @@ -3074,7 +3074,7 @@ tasks: - standalone-noauth-nossl - async - pr - - name: test-server-version-pypy3.10-sync-noauth-nossl-standalone + - name: test-server-version-pypy3.11-sync-noauth-nossl-standalone commands: - func: run server vars: @@ -3086,11 +3086,11 @@ tasks: AUTH: noauth SSL: nossl TOPOLOGY: standalone - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_sync tags: - server-version - - python-pypy3.10 + - python-pypy3.11 - standalone-noauth-nossl - sync - pr @@ -3203,7 +3203,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - sync - - name: test-standard-v4.2-pypy3.10-sync-auth-ssl-sharded-cluster + - name: test-standard-v4.2-pypy3.11-sync-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -3217,12 +3217,12 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard - server-4.2 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - sync - pypy @@ -3337,7 +3337,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - async - - name: test-standard-v4.4-pypy3.10-async-auth-ssl-sharded-cluster + - name: test-standard-v4.4-pypy3.11-async-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -3351,12 +3351,12 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard - server-4.4 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - async - pypy @@ -3716,7 +3716,7 @@ tasks: - python-3.12 - standalone-noauth-nossl - sync - - name: test-standard-v7.0-pypy3.10-sync-noauth-nossl-standalone + - name: test-standard-v7.0-pypy3.11-sync-noauth-nossl-standalone commands: - func: run server vars: @@ -3730,12 +3730,12 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard - server-7.0 - - python-pypy3.10 + - python-pypy3.11 - standalone-noauth-nossl - sync - pypy @@ -3828,7 +3828,7 @@ tasks: - python-3.12 - standalone-noauth-nossl - async - - name: test-standard-v8.0-pypy3.10-async-noauth-nossl-standalone + - name: test-standard-v8.0-pypy3.11-async-noauth-nossl-standalone commands: - func: run server vars: @@ -3842,12 +3842,12 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard - server-8.0 - - python-pypy3.10 + - python-pypy3.11 - standalone-noauth-nossl - async - pypy @@ -3874,7 +3874,7 @@ tasks: - replica_set-noauth-ssl - async - pr - - name: test-standard-latest-pypy3.10-async-noauth-ssl-replica-set + - name: test-standard-latest-pypy3.11-async-noauth-ssl-replica-set commands: - func: run server vars: @@ -3888,12 +3888,12 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard - server-latest - - python-pypy3.10 + - python-pypy3.11 - replica_set-noauth-ssl - async - pypy @@ -3988,7 +3988,7 @@ tasks: - python-3.12 - replica_set-noauth-ssl - sync - - name: test-standard-rapid-pypy3.10-sync-noauth-ssl-replica-set + - name: test-standard-rapid-pypy3.11-sync-noauth-ssl-replica-set commands: - func: run server vars: @@ -4002,12 +4002,12 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard - server-rapid - - python-pypy3.10 + - python-pypy3.11 - replica_set-noauth-ssl - sync - pypy @@ -4142,7 +4142,7 @@ tasks: - python-3.10 - standalone-noauth-nossl - noauth - - name: test-non-standard-v4.2-pypy3.10-noauth-nossl-standalone + - name: test-non-standard-v4.2-pypy3.11-noauth-nossl-standalone commands: - func: run server vars: @@ -4156,11 +4156,11 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-4.2 - - python-pypy3.10 + - python-pypy3.11 - standalone-noauth-nossl - noauth - pypy @@ -4186,7 +4186,7 @@ tasks: - replica_set-noauth-ssl - noauth - free-threaded - - name: test-non-standard-v4.4-pypy3.10-noauth-ssl-replica-set + - name: test-non-standard-v4.4-pypy3.11-noauth-ssl-replica-set commands: - func: run server vars: @@ -4200,11 +4200,11 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.4" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-4.4 - - python-pypy3.10 + - python-pypy3.11 - replica_set-noauth-ssl - noauth - pypy @@ -4292,7 +4292,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v5.0-pypy3.10-auth-ssl-sharded-cluster + - name: test-non-standard-v5.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4306,11 +4306,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-5.0 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -4399,7 +4399,7 @@ tasks: - python-3.13 - standalone-noauth-nossl - noauth - - name: test-non-standard-v6.0-pypy3.10-noauth-nossl-standalone + - name: test-non-standard-v6.0-pypy3.11-noauth-nossl-standalone commands: - func: run server vars: @@ -4413,11 +4413,11 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "6.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-6.0 - - python-pypy3.10 + - python-pypy3.11 - standalone-noauth-nossl - noauth - pypy @@ -4442,7 +4442,7 @@ tasks: - python-3.11 - replica_set-noauth-ssl - noauth - - name: test-non-standard-v7.0-pypy3.10-noauth-ssl-replica-set + - name: test-non-standard-v7.0-pypy3.11-noauth-ssl-replica-set commands: - func: run server vars: @@ -4456,11 +4456,11 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "7.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-7.0 - - python-pypy3.10 + - python-pypy3.11 - replica_set-noauth-ssl - noauth - pypy @@ -4549,7 +4549,7 @@ tasks: - python-3.14 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v8.0-pypy3.10-auth-ssl-sharded-cluster + - name: test-non-standard-v8.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4563,11 +4563,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-8.0 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -4615,7 +4615,7 @@ tasks: - noauth - free-threaded - pr - - name: test-non-standard-latest-pypy3.10-noauth-ssl-replica-set + - name: test-non-standard-latest-pypy3.11-noauth-ssl-replica-set commands: - func: run server vars: @@ -4629,11 +4629,11 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-latest - - python-pypy3.10 + - python-pypy3.11 - replica_set-noauth-ssl - noauth - pypy @@ -4744,7 +4744,7 @@ tasks: - python-3.10 - standalone-noauth-nossl - noauth - - name: test-non-standard-rapid-pypy3.10-noauth-nossl-standalone + - name: test-non-standard-rapid-pypy3.11-noauth-nossl-standalone commands: - func: run server vars: @@ -4758,11 +4758,11 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-non-standard - server-rapid - - python-pypy3.10 + - python-pypy3.11 - standalone-noauth-nossl - noauth - pypy @@ -4789,7 +4789,7 @@ tasks: - python-3.10 - sharded_cluster-auth-ssl - auth - - name: test-standard-auth-v4.2-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v4.2-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4803,11 +4803,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-4.2 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -4832,7 +4832,7 @@ tasks: - python-3.11 - sharded_cluster-auth-ssl - auth - - name: test-standard-auth-v4.4-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v4.4-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4846,11 +4846,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-4.4 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -4875,7 +4875,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-standard-auth-v5.0-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v5.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4889,11 +4889,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-5.0 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -4918,7 +4918,7 @@ tasks: - python-3.13 - sharded_cluster-auth-ssl - auth - - name: test-standard-auth-v6.0-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v6.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4932,11 +4932,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-6.0 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -4962,7 +4962,7 @@ tasks: - sharded_cluster-auth-ssl - auth - free-threaded - - name: test-standard-auth-v7.0-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v7.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4976,11 +4976,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-7.0 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -5005,7 +5005,7 @@ tasks: - python-3.14 - sharded_cluster-auth-ssl - auth - - name: test-standard-auth-v8.0-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v8.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5019,11 +5019,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-8.0 - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -5049,7 +5049,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pr - - name: test-standard-auth-latest-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-latest-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5063,11 +5063,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-latest - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy @@ -5092,7 +5092,7 @@ tasks: - python-3.10 - sharded_cluster-auth-ssl - auth - - name: test-standard-auth-rapid-pypy3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-rapid-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5106,11 +5106,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid - PYTHON_VERSION: pypy3.10 + PYTHON_VERSION: pypy3.11 tags: - test-standard-auth - server-rapid - - python-pypy3.10 + - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 4eb6bcb0dc..28ee45f9b7 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -23,7 +23,7 @@ ALL_VERSIONS = ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"] CPYTHONS = ["3.10", "3.11", "3.12", "3.13", "3.14t", "3.14"] -PYPYS = ["pypy3.10"] +PYPYS = ["pypy3.11"] ALL_PYTHONS = CPYTHONS + PYPYS MIN_MAX_PYTHON = [CPYTHONS[0], CPYTHONS[-1]] BATCHTIME_WEEK = 10080 diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index a057570f3f..4685ba2d92 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -59,7 +59,7 @@ jobs: matrix: # Tests currently only pass on ubuntu on GitHub Actions. os: [ubuntu-latest] - python-version: ["3.10", "pypy-3.10", "3.13t"] + python-version: ["3.10", "pypy-3.11", "3.13t"] mongodb-version: ["8.0"] name: CPython ${{ matrix.python-version }}-${{ matrix.os }} diff --git a/doc/changelog.rst b/doc/changelog.rst index f3eb4f6f23..47d3fafd66 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,9 +6,9 @@ Changes in Version 4.16.0 (XXXX/XX/XX) PyMongo 4.16 brings a number of changes including: -.. warning:: PyMongo 4.16 drops support for Python 3.9: Python 3.10+ is now required. +.. warning:: PyMongo 4.16 drops support for Python 3.9 and PyPy 3.10: Python 3.10+ or PyPy 3.11+ is now required. -- Dropped support for Python 3.9. +- Dropped support for Python 3.9 and PyPy 3.10. - Removed invalid documents from :class:`bson.errors.InvalidDocument` error messages as doing so may leak sensitive user data. Instead, invalid documents are stored in :attr:`bson.errors.InvalidDocument.document`. From ad1167d01ea03780bae1fdb66ddc88283ce9a983 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 21 Oct 2025 15:57:36 -0400 Subject: [PATCH 002/129] [Task]-PYTHON-5626: Remove project.license toml table (#2595) --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 623eb6c164..ef7140eddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,8 @@ name = "pymongo" dynamic = ["version", "dependencies", "optional-dependencies"] description = "PyMongo - the Official MongoDB Python driver" readme = "README.md" -license = {file="LICENSE"} +license = "Apache-2.0" +license-files = ["LICENSE"] requires-python = ">=3.9" authors = [ { name = "The MongoDB Python Team" }, @@ -22,7 +23,6 @@ keywords = [ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", From a5f6d638b9e2243cb03202c996f54b2d56022aca Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 22 Oct 2025 17:22:22 -0500 Subject: [PATCH 003/129] PYTHON-5615 Use uv python when python toolchain is not available (#2597) --- .evergreen/combine-coverage.sh | 19 +- .evergreen/generated_configs/functions.yml | 8 +- .evergreen/generated_configs/tasks.yml | 631 ++++++++++---------- .evergreen/generated_configs/variants.yml | 10 +- .evergreen/run-mongodb-aws-ecs-test.sh | 12 +- .evergreen/run-tests.sh | 2 + .evergreen/scripts/check-import-time.sh | 10 +- .evergreen/scripts/generate_config.py | 41 +- .evergreen/scripts/generate_config_utils.py | 26 - .evergreen/scripts/install-dependencies.sh | 57 +- .evergreen/scripts/kms_tester.py | 4 +- .evergreen/scripts/oidc_tester.py | 2 +- .evergreen/scripts/run_tests.py | 13 +- .evergreen/scripts/setup-dev-env.sh | 75 ++- .evergreen/scripts/setup-system.sh | 6 +- .evergreen/scripts/setup-uv-python.sh | 53 ++ .evergreen/scripts/setup_tests.py | 3 +- .evergreen/setup-spawn-host.sh | 3 +- .evergreen/utils.sh | 148 ----- CONTRIBUTING.md | 14 +- justfile | 1 - 21 files changed, 484 insertions(+), 654 deletions(-) create mode 100755 .evergreen/scripts/setup-uv-python.sh delete mode 100755 .evergreen/utils.sh diff --git a/.evergreen/combine-coverage.sh b/.evergreen/combine-coverage.sh index 36266c1842..5f04f72adb 100755 --- a/.evergreen/combine-coverage.sh +++ b/.evergreen/combine-coverage.sh @@ -5,19 +5,12 @@ set -eu -. .evergreen/utils.sh +# Set up the virtual env. +. .evergreen/scripts/setup-dev-env.sh +uv sync --group coverage +source .venv/bin/activate -if [ -z "${PYTHON_BINARY:-}" ]; then - PYTHON_BINARY=$(find_python3) -fi - -createvirtualenv "$PYTHON_BINARY" covenv -# Keep in sync with run-tests.sh -# coverage >=5 is needed for relative_files=true. -pip install -q "coverage[toml]>=5,<=7.5" - -pip list ls -la coverage/ -python -m coverage combine coverage/coverage.* -python -m coverage html -d htmlcov +coverage combine coverage/coverage.* +coverage html -d htmlcov diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index ce95648849..4b53ac6ac8 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -101,8 +101,8 @@ functions: - AUTH - SSL - ORCHESTRATION_FILE - - PYTHON_BINARY - - PYTHON_VERSION + - UV_PYTHON + - TOOLCHAIN_VERSION - STORAGE_ENGINE - REQUIRE_API_VERSION - DRIVERS_TOOLS @@ -134,10 +134,10 @@ functions: - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN - COVERAGE - - PYTHON_BINARY + - UV_PYTHON - LIBMONGOCRYPT_URL - MONGODB_URI - - PYTHON_VERSION + - TOOLCHAIN_VERSION - DISABLE_TEST_COMMANDS - GREEN_FRAMEWORK - NO_EXT diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index 9a7f16f543..cb7ae1e6c9 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -20,7 +20,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: regular - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: [auth-aws, auth-aws-regular] - name: test-auth-aws-5.0-assume-role-python3.11 commands: @@ -33,7 +33,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: assume-role - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: [auth-aws, auth-aws-assume-role] - name: test-auth-aws-6.0-ec2-python3.12 commands: @@ -46,7 +46,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: ec2 - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: [auth-aws, auth-aws-ec2] - name: test-auth-aws-7.0-env-creds-python3.13 commands: @@ -59,7 +59,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: env-creds - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: [auth-aws, auth-aws-env-creds] - name: test-auth-aws-8.0-session-creds-python3.14t commands: @@ -72,7 +72,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: session-creds - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t tags: [auth-aws, auth-aws-session-creds, free-threaded] - name: test-auth-aws-rapid-web-identity-python3.14 commands: @@ -85,7 +85,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: web-identity - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: [auth-aws, auth-aws-web-identity] - name: test-auth-aws-rapid-web-identity-session-name-python3.14 commands: @@ -99,7 +99,7 @@ tasks: TEST_NAME: auth_aws SUB_TEST_NAME: web-identity AWS_ROLE_SESSION_NAME: test - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: [auth-aws, auth-aws-web-identity] - name: test-auth-aws-latest-ecs-python3.10 commands: @@ -112,7 +112,7 @@ tasks: vars: TEST_NAME: auth_aws SUB_TEST_NAME: ecs - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: [auth-aws, auth-aws-ecs] # Backport pr tests @@ -248,60 +248,60 @@ tasks: - func: run server vars: TOPOLOGY: replica_set - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" - func: run tests vars: TEST_NAME: mod_wsgi SUB_TEST_NAME: standalone - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: [mod_wsgi, pr] - name: mod-wsgi-embedded-mode-replica-set-python3.11 commands: - func: run server vars: TOPOLOGY: replica_set - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" - func: run tests vars: TEST_NAME: mod_wsgi SUB_TEST_NAME: embedded - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: [mod_wsgi, pr] - name: mod-wsgi-replica-set-python3.12 commands: - func: run server vars: TOPOLOGY: replica_set - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" - func: run tests vars: TEST_NAME: mod_wsgi SUB_TEST_NAME: standalone - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: [mod_wsgi, pr] - name: mod-wsgi-embedded-mode-replica-set-python3.13 commands: - func: run server vars: TOPOLOGY: replica_set - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" - func: run tests vars: TEST_NAME: mod_wsgi SUB_TEST_NAME: embedded - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: [mod_wsgi, pr] - name: mod-wsgi-embedded-mode-replica-set-python3.14 commands: - func: run server vars: TOPOLOGY: replica_set - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" - func: run tests vars: TEST_NAME: mod_wsgi SUB_TEST_NAME: embedded - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: [mod_wsgi, pr] # No orchestration tests @@ -310,21 +310,21 @@ tasks: - func: assume ec2 role - func: run tests vars: - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: [test-no-orchestration, python-3.10] - name: test-no-orchestration-python3.14 commands: - func: assume ec2 role - func: run tests vars: - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: [test-no-orchestration, python-3.14] - name: test-no-orchestration-pypy3.11 commands: - func: assume ec2 role - func: run tests vars: - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: [test-no-orchestration, python-pypy3.11] # No toolchain tests @@ -379,7 +379,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v5.0-python3.10 @@ -389,7 +389,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v6.0-python3.10 @@ -399,7 +399,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v7.0-python3.10 @@ -409,7 +409,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v8.0-python3.10 @@ -419,7 +419,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-rapid-python3.10 @@ -429,7 +429,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-latest-python3.14 @@ -439,7 +439,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v4.4-python3.10 @@ -449,7 +449,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v5.0-python3.10 @@ -459,7 +459,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v6.0-python3.10 @@ -469,7 +469,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v7.0-python3.10 @@ -479,7 +479,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v8.0-python3.10 @@ -489,7 +489,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-rapid-python3.10 @@ -499,7 +499,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-latest-python3.14 @@ -509,7 +509,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v4.4-python3.10 @@ -519,7 +519,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v5.0-python3.10 @@ -529,7 +529,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v6.0-python3.10 @@ -539,7 +539,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v7.0-python3.10 @@ -549,7 +549,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v8.0-python3.10 @@ -559,7 +559,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-rapid-python3.10 @@ -569,7 +569,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-latest-python3.14 @@ -579,7 +579,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v4.4-python3.10 @@ -589,7 +589,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v5.0-python3.10 @@ -599,7 +599,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v6.0-python3.10 @@ -609,7 +609,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v7.0-python3.10 @@ -619,7 +619,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v8.0-python3.10 @@ -629,7 +629,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-rapid-python3.10 @@ -639,7 +639,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-latest-python3.14 @@ -649,7 +649,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-soft-fail-v4.4-python3.10 @@ -659,7 +659,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-soft-fail-v5.0-python3.10 @@ -669,7 +669,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-soft-fail-v6.0-python3.10 @@ -679,7 +679,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-soft-fail-v7.0-python3.10 @@ -689,7 +689,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-soft-fail-v8.0-python3.10 @@ -699,7 +699,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-soft-fail-rapid-python3.10 @@ -709,7 +709,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-soft-fail-latest-python3.14 @@ -719,7 +719,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-valid-cert-server-staples-v4.4-python3.10 @@ -729,7 +729,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -743,7 +743,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -757,7 +757,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -771,7 +771,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -785,7 +785,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -799,7 +799,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -813,7 +813,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -827,7 +827,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -841,7 +841,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -855,7 +855,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -869,7 +869,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -883,7 +883,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -897,7 +897,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -911,7 +911,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -925,7 +925,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -939,7 +939,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -953,7 +953,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -967,7 +967,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -981,7 +981,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -995,7 +995,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -1009,7 +1009,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -1023,7 +1023,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -1037,7 +1037,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -1051,7 +1051,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -1065,7 +1065,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -1079,7 +1079,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -1093,7 +1093,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -1107,7 +1107,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -1121,7 +1121,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 @@ -1131,7 +1131,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 @@ -1141,7 +1141,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 @@ -1151,7 +1151,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 @@ -1161,7 +1161,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 @@ -1171,7 +1171,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 @@ -1181,7 +1181,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10 @@ -1191,7 +1191,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 @@ -1201,7 +1201,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 @@ -1211,7 +1211,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 @@ -1221,7 +1221,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 @@ -1231,7 +1231,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 @@ -1241,7 +1241,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 @@ -1251,7 +1251,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v4.4-python3.10 @@ -1261,7 +1261,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-ecdsa, "4.4"] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v5.0-python3.10 @@ -1271,7 +1271,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-ecdsa, "5.0"] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v6.0-python3.10 @@ -1281,7 +1281,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-ecdsa, "6.0"] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v7.0-python3.10 @@ -1291,7 +1291,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-ecdsa, "7.0"] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v8.0-python3.10 @@ -1301,7 +1301,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-ecdsa, "8.0"] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-rapid-python3.10 @@ -1311,7 +1311,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-latest-python3.14 @@ -1321,7 +1321,7 @@ tasks: ORCHESTRATION_FILE: ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v4.4-python3.10 @@ -1331,7 +1331,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v5.0-python3.10 @@ -1341,7 +1341,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v6.0-python3.10 @@ -1351,7 +1351,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v7.0-python3.10 @@ -1361,7 +1361,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v8.0-python3.10 @@ -1371,7 +1371,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-rapid-python3.10 @@ -1381,7 +1381,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-latest-python3.14 @@ -1391,7 +1391,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v4.4-python3.10 @@ -1401,7 +1401,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v5.0-python3.10 @@ -1411,7 +1411,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v6.0-python3.10 @@ -1421,7 +1421,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v7.0-python3.10 @@ -1431,7 +1431,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v8.0-python3.10 @@ -1441,7 +1441,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-rapid-python3.10 @@ -1451,7 +1451,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-latest-python3.14 @@ -1461,7 +1461,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v4.4-python3.10 @@ -1471,7 +1471,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v5.0-python3.10 @@ -1481,7 +1481,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v6.0-python3.10 @@ -1491,7 +1491,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v7.0-python3.10 @@ -1501,7 +1501,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v8.0-python3.10 @@ -1511,7 +1511,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-rapid-python3.10 @@ -1521,7 +1521,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-latest-python3.14 @@ -1531,7 +1531,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v4.4-python3.10 @@ -1541,7 +1541,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v5.0-python3.10 @@ -1551,7 +1551,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v6.0-python3.10 @@ -1561,7 +1561,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v7.0-python3.10 @@ -1571,7 +1571,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v8.0-python3.10 @@ -1581,7 +1581,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-rapid-python3.10 @@ -1591,7 +1591,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-latest-python3.14 @@ -1601,7 +1601,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-soft-fail-v4.4-python3.10 @@ -1611,7 +1611,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-soft-fail-v5.0-python3.10 @@ -1621,7 +1621,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-soft-fail-v6.0-python3.10 @@ -1631,7 +1631,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-soft-fail-v7.0-python3.10 @@ -1641,7 +1641,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-soft-fail-v8.0-python3.10 @@ -1651,7 +1651,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-soft-fail-rapid-python3.10 @@ -1661,7 +1661,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-soft-fail-latest-python3.14 @@ -1671,7 +1671,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-valid-cert-server-staples-v4.4-python3.10 @@ -1681,7 +1681,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -1695,7 +1695,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -1709,7 +1709,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -1723,7 +1723,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -1737,7 +1737,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -1751,7 +1751,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -1765,7 +1765,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -1779,7 +1779,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -1793,7 +1793,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -1807,7 +1807,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -1821,7 +1821,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -1835,7 +1835,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -1849,7 +1849,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -1863,7 +1863,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -1877,7 +1877,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -1891,7 +1891,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -1905,7 +1905,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -1919,7 +1919,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -1933,7 +1933,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -1947,7 +1947,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -1961,7 +1961,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: valid-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -1975,7 +1975,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: - ocsp @@ -1989,7 +1989,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: - ocsp @@ -2003,7 +2003,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: - ocsp @@ -2017,7 +2017,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: - ocsp @@ -2031,7 +2031,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: - ocsp @@ -2045,7 +2045,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: - ocsp @@ -2059,7 +2059,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: - ocsp @@ -2073,7 +2073,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 @@ -2083,7 +2083,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 @@ -2093,7 +2093,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 @@ -2103,7 +2103,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 @@ -2113,7 +2113,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 @@ -2123,7 +2123,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 @@ -2133,7 +2133,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10 @@ -2143,7 +2143,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 @@ -2153,7 +2153,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 @@ -2163,7 +2163,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 @@ -2173,7 +2173,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 @@ -2183,7 +2183,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 @@ -2193,7 +2193,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 @@ -2203,7 +2203,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: revoked-delegate TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v4.4-python3.10 @@ -2213,7 +2213,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" tags: [ocsp, ocsp-rsa, "4.4"] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v5.0-python3.10 @@ -2223,7 +2223,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" tags: [ocsp, ocsp-rsa, "5.0"] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v6.0-python3.10 @@ -2233,7 +2233,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" tags: [ocsp, ocsp-rsa, "6.0"] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v7.0-python3.10 @@ -2243,7 +2243,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" tags: [ocsp, ocsp-rsa, "7.0"] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v8.0-python3.10 @@ -2253,7 +2253,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" tags: [ocsp, ocsp-rsa, "8.0"] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-rapid-python3.10 @@ -2263,7 +2263,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" VERSION: rapid tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-latest-python3.14 @@ -2273,7 +2273,7 @@ tasks: ORCHESTRATION_FILE: rsa-basic-tls-ocsp-mustStaple-disableStapling.json OCSP_SERVER_TYPE: no-responder TEST_NAME: ocsp - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] @@ -2385,6 +2385,7 @@ tasks: - func: run tests vars: TEST_NAME: search_index + TOOLCHAIN_VERSION: "3.10" tags: [search_index] # Server version tests @@ -2400,7 +2401,7 @@ tasks: AUTH: auth SSL: nossl TOPOLOGY: replica_set - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - server-version @@ -2422,7 +2423,7 @@ tasks: SSL: nossl TOPOLOGY: replica_set COVERAGE: "1" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: - server-version @@ -2443,7 +2444,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set COVERAGE: "1" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - server-version @@ -2464,7 +2465,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set COVERAGE: "1" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - server-version @@ -2485,7 +2486,7 @@ tasks: SSL: nossl TOPOLOGY: replica_set COVERAGE: "1" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - server-version @@ -2507,7 +2508,7 @@ tasks: SSL: nossl TOPOLOGY: replica_set COVERAGE: "1" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - server-version @@ -2527,7 +2528,7 @@ tasks: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - server-version @@ -2548,7 +2549,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set COVERAGE: "1" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: - server-version @@ -2569,7 +2570,7 @@ tasks: SSL: nossl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: - server-version @@ -2588,7 +2589,7 @@ tasks: AUTH: auth SSL: nossl TOPOLOGY: sharded_cluster - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - server-version @@ -2610,7 +2611,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - server-version @@ -2632,7 +2633,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - server-version @@ -2653,7 +2654,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - server-version @@ -2674,7 +2675,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: - server-version @@ -2695,7 +2696,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: - server-version @@ -2714,7 +2715,7 @@ tasks: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - server-version @@ -2734,7 +2735,7 @@ tasks: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - server-version @@ -2755,7 +2756,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - server-version @@ -2777,7 +2778,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - server-version @@ -2798,7 +2799,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - server-version @@ -2819,7 +2820,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: - server-version @@ -2840,7 +2841,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: - server-version @@ -2859,7 +2860,7 @@ tasks: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - server-version @@ -2879,7 +2880,7 @@ tasks: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - server-version @@ -2900,7 +2901,7 @@ tasks: SSL: nossl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - server-version @@ -2921,7 +2922,7 @@ tasks: SSL: nossl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - server-version @@ -2942,7 +2943,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster COVERAGE: "1" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - server-version @@ -2961,7 +2962,7 @@ tasks: AUTH: noauth SSL: ssl TOPOLOGY: sharded_cluster - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - server-version @@ -2982,7 +2983,7 @@ tasks: SSL: nossl TOPOLOGY: standalone COVERAGE: "1" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: - server-version @@ -3003,7 +3004,7 @@ tasks: SSL: nossl TOPOLOGY: standalone COVERAGE: "1" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - server-version @@ -3024,7 +3025,7 @@ tasks: SSL: ssl TOPOLOGY: standalone COVERAGE: "1" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - server-version @@ -3045,7 +3046,7 @@ tasks: SSL: ssl TOPOLOGY: standalone COVERAGE: "1" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - server-version @@ -3066,7 +3067,7 @@ tasks: SSL: nossl TOPOLOGY: standalone COVERAGE: "1" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - server-version @@ -3086,7 +3087,7 @@ tasks: AUTH: noauth SSL: nossl TOPOLOGY: standalone - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - server-version @@ -3108,7 +3109,7 @@ tasks: SSL: ssl TOPOLOGY: standalone COVERAGE: "1" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: - server-version @@ -3127,7 +3128,7 @@ tasks: AUTH: noauth SSL: ssl TOPOLOGY: standalone - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - server-version @@ -3151,7 +3152,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.2" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - test-standard @@ -3173,7 +3174,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.2" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: - test-standard @@ -3195,7 +3196,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - test-standard @@ -3217,7 +3218,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard @@ -3240,7 +3241,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - test-standard @@ -3262,7 +3263,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - test-standard @@ -3285,7 +3286,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.4" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - test-standard @@ -3307,7 +3308,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.4" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: - test-standard @@ -3329,7 +3330,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - test-standard @@ -3351,7 +3352,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard @@ -3374,7 +3375,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.4" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - test-standard @@ -3396,7 +3397,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.4" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - test-standard @@ -3419,7 +3420,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "5.0" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - test-standard @@ -3441,7 +3442,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "5.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - test-standard @@ -3464,7 +3465,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - test-standard @@ -3486,7 +3487,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: - test-standard @@ -3508,7 +3509,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "5.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: - test-standard @@ -3530,7 +3531,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "6.0" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - test-standard @@ -3552,7 +3553,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "6.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - test-standard @@ -3575,7 +3576,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - test-standard @@ -3597,7 +3598,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: - test-standard @@ -3619,7 +3620,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "6.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: - test-standard @@ -3641,7 +3642,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "7.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: - test-standard @@ -3663,7 +3664,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - test-standard @@ -3685,7 +3686,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - test-standard @@ -3708,7 +3709,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - test-standard @@ -3730,7 +3731,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard @@ -3753,7 +3754,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "8.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: - test-standard @@ -3775,7 +3776,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - test-standard @@ -3797,7 +3798,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - test-standard @@ -3820,7 +3821,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - test-standard @@ -3842,7 +3843,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard @@ -3865,7 +3866,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - test-standard @@ -3888,7 +3889,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard @@ -3911,7 +3912,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: - test-standard @@ -3934,7 +3935,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - test-standard @@ -3957,7 +3958,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: - test-standard @@ -3980,7 +3981,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - test-standard @@ -4002,7 +4003,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard @@ -4025,7 +4026,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: - test-standard @@ -4047,7 +4048,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - test-standard @@ -4069,7 +4070,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: - test-standard @@ -4093,7 +4094,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.2" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - server-4.2 @@ -4114,7 +4115,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - server-4.2 @@ -4135,7 +4136,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - server-4.2 @@ -4156,7 +4157,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-4.2 @@ -4178,7 +4179,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.4" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - server-4.4 @@ -4200,7 +4201,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "4.4" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-4.4 @@ -4222,7 +4223,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - server-4.4 @@ -4243,7 +4244,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.4" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - server-4.4 @@ -4264,7 +4265,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "5.0" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - server-5.0 @@ -4285,7 +4286,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - server-5.0 @@ -4306,7 +4307,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-5.0 @@ -4328,7 +4329,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "5.0" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - server-5.0 @@ -4349,7 +4350,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "6.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - server-6.0 @@ -4371,7 +4372,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - server-6.0 @@ -4392,7 +4393,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "6.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - server-6.0 @@ -4413,7 +4414,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "6.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-6.0 @@ -4435,7 +4436,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "7.0" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - server-7.0 @@ -4456,7 +4457,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "7.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-7.0 @@ -4478,7 +4479,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - server-7.0 @@ -4499,7 +4500,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - server-7.0 @@ -4520,7 +4521,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "8.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - server-8.0 @@ -4542,7 +4543,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - server-8.0 @@ -4563,7 +4564,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-8.0 @@ -4585,7 +4586,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - server-8.0 @@ -4606,7 +4607,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - server-latest @@ -4629,7 +4630,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-latest @@ -4651,7 +4652,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - server-latest @@ -4673,7 +4674,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - server-latest @@ -4695,7 +4696,7 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - server-rapid @@ -4716,7 +4717,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - server-rapid @@ -4737,7 +4738,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - server-rapid @@ -4758,7 +4759,7 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-rapid @@ -4782,7 +4783,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: - test-standard-auth - server-4.2 @@ -4803,7 +4804,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-4.2 @@ -4825,7 +4826,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: - test-standard-auth - server-4.4 @@ -4846,7 +4847,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.4" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-4.4 @@ -4868,7 +4869,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.12" tags: - test-standard-auth - server-5.0 @@ -4889,7 +4890,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-5.0 @@ -4911,7 +4912,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - PYTHON_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.13" tags: - test-standard-auth - server-6.0 @@ -4932,7 +4933,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-6.0 @@ -4954,7 +4955,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - PYTHON_VERSION: 3.14t + TOOLCHAIN_VERSION: 3.14t tags: - test-standard-auth - server-7.0 @@ -4976,7 +4977,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-7.0 @@ -4998,7 +4999,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.14" tags: - test-standard-auth - server-8.0 @@ -5019,7 +5020,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-8.0 @@ -5041,7 +5042,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - PYTHON_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.11" tags: - test-standard-auth - server-latest @@ -5063,7 +5064,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-latest @@ -5085,7 +5086,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid - PYTHON_VERSION: "3.10" + TOOLCHAIN_VERSION: "3.10" tags: - test-standard-auth - server-rapid @@ -5106,7 +5107,7 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid - PYTHON_VERSION: pypy3.11 + TOOLCHAIN_VERSION: pypy3.11 tags: - test-standard-auth - server-rapid diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 9bae5f4680..816ff8d188 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -11,7 +11,7 @@ buildvariants: VERSION: latest NO_EXT: "1" REQUIRE_FIPS: "1" - PYTHON_BINARY: /usr/bin/python3.11 + UV_PYTHON: /usr/bin/python3.11 tags: [] - name: other-hosts-rhel8-zseries-latest tasks: @@ -72,7 +72,7 @@ buildvariants: # Aws auth tests - name: auth-aws-ubuntu-20 tasks: - - name: .auth-aws !.auth-aws-ecs + - name: .auth-aws display_name: Auth AWS Ubuntu-20 run_on: - ubuntu2004-small @@ -453,14 +453,12 @@ buildvariants: SUB_TEST_NAME: pyopenssl # Search index tests - - name: search-index-helpers-rhel8-python3.10 + - name: search-index-helpers-rhel8 tasks: - name: .search_index - display_name: Search Index Helpers RHEL8 Python3.10 + display_name: Search Index Helpers RHEL8 run_on: - rhel87-small - expansions: - PYTHON_BINARY: /opt/python/3.10/bin/python3 # Server version tests - name: mongodb-v4.2 diff --git a/.evergreen/run-mongodb-aws-ecs-test.sh b/.evergreen/run-mongodb-aws-ecs-test.sh index b8330de511..414c7a0a25 100755 --- a/.evergreen/run-mongodb-aws-ecs-test.sh +++ b/.evergreen/run-mongodb-aws-ecs-test.sh @@ -19,20 +19,14 @@ fi # Now we can safely enable xtrace set -o xtrace -# Install python with pip. -PYTHON_VER="python3.10" +# Install a c compiler. apt-get -qq update < /dev/null > /dev/null -apt-get -q install -y software-properties-common -# Use openpgp to avoid gpg key timeout. -mkdir -p $HOME/.gnupg -echo "keyserver keys.openpgp.org" >> $HOME/.gnupg/gpg.conf -add-apt-repository -y 'ppa:deadsnakes/ppa' -apt-get -qq install $PYTHON_VER $PYTHON_VER-venv build-essential $PYTHON_VER-dev -y < /dev/null > /dev/null +apt-get -q install -y build-essential -export PYTHON_BINARY=$PYTHON_VER export SET_XTRACE_ON=1 cd src rm -rf .venv rm -f .evergreen/scripts/test-env.sh || true +rm -f .evergreen/scripts/env.sh || true bash ./.evergreen/just.sh setup-tests auth_aws ecs-remote bash .evergreen/just.sh run-tests diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index c14215244e..095b7938dc 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -37,6 +37,8 @@ cleanup_tests() { trap "cleanup_tests" SIGINT ERR # Start the test runner. +echo "Running tests with UV_PYTHON=${UV_PYTHON:-}..." uv run ${UV_ARGS} --reinstall-package pymongo .evergreen/scripts/run_tests.py "$@" +echo "Running tests with UV_PYTHON=${UV_PYTHON:-}... done." cleanup_tests diff --git a/.evergreen/scripts/check-import-time.sh b/.evergreen/scripts/check-import-time.sh index f7a1117b97..ce4e834708 100755 --- a/.evergreen/scripts/check-import-time.sh +++ b/.evergreen/scripts/check-import-time.sh @@ -11,11 +11,10 @@ pushd $HERE/../.. >/dev/null BASE_SHA="$1" HEAD_SHA="$2" -. .evergreen/utils.sh - -if [ -z "${PYTHON_BINARY:-}" ]; then - PYTHON_BINARY=$(find_python3) -fi +# Set up the virtual env. +. $HERE/setup-dev-env.sh +uv venv --seed +source .venv/bin/activate # Use the previous commit if this was not a PR run. if [ "$BASE_SHA" == "$HEAD_SHA" ]; then @@ -24,7 +23,6 @@ fi function get_import_time() { local log_file - createvirtualenv "$PYTHON_BINARY" import-venv python -m pip install -q ".[aws,encryption,gssapi,ocsp,snappy,zstd]" # Import once to cache modules python -c "import pymongo" diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index daec0841d5..310da42f90 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -356,12 +356,10 @@ def create_oidc_auth_variants(): def create_search_index_variants(): host = DEFAULT_HOST - python = CPYTHONS[0] return [ create_variant( [".search_index"], - get_variant_name("Search Index Helpers", host, python=python), - python=python, + get_variant_name("Search Index Helpers", host), host=host, ) ] @@ -438,8 +436,7 @@ def create_aws_auth_variants(): for host_name in ["ubuntu20", "win64", "macos"]: expansions = dict() - # PYTHON-5604 - we need to skip ECS tests for now. - tasks = [".auth-aws !.auth-aws-ecs"] + tasks = [".auth-aws"] tags = [] if host_name == "macos": tasks = [".auth-aws !.auth-aws-web-identity !.auth-aws-ecs !.auth-aws-ec2"] @@ -477,7 +474,7 @@ def create_alternative_hosts_variants(): if "fips" in host_name.lower(): expansions["REQUIRE_FIPS"] = "1" # Use explicit Python 3.11 binary on the host since the default python3 is 3.9. - expansions["PYTHON_BINARY"] = "/usr/bin/python3.11" + expansions["UV_PYTHON"] = "/usr/bin/python3.11" if "amazon" in host_name.lower(): tags.append("pr") variants.append( @@ -543,7 +540,7 @@ def create_server_version_tasks(): ) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() - test_vars["PYTHON_VERSION"] = python + test_vars["TOOLCHAIN_VERSION"] = python test_vars["TEST_NAME"] = f"default_{sync}" test_func = FunctionCall(func="run tests", vars=test_vars) tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func])) @@ -599,7 +596,7 @@ def create_test_non_standard_tasks(): name = get_task_name("test-non-standard", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() - test_vars["PYTHON_VERSION"] = python + test_vars["TOOLCHAIN_VERSION"] = python test_func = FunctionCall(func="run tests", vars=test_vars) tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func])) return tasks @@ -639,7 +636,7 @@ def create_test_standard_auth_tasks(): name = get_task_name("test-standard-auth", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() - test_vars["PYTHON_VERSION"] = python + test_vars["TOOLCHAIN_VERSION"] = python test_func = FunctionCall(func="run tests", vars=test_vars) tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func])) return tasks @@ -691,7 +688,7 @@ def create_standard_tasks(): name = get_task_name("test-standard", python=python, sync=sync, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() - test_vars["PYTHON_VERSION"] = python + test_vars["TOOLCHAIN_VERSION"] = python test_vars["TEST_NAME"] = f"default_{sync}" test_func = FunctionCall(func="run tests", vars=test_vars) tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func])) @@ -707,7 +704,7 @@ def create_no_orchestration_tasks(): ] name = get_task_name("test-no-orchestration", python=python) assume_func = FunctionCall(func="assume ec2 role") - test_vars = dict(PYTHON_VERSION=python) + test_vars = dict(TOOLCHAIN_VERSION=python) test_func = FunctionCall(func="run tests", vars=test_vars) commands = [assume_func, test_func] tasks.append(EvgTask(name=name, tags=tags, commands=commands)) @@ -756,7 +753,7 @@ def create_aws_tasks(): if "t" in python: tags.append("free-threaded") name = get_task_name(f"{base_name}-{test_type}", python=python) - test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME=test_type, PYTHON_VERSION=python) + test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME=test_type, TOOLCHAIN_VERSION=python) test_func = FunctionCall(func="run tests", vars=test_vars) funcs = [server_func, assume_func, test_func] tasks.append(EvgTask(name=name, tags=tags, commands=funcs)) @@ -768,7 +765,7 @@ def create_aws_tasks(): TEST_NAME="auth_aws", SUB_TEST_NAME="web-identity", AWS_ROLE_SESSION_NAME="test", - PYTHON_VERSION=python, + TOOLCHAIN_VERSION=python, ) if "t" in python: tags.append("free-threaded") @@ -806,9 +803,11 @@ def create_mod_wsgi_tasks(): task_name = "mod-wsgi-embedded-mode-" task_name += topology.replace("_", "-") task_name = get_task_name(task_name, python=python) - server_vars = dict(TOPOLOGY=topology, PYTHON_VERSION=python) + server_vars = dict(TOPOLOGY=topology, TOOLCHAIN_VERSION=python) server_func = FunctionCall(func="run server", vars=server_vars) - vars = dict(TEST_NAME="mod_wsgi", SUB_TEST_NAME=test.split("-")[0], PYTHON_VERSION=python) + vars = dict( + TEST_NAME="mod_wsgi", SUB_TEST_NAME=test.split("-")[0], TOOLCHAIN_VERSION=python + ) test_func = FunctionCall(func="run tests", vars=vars) tags = ["mod_wsgi", "pr"] commands = [server_func, test_func] @@ -830,7 +829,7 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name): ORCHESTRATION_FILE=file_name, OCSP_SERVER_TYPE=server_type, TEST_NAME="ocsp", - PYTHON_VERSION=python, + TOOLCHAIN_VERSION=python, VERSION=version, ) test_func = FunctionCall(func="run tests", vars=vars) @@ -864,7 +863,7 @@ def create_aws_lambda_tasks(): def create_search_index_tasks(): assume_func = FunctionCall(func="assume ec2 role") server_func = FunctionCall(func="run server", vars=dict(TEST_NAME="search_index")) - vars = dict(TEST_NAME="search_index") + vars = dict(TEST_NAME="search_index", TOOLCHAIN_VERSION=CPYTHONS[0]) test_func = FunctionCall(func="run tests", vars=vars) task_name = "test-search-index-helpers" tags = ["search_index"] @@ -1075,8 +1074,8 @@ def create_run_server_func(): "AUTH", "SSL", "ORCHESTRATION_FILE", - "PYTHON_BINARY", - "PYTHON_VERSION", + "UV_PYTHON", + "TOOLCHAIN_VERSION", "STORAGE_ENGINE", "REQUIRE_API_VERSION", "DRIVERS_TOOLS", @@ -1100,10 +1099,10 @@ def create_run_tests_func(): "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "COVERAGE", - "PYTHON_BINARY", + "UV_PYTHON", "LIBMONGOCRYPT_URL", "MONGODB_URI", - "PYTHON_VERSION", + "TOOLCHAIN_VERSION", "DISABLE_TEST_COMMANDS", "GREEN_FRAMEWORK", "NO_EXT", diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 28ee45f9b7..a32092f5b2 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -133,43 +133,17 @@ def create_variant( *, version: str | None = None, host: Host | str | None = None, - python: str | None = None, expansions: dict | None = None, **kwargs: Any, ) -> BuildVariant: expansions = expansions and expansions.copy() or dict() if version: expansions["VERSION"] = version - if python: - expansions["PYTHON_BINARY"] = get_python_binary(python, host) return create_variant_generic( tasks, display_name, version=version, host=host, expansions=expansions, **kwargs ) -def get_python_binary(python: str, host: Host) -> str: - """Get the appropriate python binary given a python version and host.""" - name = host.name - if name in ["win64", "win32"]: - if name == "win32": - base = "C:/python/32" - else: - base = "C:/python" - python_dir = python.replace(".", "").replace("t", "") - return f"{base}/Python{python_dir}/python{python}.exe" - - if name in ["rhel8", "ubuntu22", "ubuntu20", "rhel7"]: - return f"/opt/python/{python}/bin/python3" - - if name in ["macos", "macos-arm64"]: - bin_name = "python3t" if "t" in python else "python3" - python_dir = python.replace("t", "") - framework_dir = "PythonT" if "t" in python else "Python" - return f"/Library/Frameworks/{framework_dir}.Framework/Versions/{python_dir}/bin/{bin_name}" - - raise ValueError(f"no match found for python {python} on {name}") - - def get_versions_from(min_version: str) -> list[str]: """Get all server versions starting from a minimum version.""" min_version_float = float(min_version) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 23d865d0d8..8df2af79ca 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Install the dependencies needed for an evergreen run. +# Install the necessary dependencies. set -eu HERE=$(dirname ${BASH_SOURCE:-$0}) @@ -13,50 +13,6 @@ fi # Set up the default bin directory. if [ -z "${PYMONGO_BIN_DIR:-}" ]; then PYMONGO_BIN_DIR="$HOME/.local/bin" - export PATH="$PYMONGO_BIN_DIR:$PATH" -fi - -# Helper function to pip install a dependency using a temporary python env. -function _pip_install() { - _HERE=$(dirname ${BASH_SOURCE:-$0}) - . $_HERE/../utils.sh - _VENV_PATH=$(mktemp -d) - if [ "Windows_NT" = "${OS:-}" ]; then - _VENV_PATH=$(cygpath -m $_VENV_PATH) - fi - echo "Installing $2 using pip..." - createvirtualenv "$(find_python3)" $_VENV_PATH - python -m pip install $1 - _suffix="" - if [ "Windows_NT" = "${OS:-}" ]; then - _suffix=".exe" - fi - ln -s "$(which $2)" $PYMONGO_BIN_DIR/${2}${_suffix} - # uv also comes with a uvx binary. - if [ $2 == "uv" ]; then - ln -s "$(which uvx)" $PYMONGO_BIN_DIR/uvx${_suffix} - fi - echo "Installed to ${PYMONGO_BIN_DIR}" - echo "Installing $2 using pip... done." -} - -# Ensure just is installed. -if ! command -v just &>/dev/null; then - # On most systems we can install directly. - _TARGET="" - if [ "Windows_NT" = "${OS:-}" ]; then - _TARGET="--target x86_64-pc-windows-msvc" - fi - _BIN_DIR=$PYMONGO_BIN_DIR - mkdir -p ${_BIN_DIR} - echo "Installing just..." - mkdir -p "$_BIN_DIR" 2>/dev/null || true - curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- $_TARGET --to "$_BIN_DIR" || { - # Remove just file if it exists (can be created if there was an install error). - rm -f ${_BIN_DIR}/just - _pip_install rust-just just - } - echo "Installing just... done." fi # Ensure uv is installed. @@ -64,14 +20,17 @@ if ! command -v uv &>/dev/null; then _BIN_DIR=$PYMONGO_BIN_DIR mkdir -p ${_BIN_DIR} echo "Installing uv..." - # On most systems we can install directly. - curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$_BIN_DIR" INSTALLER_NO_MODIFY_PATH=1 sh || { - _pip_install uv uv - } + 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 +fi + popd > /dev/null diff --git a/.evergreen/scripts/kms_tester.py b/.evergreen/scripts/kms_tester.py index e3833ae63a..9bafdc1f64 100644 --- a/.evergreen/scripts/kms_tester.py +++ b/.evergreen/scripts/kms_tester.py @@ -33,7 +33,7 @@ def _setup_azure_vm(base_env: dict[str, str]) -> None: env["AZUREKMS_CMD"] = "sudo apt-get install -y python3-dev build-essential" run_command(f"{azure_dir}/run-command.sh", env=env) - env["AZUREKMS_CMD"] = "NO_EXT=1 bash .evergreen/just.sh setup-tests kms azure-remote" + env["AZUREKMS_CMD"] = "bash .evergreen/just.sh setup-tests kms azure-remote" run_command(f"{azure_dir}/run-command.sh", env=env) LOGGER.info("Setting up Azure VM... done.") @@ -53,7 +53,7 @@ def _setup_gcp_vm(base_env: dict[str, str]) -> None: env["GCPKMS_CMD"] = "sudo apt-get install -y python3-dev build-essential" run_command(f"{gcp_dir}/run-command.sh", env=env) - env["GCPKMS_CMD"] = "NO_EXT=1 bash ./.evergreen/just.sh setup-tests kms gcp-remote" + env["GCPKMS_CMD"] = "bash ./.evergreen/just.sh setup-tests kms gcp-remote" run_command(f"{gcp_dir}/run-command.sh", env=env) LOGGER.info("Setting up GCP VM...") diff --git a/.evergreen/scripts/oidc_tester.py b/.evergreen/scripts/oidc_tester.py index ac2960371e..a8949a9381 100644 --- a/.evergreen/scripts/oidc_tester.py +++ b/.evergreen/scripts/oidc_tester.py @@ -89,7 +89,7 @@ def test_oidc_send_to_remote(sub_test_name: str) -> None: env[f"{upper_name}OIDC_DRIVERS_TAR_FILE"] = TMP_DRIVER_FILE env[ f"{upper_name}OIDC_TEST_CMD" - ] = f"NO_EXT=1 OIDC_ENV={sub_test_name} ./.evergreen/run-mongodb-oidc-test.sh" + ] = f"OIDC_ENV={sub_test_name} ./.evergreen/run-mongodb-oidc-test.sh" elif sub_test_name in K8S_NAMES: env["K8S_DRIVERS_TAR_FILE"] = TMP_DRIVER_FILE env["K8S_TEST_CMD"] = "OIDC_ENV=k8s ./.evergreen/run-mongodb-oidc-test.sh" diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py index c1c29c58bc..9c8101c5b1 100644 --- a/.evergreen/scripts/run_tests.py +++ b/.evergreen/scripts/run_tests.py @@ -106,10 +106,11 @@ def handle_aws_lambda() -> None: env["TEST_LAMBDA_DIRECTORY"] = str(target_dir) env.setdefault("AWS_REGION", "us-east-1") dirs = ["pymongo", "gridfs", "bson"] - # Store the original .so files. - before_sos = [] + # Remove the original .so files. for dname in dirs: - before_sos.extend(f"{f.parent.name}/{f.name}" for f in (ROOT / dname).glob("*.so")) + so_paths = [f"{f.parent.name}/{f.name}" for f in (ROOT / dname).glob("*.so")] + for so_path in list(so_paths): + Path(so_path).unlink() # Build the c extensions. docker = which("docker") or which("podman") if not docker: @@ -122,15 +123,11 @@ def handle_aws_lambda() -> None: target = ROOT / "test/lambda/mongodb" / dname shutil.rmtree(target, ignore_errors=True) shutil.copytree(ROOT / dname, target) - # Remove the original so files from the lambda directory. - for so_path in before_sos: - (ROOT / "test/lambda/mongodb" / so_path).unlink() # Remove the new so files from the ROOT directory. for dname in dirs: so_paths = [f"{f.parent.name}/{f.name}" for f in (ROOT / dname).glob("*.so")] for so_path in list(so_paths): - if so_path not in before_sos: - Path(so_path).unlink() + Path(so_path).unlink() script_name = "run-deployed-lambda-aws-tests.sh" run_command(f"bash {DRIVERS_TOOLS}/.evergreen/aws_lambda/{script_name}", env=env) diff --git a/.evergreen/scripts/setup-dev-env.sh b/.evergreen/scripts/setup-dev-env.sh index 209857d542..fa5f86d798 100755 --- a/.evergreen/scripts/setup-dev-env.sh +++ b/.evergreen/scripts/setup-dev-env.sh @@ -1,22 +1,17 @@ #!/bin/bash -# Set up a development environment on an evergreen host. +# Set up development environment. set -eu HERE=$(dirname ${BASH_SOURCE:-$0}) HERE="$( cd -- "$HERE" > /dev/null 2>&1 && pwd )" ROOT=$(dirname "$(dirname $HERE)") -pushd $ROOT > /dev/null - -# Bail early if running on GitHub Actions. -if [ -n "${GITHUB_ACTION:-}" ]; then - exit 0 -fi # Source the env files to pick up common variables. if [ -f $HERE/env.sh ]; then . $HERE/env.sh fi -# PYTHON_BINARY or PYTHON_VERSION may be defined in test-env.sh. + +# Get variables defined in test-env.sh. if [ -f $HERE/test-env.sh ]; then . $HERE/test-env.sh fi @@ -24,36 +19,40 @@ fi # Ensure dependencies are installed. bash $HERE/install-dependencies.sh -# Get the appropriate UV_PYTHON. -. $ROOT/.evergreen/utils.sh - -if [ -z "${PYTHON_BINARY:-}" ]; then - if [ -n "${PYTHON_VERSION:-}" ]; then - PYTHON_BINARY=$(get_python_binary $PYTHON_VERSION) - else - PYTHON_BINARY=$(find_python3) +# 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 + # Add the default install path to the path if needed. + if [ -z "${PYMONGO_BIN_DIR:-}" ]; then + export PATH="$PATH:$HOME/.local/bin" + fi + + # Set up venv, making sure c extensions build unless disabled. + if [ -z "${NO_EXT:-}" ]; then + export PYMONGO_C_EXT_MUST_BUILD=1 + fi + + ( + cd $ROOT && uv sync + ) + + # Set up build utilities on Windows spawn hosts. + if [ -f $HOME/.visualStudioEnv.sh ]; then + set +u + SSH_TTY=1 source $HOME/.visualStudioEnv.sh + set -u + fi + + # Only set up pre-commit if we are in a git checkout. + if [ -f $HERE/.git ]; then + if ! command -v pre-commit &>/dev/null; then + uv tool install pre-commit fi -fi -export UV_PYTHON=${PYTHON_BINARY} -echo "Using python $UV_PYTHON" -# Add the default install path to the path if needed. -if [ -z "${PYMONGO_BIN_DIR:-}" ]; then - export PATH="$PATH:$HOME/.local/bin" -fi - -# Set up venv, making sure c extensions build unless disabled. -if [ -z "${NO_EXT:-}" ]; then - export PYMONGO_C_EXT_MUST_BUILD=1 -fi -# Set up visual studio env on Windows spawn hosts. -if [ -f $HOME/.visualStudioEnv.sh ]; then - set +u - SSH_TTY=1 source $HOME/.visualStudioEnv.sh - set -u + if [ ! -f .git/hooks/pre-commit ]; then + uvx pre-commit install + fi + fi fi -uv sync - -echo "Setting up python environment... done." - -popd > /dev/null diff --git a/.evergreen/scripts/setup-system.sh b/.evergreen/scripts/setup-system.sh index 9158414cce..bd7e2dd4bc 100755 --- a/.evergreen/scripts/setup-system.sh +++ b/.evergreen/scripts/setup-system.sh @@ -8,9 +8,13 @@ echo "Setting up system..." bash .evergreen/scripts/configure-env.sh source .evergreen/scripts/env.sh bash $DRIVERS_TOOLS/.evergreen/setup.sh -bash .evergreen/scripts/install-dependencies.sh popd +# Run spawn host-specific tasks. +if [ -z "${CI:-}" ]; then + bash $HERE/setup-dev-env.sh +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-python.sh b/.evergreen/scripts/setup-uv-python.sh new file mode 100755 index 0000000000..a32e4a8f92 --- /dev/null +++ b/.evergreen/scripts/setup-uv-python.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Set up the UV_PYTHON variable. +set -eu + +HERE=$(dirname ${BASH_SOURCE:-$0}) +HERE="$( cd -- "$HERE" > /dev/null 2>&1 && pwd )" + +# Use min supported version by default. +_python="3.10" + +# Source the env files to pick up common variables. +if [ -f $HERE/env.sh ]; then + . $HERE/env.sh +fi + +# Get variables defined in test-env.sh. +if [ -f $HERE/test-env.sh ]; then + . $HERE/test-env.sh +fi + +if [ -z "${UV_PYTHON:-}" ]; then + set -x + # Translate a TOOLCHAIN_VERSION to UV_PYTHON. + if [ -n "${TOOLCHAIN_VERSION:-}" ]; then + _python=$TOOLCHAIN_VERSION + if [ "$(uname -s)" = "Darwin" ]; then + if [[ "$_python" == *"t"* ]]; then + binary_name="python3t" + framework_dir="PythonT" + else + binary_name="python3" + framework_dir="Python" + fi + _python=$(echo "$_python" | sed 's/t//g') + _python="/Library/Frameworks/$framework_dir.Framework/Versions/$_python/bin/$binary_name" + elif [ "Windows_NT" = "${OS:-}" ]; then + _python=$(echo $_python | cut -d. -f1,2 | sed 's/\.//g; s/t//g') + if [[ "$TOOLCHAIN_VERSION" == *"t"* ]]; then + _exe="python${TOOLCHAIN_VERSION}.exe" + else + _exe="python.exe" + fi + if [ -n "${IS_WIN32:-}" ]; then + _python="C:/python/32/Python${_python}/${_exe}" + else + _python="C:/python/Python${_python}/${_exe}" + fi + elif [ -d "/opt/python/$_python/bin" ]; then + _python="/opt/python/$_python/bin/python3" + fi + fi + export UV_PYTHON="$_python" +fi diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 3f0a8cc7f9..7908836f50 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -31,8 +31,7 @@ "NO_EXT", "MONGODB_API_VERSION", "DEBUG_LOG", - "PYTHON_BINARY", - "PYTHON_VERSION", + "UV_PYTHON", "REQUIRE_FIPS", "IS_WIN32", ] diff --git a/.evergreen/setup-spawn-host.sh b/.evergreen/setup-spawn-host.sh index bada61e568..3db18d15ab 100755 --- a/.evergreen/setup-spawn-host.sh +++ b/.evergreen/setup-spawn-host.sh @@ -15,5 +15,4 @@ echo "Copying files to $target..." rsync -az -e ssh --exclude '.git' --filter=':- .gitignore' -r . $target:$remote_dir echo "Copying files to $target... done" -ssh $target $remote_dir/.evergreen/scripts/setup-system.sh -ssh $target "cd $remote_dir && PYTHON_BINARY=${PYTHON_BINARY:-} .evergreen/scripts/setup-dev-env.sh" +ssh $target "$remote_dir/.evergreen/scripts/setup-system.sh" diff --git a/.evergreen/utils.sh b/.evergreen/utils.sh deleted file mode 100755 index dadb7db084..0000000000 --- a/.evergreen/utils.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/bin/bash -# Utility functions used by pymongo evergreen scripts. -set -eu - -find_python3() { - PYTHON="" - # Find a suitable toolchain version, if available. - if [ "$(uname -s)" = "Darwin" ]; then - PYTHON="/Library/Frameworks/Python.Framework/Versions/3.10/bin/python3" - elif [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin - PYTHON="C:/python/Python310/python.exe" - else - # Prefer our own toolchain, fall back to mongodb toolchain if it has Python 3.10+. - if [ -f "/opt/python/3.10/bin/python3" ]; then - PYTHON="/opt/python/Current/bin/python3" - elif is_python_310 "$(command -v /opt/mongodbtoolchain/v5/bin/python3)"; then - PYTHON="/opt/mongodbtoolchain/v5/bin/python3" - elif is_python_310 "$(command -v /opt/mongodbtoolchain/v4/bin/python3)"; then - PYTHON="/opt/mongodbtoolchain/v4/bin/python3" - elif is_python_310 "$(command -v /opt/mongodbtoolchain/v3/bin/python3)"; then - PYTHON="/opt/mongodbtoolchain/v3/bin/python3" - fi - fi - # Add a fallback system python3 if it is available and Python 3.10+. - if [ -z "$PYTHON" ]; then - if is_python_310 "$(command -v python3)"; then - PYTHON="$(command -v python3)" - fi - fi - if [ -z "$PYTHON" ]; then - echo "Cannot test without python3.10+ installed!" - exit 1 - fi - echo "$PYTHON" -} - -# Usage: -# createvirtualenv /path/to/python /output/path/for/venv -# * param1: Python binary to use for the virtualenv -# * param2: Path to the virtualenv to create -createvirtualenv () { - PYTHON=$1 - VENVPATH=$2 - - # Prefer venv - VENV="$PYTHON -m venv" - if [ "$(uname -s)" = "Darwin" ]; then - VIRTUALENV="$PYTHON -m virtualenv" - else - VIRTUALENV=$(command -v virtualenv 2>/dev/null || echo "$PYTHON -m virtualenv") - VIRTUALENV="$VIRTUALENV -p $PYTHON" - fi - if ! $VENV $VENVPATH 2>/dev/null; then - # Workaround for bug in older versions of virtualenv. - $VIRTUALENV $VENVPATH 2>/dev/null || $VIRTUALENV $VENVPATH - fi - if [ "Windows_NT" = "${OS:-}" ]; then - # Workaround https://bugs.python.org/issue32451: - # mongovenv/Scripts/activate: line 3: $'\r': command not found - dos2unix $VENVPATH/Scripts/activate || true - . $VENVPATH/Scripts/activate - else - . $VENVPATH/bin/activate - fi - - export PIP_QUIET=1 - python -m pip install --upgrade pip -} - -# Usage: -# testinstall /path/to/python /path/to/.whl ["no-virtualenv"] -# * param1: Python binary to test -# * param2: Path to the wheel to install -# * param3 (optional): If set to a non-empty string, don't create a virtualenv. Used in manylinux containers. -testinstall () { - PYTHON=$1 - RELEASE=$2 - NO_VIRTUALENV=$3 - PYTHON_IMPL=$(python -c "import platform; print(platform.python_implementation())") - - if [ -z "$NO_VIRTUALENV" ]; then - createvirtualenv $PYTHON venvtestinstall - PYTHON=python - fi - - $PYTHON -m pip install --upgrade $RELEASE - cd tools - - if [ "$PYTHON_IMPL" = "CPython" ]; then - $PYTHON fail_if_no_c.py - fi - - $PYTHON -m pip uninstall -y pymongo - cd .. - - if [ -z "$NO_VIRTUALENV" ]; then - deactivate - rm -rf venvtestinstall - fi -} - -# Function that returns success if the provided Python binary is version 3.10 or later -# Usage: -# is_python_310 /path/to/python -# * param1: Python binary -is_python_310() { - if [ -z "$1" ]; then - return 1 - elif $1 -c "import sys; exit(sys.version_info[:2] < (3, 10))"; then - # runs when sys.version_info[:2] >= (3, 10) - return 0 - else - return 1 - fi -} - - -# Function that gets a python binary given a python version string. -# Versions can be of the form 3.xx or pypy3.xx. -get_python_binary() { - version=$1 - if [ "$(uname -s)" = "Darwin" ]; then - if [[ "$version" == *"t"* ]]; then - binary_name="python3t" - framework_dir="PythonT" - else - binary_name="python3" - framework_dir="Python" - fi - version=$(echo "$version" | sed 's/t//g') - PYTHON="/Library/Frameworks/$framework_dir.Framework/Versions/$version/bin/$binary_name" - elif [ "Windows_NT" = "${OS:-}" ]; then - version=$(echo $version | cut -d. -f1,2 | sed 's/\.//g; s/t//g') - if [ -n "${IS_WIN32:-}" ]; then - PYTHON="C:/python/32/Python$version/python.exe" - else - PYTHON="C:/python/Python$version/python.exe" - fi - else - PYTHON="/opt/python/$version/bin/python3" - fi - if is_python_310 "$(command -v $PYTHON)"; then - echo "$PYTHON" - else - echo "Could not find suitable python binary for '$version'" >&2 - return 1 - fi -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8881db9cb..2cf15a9838 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -194,7 +194,7 @@ 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, remove the `.venv` folder and set `PYTHON_BINARY` before running `just install`. + version of Python, set `UV_PYTHON` before running `just install`. - 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-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/evergreen/run-orchestration.sh -h` @@ -335,7 +335,7 @@ Locally you can run: - Run `just run-server`. - Run `just setup-tests`. -- Run `UV_PYTHON=3.13t just run-tests`. +- Run `UV_PYTHON=3.14t just run-tests`. ### AWS Lambda tests @@ -399,6 +399,16 @@ To run any of the test suites with minimum supported dependencies, pass `--test- - If there are any special test considerations, including not running `pytest` at all, handle it in `.evergreen/scripts/run_tests.py`. - If there are any services or atlas clusters to teardown, handle them in `.evergreen/scripts/teardown_tests.py`. - Add functions to generate the test variant(s) and task(s) to the `.evergreen/scripts/generate_config.py`. +- There are some considerations about the Python version used in the test: + - If a specific version of Python is needed in a task that is running on variants with a toolchain, use +``TOOLCHAIN_VERSION`` (e.g. `TOOLCHAIN_VERSION=3.10`). The actual path lookup needs to be done on the host, since +tasks are host-agnostic. + - If a specific Python binary is needed (for example on the FIPS host), set `UV_PYTHON=/path/to/python`. + - If a specific Python version is needed and the toolchain will not be available, use `UV_PYTHON` (e.g. `UV_PYTHON=3.11`). + - The default if neither ``TOOLCHAIN_VERSION`` or ``UV_PYTHON`` is set is to use UV to install the minimum + 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. - 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 17b95e87b7..92bdee5be3 100644 --- a/justfile +++ b/justfile @@ -18,7 +18,6 @@ resync: install: bash .evergreen/scripts/setup-dev-env.sh - uvx pre-commit install [group('docs')] docs: && resync From 0c8a22b87d40d2afcec65de08c5f76505fa03091 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Fri, 24 Oct 2025 15:26:46 -0400 Subject: [PATCH 004/129] PYTHON-5627 - Update feedback link (#2601) --- doc/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index 85812d1b14..9a2c3eb6b2 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -37,7 +37,7 @@ project. Feature Requests / Feedback --------------------------- -Use our `feedback engine `_ +Use our `feedback engine `_ to send us feature requests and general feedback about PyMongo. Contributing From fd025503491e2bc939d8272770623c47dbaa3fd7 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 27 Oct 2025 11:41:14 -0400 Subject: [PATCH 005/129] PYTHON-5628 - Update the link for help in the documentation (#2602) --- doc/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index 9a2c3eb6b2..17b12e51a8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -22,7 +22,7 @@ work with MongoDB from Python. Getting Help ------------ If you're having trouble or have questions about PyMongo, ask your question on -our `MongoDB Community Forum `_. +one of the platforms listed on `Technical Support `_. You may also want to consider a `commercial support subscription `_. Once you get an answer, it'd be great if you could work it back into this From b607ef144cf0c1e379f8c83677a614cd44670722 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 29 Oct 2025 14:30:18 -0400 Subject: [PATCH 006/129] PYTHON-5214 - Improve BSON decoding InvalidBSON error message (#2605) --- bson/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bson/__init__.py b/bson/__init__.py index d260fb876f..ebb1bd0ccc 100644 --- a/bson/__init__.py +++ b/bson/__init__.py @@ -1109,7 +1109,9 @@ def _decode_all(data: _ReadableBuffer, opts: CodecOptions[_DocumentType]) -> lis while position < end: obj_size = _UNPACK_INT_FROM(data, position)[0] if data_len - position < obj_size: - raise InvalidBSON("invalid object size") + raise InvalidBSON( + f"invalid object size: expected {obj_size}, got {data_len - position}" + ) obj_end = position + obj_size - 1 if data[obj_end] != 0: raise InvalidBSON("bad eoo") From 5f00966f9ce591cbff982bea56df888875b80fb7 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 29 Oct 2025 14:31:25 -0400 Subject: [PATCH 007/129] [TASK]-[PYTHON-5623]: Change with_transaction callback return type to Awaitable (#2594) Co-authored-by: Logan Pulley --- doc/changelog.rst | 16 ++++++++++++++++ pymongo/asynchronous/client_session.py | 4 ++-- test/asynchronous/test_transactions.py | 12 ++++++++++++ test/test_transactions.py | 12 ++++++++++++ tools/synchro.py | 8 ++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 47d3fafd66..52e282228f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -17,6 +17,22 @@ PyMongo 4.16 brings a number of changes including: - Removed support for Eventlet. Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. +Changes in Version 4.15.4 (2025/10/21) +-------------------------------------- + +Version 4.15.4 is a bug fix release. + +- Relaxed the callback type of :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.with_transaction` to allow the broader Awaitable type rather than only Coroutine objects. +- Added the missing Python 3.14 trove classifier to the package metadata. + +Issues Resolved +............... + +See the `PyMongo 4.15.4 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 4.15.4 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=47237 + Changes in Version 4.15.3 (2025/10/07) -------------------------------------- diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index 8674e98447..6ab3b39983 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -143,8 +143,8 @@ TYPE_CHECKING, Any, AsyncContextManager, + Awaitable, Callable, - Coroutine, Mapping, MutableMapping, NoReturn, @@ -604,7 +604,7 @@ def _inherit_option(self, name: str, val: _T) -> _T: async def with_transaction( self, - callback: Callable[[AsyncClientSession], Coroutine[Any, Any, _T]], + callback: Callable[[AsyncClientSession], Awaitable[_T]], read_concern: Optional[ReadConcern] = None, write_concern: Optional[WriteConcern] = None, read_preference: Optional[_ServerMode] = None, diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 478710362e..29c5d26423 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -15,6 +15,7 @@ """Execute Transactions Spec tests.""" from __future__ import annotations +import asyncio import sys from io import BytesIO from test.asynchronous.utils_spec_runner import AsyncSpecRunner @@ -468,6 +469,17 @@ async def callback2(session): async with self.client.start_session() as s: self.assertEqual(await s.with_transaction(callback2), "Foo") + @async_client_context.require_transactions + @async_client_context.require_async + async def test_callback_awaitable_no_coroutine(self): + def callback(_): + future = asyncio.Future() + future.set_result("Foo") + return future + + async with self.client.start_session() as s: + self.assertEqual(await s.with_transaction(callback), "Foo") + @async_client_context.require_transactions async def test_callback_not_retried_after_timeout(self): listener = OvertCommandListener() diff --git a/test/test_transactions.py b/test/test_transactions.py index 813d6a688d..37e1a249e0 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -15,6 +15,7 @@ """Execute Transactions Spec tests.""" from __future__ import annotations +import asyncio import sys from io import BytesIO from test.utils_spec_runner import SpecRunner @@ -460,6 +461,17 @@ def callback2(session): with self.client.start_session() as s: self.assertEqual(s.with_transaction(callback2), "Foo") + @client_context.require_transactions + @client_context.require_async + def test_callback_awaitable_no_coroutine(self): + def callback(_): + future = asyncio.Future() + future.set_result("Foo") + return future + + with self.client.start_session() as s: + self.assertEqual(s.with_transaction(callback), "Foo") + @client_context.require_transactions def test_callback_not_retried_after_timeout(self): listener = OvertCommandListener() diff --git a/tools/synchro.py b/tools/synchro.py index e3d4835502..1444b22994 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -322,6 +322,14 @@ def translate_coroutine_types(lines: list[str]) -> list[str]: index = lines.index(type) new = type.replace(old, res.group(3)) lines[index] = new + coroutine_types = [line for line in lines if "Awaitable[" in line] + for type in coroutine_types: + res = re.search(r"Awaitable\[([A-z]+)\]", type) + if res: + old = res[0] + index = lines.index(type) + new = type.replace(old, res.group(1)) + lines[index] = new return lines From f278e471d1637d297551477487b2d9ef25656374 Mon Sep 17 00:00:00 2001 From: Rogdham <3994389+Rogdham@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:14:14 +0100 Subject: [PATCH 008/129] PYTHON-5522: Support std lib zstandard in 3.14 (#2592) --- .evergreen/generated_configs/variants.yml | 9 + .evergreen/scripts/generate_config.py | 16 ++ README.md | 3 +- doc/changelog.rst | 1 + pymongo/asynchronous/mongo_client.py | 4 +- pymongo/compression_support.py | 42 +++-- pymongo/synchronous/mongo_client.py | 4 +- requirements/zstd.txt | 2 +- uv.lock | 205 ++++++++++++---------- 9 files changed, 172 insertions(+), 114 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 816ff8d188..4efa338991 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -133,6 +133,15 @@ buildvariants: - rhel87-small expansions: COMPRESSOR: zstd + - name: compression-zstd-ubuntu-22 + tasks: + - name: .test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14 + - name: .test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14t + display_name: Compression zstd Ubuntu-22 + run_on: + - ubuntu2204-small + expansions: + COMPRESSOR: ztsd # Coverage report tests - name: coverage-report diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 310da42f90..26580cfae1 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -194,6 +194,22 @@ def create_compression_variants(): expansions=expansions, ) ) + # Add explicit tests with compression.zstd support on linux. + host = HOSTS["ubuntu22"] + expansions = dict(COMPRESSOR="ztsd") + tasks = [ + ".test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14", + ".test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14t", + ] + display_name = get_variant_name(f"Compression {compressor}", host) + variants.append( + create_variant( + tasks, + display_name, + host=host, + expansions=expansions, + ) + ) return variants diff --git a/README.md b/README.md index ba1688cb70..b8bfa294a6 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,8 @@ python -m pip install "pymongo[snappy]" ``` Wire protocol compression with zstandard requires -[zstandard](https://pypi.org/project/zstandard): +[backports.zstd](https://pypi.org/project/backports.zstd) +when used with Python versions before 3.14: ```bash python -m pip install "pymongo[zstd]" diff --git a/doc/changelog.rst b/doc/changelog.rst index 52e282228f..dbf24aaaad 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -16,6 +16,7 @@ PyMongo 4.16 brings a number of changes including: Python 3.10+. The minimum version is ``2.6.1`` to account for `CVE-2023-29483 `_. - Removed support for Eventlet. Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. +- Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions. Changes in Version 4.15.4 (2025/10/21) -------------------------------------- diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index d9bf808d55..2a8ff43392 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -422,8 +422,8 @@ def __init__( with the server. Currently supported options are "snappy", "zlib" and "zstd". Support for snappy requires the `python-snappy `_ package. - zlib support requires the Python standard library zlib module. zstd - requires the `zstandard `_ + zlib support requires the Python standard library zlib module. For + Python before 3.14 zstd requires the `backports.zstd `_ package. By default no compression is used. Compression support must also be enabled on the server. MongoDB 3.6+ supports snappy and zlib compression. MongoDB 4.2+ adds support for zstd. diff --git a/pymongo/compression_support.py b/pymongo/compression_support.py index 64ffe052ec..f7ed1aadcc 100644 --- a/pymongo/compression_support.py +++ b/pymongo/compression_support.py @@ -13,6 +13,7 @@ # limitations under the License. from __future__ import annotations +import sys import warnings from typing import Any, Iterable, Optional, Union @@ -44,7 +45,10 @@ def _have_zlib() -> bool: def _have_zstd() -> bool: try: - import zstandard # noqa: F401 + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd # noqa: F401 return True except ImportError: @@ -79,11 +83,18 @@ def validate_compressors(dummy: Any, value: Union[str, Iterable[str]]) -> list[s ) elif compressor == "zstd" and not _have_zstd(): compressors.remove(compressor) - warnings.warn( - "Wire protocol compression with zstandard is not available. " - "You must install the zstandard module for zstandard support.", - stacklevel=2, - ) + if sys.version_info >= (3, 14): + warnings.warn( + "Wire protocol compression with zstandard is not available. " + "The compression.zstd module is not available.", + stacklevel=2, + ) + else: + warnings.warn( + "Wire protocol compression with zstandard is not available. " + "You must install the backports.zstd module for zstandard support.", + stacklevel=2, + ) return compressors @@ -144,12 +155,12 @@ class ZstdContext: @staticmethod def compress(data: bytes) -> bytes: - # ZstdCompressor is not thread safe. - # TODO: Use a pool? - - import zstandard + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd - return zstandard.ZstdCompressor().compress(data) + return zstd.compress(data) def decompress(data: bytes | memoryview, compressor_id: int) -> bytes: @@ -166,10 +177,11 @@ def decompress(data: bytes | memoryview, compressor_id: int) -> bytes: return zlib.decompress(data) elif compressor_id == ZstdContext.compressor_id: - # ZstdDecompressor is not thread safe. - # TODO: Use a pool? - import zstandard + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd - return zstandard.ZstdDecompressor().decompress(data) + return zstd.decompress(data) else: raise ValueError("Unknown compressorId %d" % (compressor_id,)) diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 6e716402f4..fea2d6daef 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -422,8 +422,8 @@ def __init__( with the server. Currently supported options are "snappy", "zlib" and "zstd". Support for snappy requires the `python-snappy `_ package. - zlib support requires the Python standard library zlib module. zstd - requires the `zstandard `_ + zlib support requires the Python standard library zlib module. For + Python before 3.14 zstd requires the `backports.zstd `_ package. By default no compression is used. Compression support must also be enabled on the server. MongoDB 3.6+ supports snappy and zlib compression. MongoDB 4.2+ adds support for zstd. diff --git a/requirements/zstd.txt b/requirements/zstd.txt index 864700d2b3..26c899e04c 100644 --- a/requirements/zstd.txt +++ b/requirements/zstd.txt @@ -1 +1 @@ -zstandard +backports.zstd>=1.0.0;python_version<'3.14' diff --git a/uv.lock b/uv.lock index f9a389c896..c021943b07 100644 --- a/uv.lock +++ b/uv.lock @@ -88,6 +88,116 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, ] +[[package]] +name = "backports-zstd" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/12/8080a1b7bce609eb250813519f550b36ad5950b64f0af2738c0fb53e7fb3/backports_zstd-1.0.0.tar.gz", hash = "sha256:8e99702fd4092c26624b914bcd140d03911a16445ba6a74435b29a190469cce3", size = 995991, upload-time = "2025-10-10T07:06:18.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/79/10389134d3a7e3099798ca55fc82abe9d7f49239c69c8d9c4979b091338c/backports_zstd-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34cd28f44f6b8f70ea5d86c2b3ba26d0d51f94606bd1f0c80bde1f820e8c82b2", size = 435682, upload-time = "2025-10-10T07:03:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6e/40d033accd0d54ee0b696f8f2ae0840bd4fae7255b3463ff46b210520e4f/backports_zstd-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7ccfd860da6b4c0c1f0f68dc917483977383e1207bdee262d40369fe616fc8", size = 362079, upload-time = "2025-10-10T07:04:00.784Z" }, + { url = "https://files.pythonhosted.org/packages/73/47/a1ed28ffd9b956aadbde6ad9a8d4adeab38b5cbfdd1d3f3d485a1bb18eba/backports_zstd-1.0.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:65de196c84b0d2c44fb76a729041de3a292f0128ded1b5ff7c1c4eab948aae0b", size = 505978, upload-time = "2025-10-10T07:04:02.903Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/6ea8a2567881ce0a46b7c8376c336f366e6e5dfe84766c45fed7473f0649/backports_zstd-1.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1096d4504557d2deb9e71e1aef9914c651b7aa05c35276d77854fee7a4bdd09a", size = 475592, upload-time = "2025-10-10T07:04:04.275Z" }, + { url = "https://files.pythonhosted.org/packages/62/27/5782d0bb36adbeec58687a2abf7e1a1659af30782129355456551486794f/backports_zstd-1.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a0db63d2a112c217a347789aecd38bdd17bc1030fa9d3d256d5d011231852f2", size = 581221, upload-time = "2025-10-10T07:04:05.759Z" }, + { url = "https://files.pythonhosted.org/packages/69/0b/accbbdbd24940b7a93d8064224b4eb304bd51d68a5e03fd2af3e2b5af268/backports_zstd-1.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0dfab6714325c29d621593b1e9198e47064bb755e601c3e79fde5211a16527f7", size = 640865, upload-time = "2025-10-10T07:04:07.236Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c4/7c5c5e47565e959a5dd6d2eae4e34e2ca46b6ba123ee1dd548c0c0d316f2/backports_zstd-1.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63076e558ba75b2c016257bdbe6b83b5b9a41fe5b6601b567f1e07890370f2f1", size = 491083, upload-time = "2025-10-10T07:04:09.04Z" }, + { url = "https://files.pythonhosted.org/packages/47/46/4ef914cfaf8a91fecd01e3d342fd506f450a06109c749a034e3a48ce97b2/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cd445dcb1ec8a8aa38d3c4e9cda7991cacead193bb07cf002f60cfc002d8628", size = 481539, upload-time = "2025-10-10T07:04:10.532Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f0/ae1dd6cf45d48b535fb6e5a77a107d6cc39db2ae8a9c060762d8fb6bcad2/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d01aa1b91007edd00abc78b3cb7e10ad9394b54a75cbd76b1584524a3b238cfc", size = 509486, upload-time = "2025-10-10T07:04:11.763Z" }, + { url = "https://files.pythonhosted.org/packages/91/35/a4829b1715e965baa00ef52529f513c8c30de83d3d2f662cbd016ad8861a/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d6bbb553640f7ed630223dae9ec93d6f56785d03db0c8385e0c4cfc88d54bdf4", size = 585584, upload-time = "2025-10-10T07:04:13.264Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/3d139188c2803b7e2944cc22ad7e2a974cc9773534c4dd736a136b671160/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d25adbe96f4a3fb6492d8d504e78c1641b06002ca2b578a0b24862ee9fd5a58", size = 631442, upload-time = "2025-10-10T07:04:14.655Z" }, + { url = "https://files.pythonhosted.org/packages/a5/72/442ada2be3fa510b0a93ca3f353c546c7626690029c3533dd348ea3c7730/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dde864a9c6aaa94eafe4962f59916de3165e99b3fd4d2f6584cd116707aed8ff", size = 495142, upload-time = "2025-10-10T07:04:16.144Z" }, + { url = "https://files.pythonhosted.org/packages/4c/80/e26f98015801a790262f9637feaebf2028edebd915e196cc1507f4ee7b6f/backports_zstd-1.0.0-cp310-cp310-win32.whl", hash = "sha256:474847d1ac3ed2e4bfca2207bbfd5632110143ddd4fc6ea41ff5c5b05d2fda1d", size = 288590, upload-time = "2025-10-10T07:04:17.755Z" }, + { url = "https://files.pythonhosted.org/packages/db/39/f322cf4d8b3194353a5bc01db6f2829835d1df273e93ebd1607f130213b5/backports_zstd-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d2fb3f4d53b2f92a26e7fc34b313ac5eebd7ff437f37f8f5c308d72c844fbd7", size = 313505, upload-time = "2025-10-10T07:04:18.895Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c0/e6ce5b66c48dfe29ec149eee01901be136071dd1692d6f99e14dbd7ba7d1/backports_zstd-1.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:525a1ecb62edd97c6695812fef0e4dc7c2faa5edf3057aa3d8ec9d2dbd0f7799", size = 288707, upload-time = "2025-10-10T07:04:19.968Z" }, + { url = "https://files.pythonhosted.org/packages/01/0a/cbf3f9cb7ca865eca93744d1b859ed50d28be3f64d83cfd96ad114ed88d6/backports_zstd-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:782923d65aa6d0c8c465c32563df70dbdd3e255532de2a2d26e13598fc5f85ae", size = 435683, upload-time = "2025-10-10T07:04:21.097Z" }, + { url = "https://files.pythonhosted.org/packages/c7/70/65f975ac0e1780963c5bcfae40e822724d7e4bfe902eeef3637a14fb56b1/backports_zstd-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6825598589ef9b8c0c4e574170d29d84400be24c2f172b81403435b33c8d103a", size = 362075, upload-time = "2025-10-10T07:04:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/0a/22/007acd1b0af3a78188c2b71fd4a3284f005826bd93e234e73412944d7b99/backports_zstd-1.0.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:15863463b7f28049d4511f9f123c3b96d66c3de543315c21ef3bc6b001b20d01", size = 505978, upload-time = "2025-10-10T07:04:23.504Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d6/f0a148d3f0d0558ace2fc0e7d4f0cc648e88c212665cbf8df718037adde9/backports_zstd-1.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b39da619431f4782f3d8bb0d99a6067db571eab50579527ba168bcc12887d328", size = 475589, upload-time = "2025-10-10T07:04:24.791Z" }, + { url = "https://files.pythonhosted.org/packages/49/b5/32fcb6342cfa9ca5692b0344961aafd082887e4fad89248f890927522bad/backports_zstd-1.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:411da73bb3eadef58da781c55c6399fc6dba9b898ca05009410138fb1d7fef8d", size = 581218, upload-time = "2025-10-10T07:04:26.493Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/757aa4952b8f3d955bb62b72360940639c781fc4f39249f5ea40e0b8125b/backports_zstd-1.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f8b0bc92f5be153a4878188ab0aeab5b9bbff3dc3e9d3ad3b19e29fe4932741", size = 640908, upload-time = "2025-10-10T07:04:27.837Z" }, + { url = "https://files.pythonhosted.org/packages/37/5f/075c31cbe58fffd8144bc482fea73d2833562159684430b3f1d402fa9f8d/backports_zstd-1.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cd5bdb76448f2259ea371d6cd62a7e339021e1429fe3c386acb3e58c1f6c61", size = 491121, upload-time = "2025-10-10T07:04:29.045Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a0/4c4b9a85ff52fe90a3265aa9b5cb7b35bf1a2d48bd1ed4604d7fe1aabfc7/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d251a49e80e1868e132e6edadfbef8dba7ded7751e59a41684cd6da38bbd3507", size = 481544, upload-time = "2025-10-10T07:04:30.174Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0e/1bd54a04e9f236f5a8d426c00ce0a6d5af6d68735138e9887d5545311761/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a54ea58ddeaab9a1385c368f84fca474b87b052087b62e56ac1ebd10cabac157", size = 509487, upload-time = "2025-10-10T07:04:31.386Z" }, + { url = "https://files.pythonhosted.org/packages/ef/eb/03a53be8a982e953acd8864d63ca1622ca309d9fbcf1f7ec5e2550b45057/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d6272730803dc5b212615f50af7395f2b05155d9415e367492d6dac807edc949", size = 585574, upload-time = "2025-10-10T07:04:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/17810915587c2686e767a5cd2de014e902c76e0a242daf1c4a97544ba1f5/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f6a27510ebb9e1cb877aaa26fc5e0303437bd2023e0a24976da854a3421e60e5", size = 631483, upload-time = "2025-10-10T07:04:34.107Z" }, + { url = "https://files.pythonhosted.org/packages/a4/22/d65a54a803061e475b66164c7d03d2ed889c32eaf32544c2e0d599c20628/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c55f842917ac4405a9779476b1ec8219247f35d86673769cf2d3c140799d3e4a", size = 495147, upload-time = "2025-10-10T07:04:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/bdf4e76e148cfac7c324b74f76fbda83c5a587b8a85871bad09722729283/backports_zstd-1.0.0-cp311-cp311-win32.whl", hash = "sha256:c28cfbd6217ba4837d35cdd8cfd5dcf84ad54bffcb531734002e27dcc84c87ca", size = 288686, upload-time = "2025-10-10T07:04:37.132Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/726a07d04b85a687776b04b53a02b7d2c4b666d51b18c44fa2ddaadfe383/backports_zstd-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f009996762b887d1bf9330ac0ce1e83608db0b881f63644ae30f2b6a290cd36b", size = 313630, upload-time = "2025-10-10T07:04:38.358Z" }, + { url = "https://files.pythonhosted.org/packages/52/e6/727584a8794fa28164e0795441d8b86f89c75a2368dec0aaaa086f7ac58c/backports_zstd-1.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:62ab49843fc7761383aa7bea8105ca70941797c7145647f71fa4340bfd3b747a", size = 288829, upload-time = "2025-10-10T07:04:39.602Z" }, + { url = "https://files.pythonhosted.org/packages/ba/22/2a68534673efe608d7b2d0de03595d5d1de629616a2f4e394813376eed21/backports_zstd-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f9afba11403cf03849464e0f1214b035970d43546a7cdd9d8ee31dc154889e78", size = 435990, upload-time = "2025-10-10T07:04:41.075Z" }, + { url = "https://files.pythonhosted.org/packages/3a/44/c3f06c172f128bf1160f6122df2a942440e36b8450cf4ba44c69465c5f55/backports_zstd-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86498856adc6e8c6f50cbfb1d4afd4e0997d5837fb225245d3fb26008f3c9412", size = 362142, upload-time = "2025-10-10T07:04:42.344Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0d/f46bba0f0df4dcd3d47160003b956b19329c25f63fe9e910aa17ca9fa0e5/backports_zstd-1.0.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:de915a8ecd290c601be7b7436291397b0ac1f7841c97c3a13777bb1065881773", size = 506399, upload-time = "2025-10-10T07:04:43.846Z" }, + { url = "https://files.pythonhosted.org/packages/92/a1/681e03e50379d72e06c3de796fb8cc5880fca8b70b82562b2eb712abf6d1/backports_zstd-1.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fffb08c5c1b629c96813108183c8b02d6b07ed6ec81cca8d094089e749db4b5", size = 476222, upload-time = "2025-10-10T07:04:44.975Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ca/8b0a8b959668668c50af6bfad6fea564d2b6becdcffd998e03dfc04c3954/backports_zstd-1.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:064d4dc840bcfd8c5c9b37dcacd4fb27eac473c75006120015a9f88b73368c9b", size = 581678, upload-time = "2025-10-10T07:04:46.459Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9a/921ec253ad5a592da20bf8ab1a5be16b242722f193e02d7a3678702aeffc/backports_zstd-1.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0051911391c3f934bb48e8ca08f4319d94b08362a40d96a4b5534c60f00deca2", size = 640408, upload-time = "2025-10-10T07:04:48.178Z" }, + { url = "https://files.pythonhosted.org/packages/ca/8c/0826259b7076cdaaceda1d52f2859c771dc45efed155084a49f538f0ea2e/backports_zstd-1.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e5f3453f0ea32ccf262e11e711ef1a0a986903b8a3a3078bf93fafdd5cf311c", size = 494195, upload-time = "2025-10-10T07:04:49.326Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a5/75b1c1e26e305f06a7cde591213d5b3c8591b06882ae635b8ffeb8df6f44/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33bce0351cd0ad7bd9f363740b894e65255eb93d16d05097ef1d60643ce1cc27", size = 482255, upload-time = "2025-10-10T07:04:50.722Z" }, + { url = "https://files.pythonhosted.org/packages/dc/24/7061610369a5dbadcddc6f340d5aa8304ae58aee07a6a851b8fa24638036/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:820f3cd08c5c8672015b7e52bf04e3e707727e6576d988eadc1799c0c47b33d9", size = 509829, upload-time = "2025-10-10T07:04:52.014Z" }, + { url = "https://files.pythonhosted.org/packages/e6/28/afc0158ba3d5d5a03560348f9a79fb8a1e0d0ef98f1d176ab37aa887ed5e/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4e327fe73bfc634e8b04b5e0f715c97680987d633f161fd4702027b34685be43", size = 586059, upload-time = "2025-10-10T07:04:53.255Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/68f1fa86a79faee7f6533bced500ee622dde98c9b3b0ddab58a4fe6410d5/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4055318ebb7f6ffad99dabd312706599c9e119c834d6c741a946c0d4b3e5be4e", size = 630869, upload-time = "2025-10-10T07:04:54.397Z" }, + { url = "https://files.pythonhosted.org/packages/83/e1/a529be674d179caf201e5e406dc70a2c4156e182fa777e43f43f6afa69c6/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79d3c879720ee4987782da55d728919f9294a8ea6fac76c9af84bc06f3b0f942", size = 498686, upload-time = "2025-10-10T07:04:55.593Z" }, + { url = "https://files.pythonhosted.org/packages/17/9a/075582e942841520c47535f9ff62b728a88565b737ae21dc99ebcc15ef61/backports_zstd-1.0.0-cp312-cp312-win32.whl", hash = "sha256:930ccc283fdf76d1acca9529acd6ccb6cd26cdaf684d69cc6f359683f90357be", size = 288822, upload-time = "2025-10-10T07:04:56.835Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/615cd31c0de23e330e13ba77a6aed9a1d27360ebdf5e68b078c54b8cdbdb/backports_zstd-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f1bb3e6d21ebfb22070288b7fb47bbb0baaae604890c4087edf5637debb6bd91", size = 313841, upload-time = "2025-10-10T07:04:58.003Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b8/87b2467bf82eabb4acd4651f193363ec04973baa35141be441bf9e9e98c0/backports_zstd-1.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:f5fe75e5996b5f4712235f9c63cdb7e5932a9cdf3a41232989f8a3ef1667f784", size = 288950, upload-time = "2025-10-10T07:04:59.279Z" }, + { url = "https://files.pythonhosted.org/packages/19/36/0182161a23009d5439e125d4af7b13d2df0292663e7f87141d5cf76d3060/backports_zstd-1.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c522469a67fef66998fd9eff2195512ca54d78c6fecdf1c466d2b7752dd810b", size = 435481, upload-time = "2025-10-10T07:05:00.833Z" }, + { url = "https://files.pythonhosted.org/packages/79/ce/6c235828d54d0027838316d9ce284b52e7bc266154f5e57086a7c7796691/backports_zstd-1.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c28546bcffb88ee38a742e3364338c49672d776ea2c73decc05fbf79f045797e", size = 361757, upload-time = "2025-10-10T07:05:02.422Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/7cbc80512df9b89ae39ab3920afbaad733d4b64390b4439e52ef3673da7b/backports_zstd-1.0.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:9719c14984ca99f5567a5974210d04c75aa02b0124653ee1b1d9a39bf0764fc6", size = 505673, upload-time = "2025-10-10T07:05:03.596Z" }, + { url = "https://files.pythonhosted.org/packages/f8/bc/ea32d4698fac21fe6cc08a124ae21daa41be03f788f244791c47e31a4360/backports_zstd-1.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a31220e8544c2194c4a7c3bd9f7fb0eee3c0ce5f8306e55df762428159ff0512", size = 475879, upload-time = "2025-10-10T07:05:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/bf/42/68344db3586455983bdcdffe51253fa4415908e700d50287249ad6589bc9/backports_zstd-1.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3571e35d6682119daf109678a68fa8a9e29f79487ee7ec2da63a7e97562acb8c", size = 581359, upload-time = "2025-10-10T07:05:05.977Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d0/3d153d78a52a46ce4c363680da7fbc593eeb314150f005c4bf7c2bd5b51f/backports_zstd-1.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:26ccb82bbeb36fffeb3865abe7df9b9b82d6462a488cd2f3c10e91c41c3103cc", size = 642203, upload-time = "2025-10-10T07:05:07.236Z" }, + { url = "https://files.pythonhosted.org/packages/11/c3/e31b4e591daec3eab2446db971f275d349aad36041236d5f067ab20fa1a9/backports_zstd-1.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d90cfb475d6d08c596ae77a7009cdda7374ecd79354fd75185cf029bf2204620", size = 490828, upload-time = "2025-10-10T07:05:08.446Z" }, + { url = "https://files.pythonhosted.org/packages/2d/80/ef7d02d846f710fc95c6d7eb3298ef6504e51f8707f24e1624d139f791d5/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0b00c9c22cae8c1f87e2f23f9aeda7fee82ff671672b9f5a161a7ba094d9904b", size = 481638, upload-time = "2025-10-10T07:05:10.18Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9b/f32500bf26ef588ce4f6284f453532d08789e412a5ecd60c501c77c88f8f/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a87c4491036954ae6d15edaf1e6d5b1941e13a9df14d6a9952899713fcfb0796", size = 509228, upload-time = "2025-10-10T07:05:11.313Z" }, + { url = "https://files.pythonhosted.org/packages/6d/67/f689055f90a2874578b2b3e7c84311c3007b2fa60c51454e8c432203f1c7/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8ea8c5d283211bc21c9782db7a8504a275a5b97e883b0bf67f6903a3af48f3d3", size = 585789, upload-time = "2025-10-10T07:05:12.477Z" }, + { url = "https://files.pythonhosted.org/packages/86/53/dea52bd76a3ba519a4937e6cab6cbdcdc36b618090eabeac998f69d1bb97/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e1d12f64d1bd535c782f30b33d1f60c060105d124f9ade22556fefbf36087776", size = 632571, upload-time = "2025-10-10T07:05:14.18Z" }, + { url = "https://files.pythonhosted.org/packages/43/c8/ce10a94132957f57860b9440fe726615a6a6e8c5fdfee565d8a1b3a573de/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df08eb2735363a11a9222203c3e9a478d7569511bdd9aa2cc64a39e0403cf09a", size = 495124, upload-time = "2025-10-10T07:05:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c0/830ea473e3c6133758a9a421157c8d4d5c65408d565336a59403e6bb0b29/backports_zstd-1.0.0-cp313-cp313-win32.whl", hash = "sha256:0309f924ec026d2174297754aeb97fe5fa665cfe0f8bc70e7bb82808a7adcd08", size = 288467, upload-time = "2025-10-10T07:05:16.563Z" }, + { url = "https://files.pythonhosted.org/packages/75/5a/318d40e1589908a44532e2c850fedfaedbf4e7c75b6fa3cf4b532fcadc84/backports_zstd-1.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:d68f7f72579070bfef7890ba5316701c001e90b4455bb5c2591558b9d53a7f6e", size = 313680, upload-time = "2025-10-10T07:05:17.71Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c8/bb0067165e9b1066104a88536eac04cfac388abb5d500b3405cf783c96e8/backports_zstd-1.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:911a099122ce7cebed9e1ec64c1fa54a6ab461d6c7cec8d460d8b3a09bbd439f", size = 288699, upload-time = "2025-10-10T07:05:18.904Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0e/83badde9b389c198a9a45bccd38a9dc5baa7db92e531d4951b1c0686e29a/backports_zstd-1.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fad9af0c89048a50e67bfd9e3509d710b268d4ae0e47a2bc945dca273a17286d", size = 436173, upload-time = "2025-10-10T07:05:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/92/d1f5e9f7e1afbb730020e8c7060d6101cad4aa20eb13b7cb98dda9414726/backports_zstd-1.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1d0459de16491399e6b6d151213964395ba092ba21b7739756f0507533c8e44f", size = 362456, upload-time = "2025-10-10T07:05:21.367Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0c/165b04a4bd9b39455e5d051f504acab6c5af3583939336bd2c77a2dc6398/backports_zstd-1.0.0-cp313-cp313t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:2dcf4c080c0fe8f4ca8f1ff560756ae78e6fada721813c1506f8fd3399996646", size = 507618, upload-time = "2025-10-10T07:05:23.083Z" }, + { url = "https://files.pythonhosted.org/packages/72/45/868e6b66852b64766feb3a3ce28cc74dd86141120ac6740855f90239fb85/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e26b558e0f2413e9499949dd75985a03be008287598916eaa75a67efc52e4f1b", size = 475518, upload-time = "2025-10-10T07:05:24.297Z" }, + { url = "https://files.pythonhosted.org/packages/44/ff/71021dae5e024d7e12b5078719582b26eeae984f5718846c135134288330/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f0a0c11aee04e0a10e9688ef8d9014af888763507bea85a0d7a7ba5220272996", size = 580942, upload-time = "2025-10-10T07:05:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/7c/64/553009a1d449033fafba311d2e204b19ebb0dfdba069a639965fb6f0bc57/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8aa92bf9407ed1ba62234e085876b628ecd9d2636c0e1e23f2dacf3be21af2a", size = 639934, upload-time = "2025-10-10T07:05:27.147Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/490a0b80144fb888ae9328f73d7bfa58fd5ccf8bdb81a6d20561ec5a0ff7/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c78c1eaf3fdea00514afe9636e01f94890f1e4c6e8e1dfede48015364b950705", size = 494822, upload-time = "2025-10-10T07:05:28.325Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/0f7702000bd08ff6aa71114b377141f2d30154597dcd9459a08554122fa5/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4ff181018de5afb1b87edf9a88ec7e62b4b053e75b91ec8ac7819042126ca7cf", size = 482001, upload-time = "2025-10-10T07:05:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/20/78/2cc5dc095b93841eb251d91cf4b3b4c1e5efc15db40f97f003603acaba3f/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eed0753c698a21f0d38464c2a6d4d5e770d2ea2e9c3a308f1712d674598a049f", size = 511380, upload-time = "2025-10-10T07:05:30.874Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b3/328c4835b661b3a9f2c6f2eb6350a9d4bc673e7e5c7d1149ecb235abe774/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:18f5d3ed08afcd08b86b305bf167c0f2b582b906742e4bd3c7389050d5b59817", size = 585514, upload-time = "2025-10-10T07:05:32.523Z" }, + { url = "https://files.pythonhosted.org/packages/4f/31/3d347703f5d913d35edb58e9fbfbf8155dc63d1e6c0ed93eb5205e09d5f1/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:7a8c950abe629e5d8ea606e6600dd1d6cd6bddd7a4566cf34201d31244d10ab3", size = 630541, upload-time = "2025-10-10T07:05:33.799Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ac/323abb5ba0e5da924dec83073464eb87223677c577e0969c90b279700c1f/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:973e74f4e1f19f7879a6a7900e9a268522eb4297100a573ed69969df63f94674", size = 499450, upload-time = "2025-10-10T07:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/81/cb/1d77d6cf3850e804f4994a8106db2830e58638ed0f2d0f92636adb38a38d/backports_zstd-1.0.0-cp313-cp313t-win32.whl", hash = "sha256:870effb06ffb7623af1c8dac35647a1c4b597d3bb0b3f9895c738bd5ad23666c", size = 289410, upload-time = "2025-10-10T07:05:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/16/59/5ec914419b6db0516794f6f5214b1990e550971fe0867c60ea55262b5d68/backports_zstd-1.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8bb6470186301e84aaa704c8eb339c97dcdec67445e7e197d44665e933807e4e", size = 314778, upload-time = "2025-10-10T07:05:38.637Z" }, + { url = "https://files.pythonhosted.org/packages/75/88/198e1726f65229f219bb2a72849c9424ba41f6de989c3a8c9bf58118a4a7/backports_zstd-1.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b2d85810393b3be6e8e77d89a165fc67c2a08290a210dbd77e2fc148dbc4106f", size = 289333, upload-time = "2025-10-10T07:05:39.758Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/cad971088dd705adedce95e4ce77801cbad61ac9250b4e77fbbb2881c34f/backports_zstd-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1315107754808856ddcf187a19cc139cb4a2a65970bd1bafd71718cfd051d32e", size = 435835, upload-time = "2025-10-10T07:05:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9f/8c13830b7d698bd270d9aaeebd685670e8955282a3e5f6967521bcb5b2d3/backports_zstd-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96bf0a564af74951adfa6addd1c148ab467ba92172cd23b267dd150b0f47fd9e", size = 362191, upload-time = "2025-10-10T07:05:42.594Z" }, + { url = "https://files.pythonhosted.org/packages/db/b4/dd0d86d04b1dd4d08468e8d980d3ece48d86909b9635f1efebce309b98d4/backports_zstd-1.0.0-cp39-cp39-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:d7c1c6ebedf7bc70c1adca3f4624e1e04b2a0d7a389b065f0c5d6244f6be3dae", size = 506076, upload-time = "2025-10-10T07:05:43.842Z" }, + { url = "https://files.pythonhosted.org/packages/86/6e/b484e33d8eb13b9379741e9e88daa48c15c9038e9ee9926ebf1096bfed6f/backports_zstd-1.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ea4ff5e162fb61f8421724021eac0a612af0aff2da9e585c96d27c2da924589", size = 475720, upload-time = "2025-10-10T07:05:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e6/c49157bb8240ffd4c0abf93306276be4e80d2ef8c1b8465e06bcecece250/backports_zstd-1.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5a6047fb0bef5bbe519b1e46108847e01a48d002b3dfc69af1423a53d8144dda", size = 581396, upload-time = "2025-10-10T07:05:46.389Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/a900cfdc4dd74306c6b53604ad51af5f38e2353b0d615a3c869051134b3b/backports_zstd-1.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2d510b422e7b2b6ca142082fa85ac360edf77b73108454335ecfd19071c819ff", size = 641053, upload-time = "2025-10-10T07:05:48.012Z" }, + { url = "https://files.pythonhosted.org/packages/3d/75/5ce7953c6306fc976abf7cf33f0071a10d58c71c94348844ae625dfdee22/backports_zstd-1.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e6349defa266342802d86343b7fc59ee12048bca5f77a9fcb1c1ab9bb894d09", size = 491186, upload-time = "2025-10-10T07:05:49.424Z" }, + { url = "https://files.pythonhosted.org/packages/f9/db/375410a26abf2ac972fec554122065d774fa037f9ffeedf4f7b05553b01d/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20b0a1be02b2ee18c74b68a89eec14be98d11f0415a79eb209dce4bc2d6f4e52", size = 481750, upload-time = "2025-10-10T07:05:50.678Z" }, + { url = "https://files.pythonhosted.org/packages/21/d1/fa7c2d7b7a1c433e4e79c027c54d17f2ffc489ab7e76496b149d9ae6f667/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3595cbc2f4d8a5dc6bd791ba8d9fee2fdfcdfc07206e944c1b3ec3090fcbc99e", size = 509601, upload-time = "2025-10-10T07:05:51.952Z" }, + { url = "https://files.pythonhosted.org/packages/c4/35/befe5ee9bec078f7f4c9290cefc56d3336b4ee52d17a60293d9dda4589c0/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d3eddb298db7a9a1b122c40bcb418a154b6c8f1b54ef7308644e0e67d42c159e", size = 585743, upload-time = "2025-10-10T07:05:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0a/cfbf0ae24348be3c3f597717c639e9cbe29692a99ad650c232b8a97c74c1/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef31a9482727e6b335f673a8b8116be186b83ca72be4a07f60684b8220a213e9", size = 631591, upload-time = "2025-10-10T07:05:54.846Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2d/7c996648c7a7b84a3e8b045fb494466475c1f599374da3c780198bde96c4/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0a6a6d114058735d042116aa9199b0b436236fddcb5f805fb17310fcadddd441", size = 495294, upload-time = "2025-10-10T07:05:56.417Z" }, + { url = "https://files.pythonhosted.org/packages/be/c8/5a15a4a52506e2e2598d2667ae67404516ea4336535fdd7b7b1b2fffd623/backports_zstd-1.0.0-cp39-cp39-win32.whl", hash = "sha256:8aea1bdc89becb21d1df1cdcc6182b2aa9540addaa20569169e01b25b8996f41", size = 288646, upload-time = "2025-10-10T07:05:57.993Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/42409d11a9d324f68a079493c5806d593f54184962e5fff1dc88a1d5e3ba/backports_zstd-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:23a40a40fb56f4b47ece5e9cb7048c2e93d9eeb81ad5fb4e68adcaeb699d6b98", size = 313532, upload-time = "2025-10-10T07:05:59.212Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f8/932b05fd2f98f85c95674f09ae28ccc1638b8cc17d6f566d21ed499ee456/backports_zstd-1.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:2f07bd1c1b478bd8a0bbe413439c24ee08ceb6ebc957a97de3666e8f2e612463", size = 288756, upload-time = "2025-10-10T07:06:01.216Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/680ac0ad73676eb1f3bb71f6dd3bbaa2d28a9e4293d3ede4adcd78905b93/backports_zstd-1.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:efa53658c1e617986ed202e7aa8eb23c69fc8f33d01192cd1565e455ed9aa057", size = 409790, upload-time = "2025-10-10T07:06:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/62/6c/6410c334890b4a43c893b9dcd3cbc8b10f17ea8dced483d9ba200b17ccab/backports_zstd-1.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4386a17c99ce647877298c916f2afeacb238e56cb7cca2d665822a0ee743b5d5", size = 339308, upload-time = "2025-10-10T07:06:03.667Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b2/ad3e651985b8a2a4876e5adc61100cef07a8caefb87180391f1f5b8c801c/backports_zstd-1.0.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:cbbb0bda54bda18af99961d7d22d7bc7fedcc7d8ca3a04dcde9189494dbfc87a", size = 420356, upload-time = "2025-10-10T07:06:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/361bde3f570804674b9033ac41cc26735ceb4e33ccce2645079eff62a26f/backports_zstd-1.0.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c49048ec50f81b196ab0f3c49c025912eba1c6e55259b99f11ee6e8c04226ab", size = 393900, upload-time = "2025-10-10T07:06:06.252Z" }, + { url = "https://files.pythonhosted.org/packages/7b/90/f7bc5c0d204c2312fbe4e62592c92200f19da8840ce8b4a1df56080b7537/backports_zstd-1.0.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6d1fe816c3c31241b0bdcc96364fae689f3e49a923469ad1ad7a9aeb0bbcd67", size = 413862, upload-time = "2025-10-10T07:06:07.506Z" }, + { url = "https://files.pythonhosted.org/packages/77/2b/9c1949456566228578d30013e81a593577e63e1cae9e72b058e37ae4c5e2/backports_zstd-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:96a57f48d2d64c985393bb4ae15b61097d8fc0d56416e790f7cc09bf9212fb87", size = 299722, upload-time = "2025-10-10T07:06:08.661Z" }, + { url = "https://files.pythonhosted.org/packages/a0/51/f22627d208ab63e97f5441374110363f4b5e0c2ce0b4f2412e753eb12bf1/backports_zstd-1.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8191c019cefaf074c3f05ebec5ad19ec606b7ac1dc915b66a0820268e6f0e327", size = 409687, upload-time = "2025-10-10T07:06:09.844Z" }, + { url = "https://files.pythonhosted.org/packages/4f/93/50b2ebb2e8f388bb124c4a39974e29f841ef1452d603045e292e107227b9/backports_zstd-1.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:479270cd6385775dca98accaf304e5f011d94280ad4681d3e925a1b4dfd19aaf", size = 339221, upload-time = "2025-10-10T07:06:11.13Z" }, + { url = "https://files.pythonhosted.org/packages/25/f5/103645f44a92c4de2860b8d6cf6c5414b63956278764f8b7db359bdeae94/backports_zstd-1.0.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:3beab43bfda8e453648b9cce5edcceb5add6c42c331873b41ab1d24232d9c2b0", size = 420355, upload-time = "2025-10-10T07:06:12.283Z" }, + { url = "https://files.pythonhosted.org/packages/d9/10/e185f05ec85bc05c82d7efdd75528e695c85181eb291cc4c19b2f26153f1/backports_zstd-1.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67469b247c99537b77f7d402580cbb7298fa15ebe3ce6984d89a5b65d4d5a6c2", size = 393900, upload-time = "2025-10-10T07:06:13.508Z" }, + { url = "https://files.pythonhosted.org/packages/fd/40/3f717216e21617e919d12d6520d0da5b22002e07f12638629acc9e5dcc2e/backports_zstd-1.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6910a9311e7a2987d353f396568f5e401cf4917e2112bf610e62385ad02d8cf4", size = 413863, upload-time = "2025-10-10T07:06:15.531Z" }, + { url = "https://files.pythonhosted.org/packages/23/f5/cb12f5dd6ac648e92d8cec8b69fd4064bd549c126fb0d3fe6d3dd237afbe/backports_zstd-1.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:85f08363b7ca504a8bceaa2d4333a1a307d2b2056f77a13036a81d7aa3c87b2a", size = 299719, upload-time = "2025-10-10T07:06:17.032Z" }, +] + [[package]] name = "beautifulsoup4" version = "4.13.4" @@ -1131,7 +1241,7 @@ test = [ { name = "pytest-asyncio" }, ] zstd = [ - { name = "zstandard" }, + { name = "backports-zstd", marker = "python_full_version < '3.14'" }, ] [package.dev-dependencies] @@ -1160,6 +1270,7 @@ typing = [ [package.metadata] requires-dist = [ + { name = "backports-zstd", marker = "python_full_version < '3.14' and extra == 'zstd'", specifier = ">=1.0.0" }, { name = "certifi", marker = "(os_name == 'nt' and extra == 'encryption') or (sys_platform == 'darwin' and extra == 'encryption')", specifier = ">=2023.7.22" }, { name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')", specifier = ">=2023.7.22" }, { name = "cryptography", marker = "extra == 'ocsp'", specifier = ">=2.5" }, @@ -1182,7 +1293,6 @@ requires-dist = [ { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = ">=2,<4" }, { name = "sphinxcontrib-shellcheck", marker = "extra == 'docs'", specifier = ">=1,<2" }, { name = "winkerberos", marker = "os_name == 'nt' and extra == 'gssapi'", specifier = ">=0.5.0" }, - { name = "zstandard", marker = "extra == 'zstd'" }, ] provides-extras = ["aws", "docs", "encryption", "gssapi", "ocsp", "snappy", "test", "zstd"] @@ -2115,94 +2225,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/d4/4ba1569b856870527cec4bf22b91fe704b81a3c1a451b2ccf234e9e0666f/zope.interface-7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad9913fd858274db8dd867012ebe544ef18d218f6f7d1e3c3e6d98000f14b75", size = 253800, upload-time = "2024-11-28T08:48:46.637Z" }, { url = "https://files.pythonhosted.org/packages/69/da/c9cfb384c18bd3a26d9fc6a9b5f32ccea49ae09444f097eaa5ca9814aff9/zope.interface-7.2-cp39-cp39-win_amd64.whl", hash = "sha256:1090c60116b3da3bfdd0c03406e2f14a1ff53e5771aebe33fec1edc0a350175d", size = 211980, upload-time = "2024-11-28T08:50:35.681Z" }, ] - -[[package]] -name = "zstandard" -version = "0.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/55/bd0487e86679db1823fc9ee0d8c9c78ae2413d34c0b461193b5f4c31d22f/zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9", size = 788701, upload-time = "2024-07-15T00:13:27.351Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8a/ccb516b684f3ad987dfee27570d635822e3038645b1a950c5e8022df1145/zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880", size = 633678, upload-time = "2024-07-15T00:13:30.24Z" }, - { url = "https://files.pythonhosted.org/packages/12/89/75e633d0611c028e0d9af6df199423bf43f54bea5007e6718ab7132e234c/zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc", size = 4941098, upload-time = "2024-07-15T00:13:32.526Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7a/bd7f6a21802de358b63f1ee636ab823711c25ce043a3e9f043b4fcb5ba32/zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573", size = 5308798, upload-time = "2024-07-15T00:13:34.925Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/775f851a4a65013e88ca559c8ae42ac1352db6fcd96b028d0df4d7d1d7b4/zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391", size = 5341840, upload-time = "2024-07-15T00:13:37.376Z" }, - { url = "https://files.pythonhosted.org/packages/09/4f/0cc49570141dd72d4d95dd6fcf09328d1b702c47a6ec12fbed3b8aed18a5/zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e", size = 5440337, upload-time = "2024-07-15T00:13:39.772Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7c/aaa7cd27148bae2dc095191529c0570d16058c54c4597a7d118de4b21676/zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd", size = 4861182, upload-time = "2024-07-15T00:13:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/ac/eb/4b58b5c071d177f7dc027129d20bd2a44161faca6592a67f8fcb0b88b3ae/zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4", size = 4932936, upload-time = "2024-07-15T00:13:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/44/f9/21a5fb9bb7c9a274b05ad700a82ad22ce82f7ef0f485980a1e98ed6e8c5f/zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea", size = 5464705, upload-time = "2024-07-15T00:13:46.822Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/b7b3e61db3f88632776b78b1db597af3f44c91ce17d533e14a25ce6a2816/zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2", size = 4857882, upload-time = "2024-07-15T00:13:49.297Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7f/d8eb1cb123d8e4c541d4465167080bec88481ab54cd0b31eb4013ba04b95/zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9", size = 4697672, upload-time = "2024-07-15T00:13:51.447Z" }, - { url = "https://files.pythonhosted.org/packages/5e/05/f7dccdf3d121309b60342da454d3e706453a31073e2c4dac8e1581861e44/zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a", size = 5206043, upload-time = "2024-07-15T00:13:53.587Z" }, - { url = "https://files.pythonhosted.org/packages/86/9d/3677a02e172dccd8dd3a941307621c0cbd7691d77cb435ac3c75ab6a3105/zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0", size = 5667390, upload-time = "2024-07-15T00:13:56.137Z" }, - { url = "https://files.pythonhosted.org/packages/41/7e/0012a02458e74a7ba122cd9cafe491facc602c9a17f590367da369929498/zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c", size = 5198901, upload-time = "2024-07-15T00:13:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/65/3a/8f715b97bd7bcfc7342d8adcd99a026cb2fb550e44866a3b6c348e1b0f02/zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813", size = 430596, upload-time = "2024-07-15T00:14:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/19/b7/b2b9eca5e5a01111e4fe8a8ffb56bdcdf56b12448a24effe6cfe4a252034/zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4", size = 495498, upload-time = "2024-07-15T00:14:02.741Z" }, - { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, - { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, - { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, - { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, - { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, - { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, - { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, - { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, - { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, - { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, - { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, - { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, - { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, - { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, - { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, - { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, - { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, - { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, - { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975, upload-time = "2024-07-15T00:16:16.005Z" }, - { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448, upload-time = "2024-07-15T00:16:17.897Z" }, - { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269, upload-time = "2024-07-15T00:16:20.136Z" }, - { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228, upload-time = "2024-07-15T00:16:23.398Z" }, - { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891, upload-time = "2024-07-15T00:16:26.391Z" }, - { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310, upload-time = "2024-07-15T00:16:29.018Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912, upload-time = "2024-07-15T00:16:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946, upload-time = "2024-07-15T00:16:34.593Z" }, - { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994, upload-time = "2024-07-15T00:16:36.887Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681, upload-time = "2024-07-15T00:16:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239, upload-time = "2024-07-15T00:16:41.83Z" }, - { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149, upload-time = "2024-07-15T00:16:44.287Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392, upload-time = "2024-07-15T00:16:46.423Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299, upload-time = "2024-07-15T00:16:49.053Z" }, - { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862, upload-time = "2024-07-15T00:16:51.003Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578, upload-time = "2024-07-15T00:16:53.135Z" }, - { url = "https://files.pythonhosted.org/packages/fb/96/4fcafeb7e013a2386d22f974b5b97a0b9a65004ed58c87ae001599bfbd48/zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb", size = 788697, upload-time = "2024-07-15T00:17:31.236Z" }, - { url = "https://files.pythonhosted.org/packages/83/ff/a52ce725be69b86a2967ecba0497a8184540cc284c0991125515449e54e2/zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916", size = 633679, upload-time = "2024-07-15T00:17:32.911Z" }, - { url = "https://files.pythonhosted.org/packages/34/0f/3dc62db122f6a9c481c335fff6fc9f4e88d8f6e2d47321ee3937328addb4/zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a", size = 4940416, upload-time = "2024-07-15T00:17:34.849Z" }, - { url = "https://files.pythonhosted.org/packages/1d/e5/9fe0dd8c85fdc2f635e6660d07872a5dc4b366db566630161e39f9f804e1/zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259", size = 5307693, upload-time = "2024-07-15T00:17:37.355Z" }, - { url = "https://files.pythonhosted.org/packages/73/bf/fe62c0cd865c171ee8ed5bc83174b5382a2cb729c8d6162edfb99a83158b/zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4", size = 5341236, upload-time = "2024-07-15T00:17:40.213Z" }, - { url = "https://files.pythonhosted.org/packages/39/86/4fe79b30c794286110802a6cd44a73b6a314ac8196b9338c0fbd78c2407d/zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58", size = 5439101, upload-time = "2024-07-15T00:17:42.284Z" }, - { url = "https://files.pythonhosted.org/packages/72/ed/cacec235c581ebf8c608c7fb3d4b6b70d1b490d0e5128ea6996f809ecaef/zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15", size = 4860320, upload-time = "2024-07-15T00:17:44.21Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1e/2c589a2930f93946b132fc852c574a19d5edc23fad2b9e566f431050c7ec/zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269", size = 4931933, upload-time = "2024-07-15T00:17:46.455Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f5/30eadde3686d902b5d4692bb5f286977cbc4adc082145eb3f49d834b2eae/zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700", size = 5463878, upload-time = "2024-07-15T00:17:48.866Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c8/8aed1f0ab9854ef48e5ad4431367fcb23ce73f0304f7b72335a8edc66556/zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9", size = 4857192, upload-time = "2024-07-15T00:17:51.558Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c6/55e666cfbcd032b9e271865e8578fec56e5594d4faeac379d371526514f5/zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69", size = 4696513, upload-time = "2024-07-15T00:17:53.924Z" }, - { url = "https://files.pythonhosted.org/packages/dc/bd/720b65bea63ec9de0ac7414c33b9baf271c8de8996e5ff324dc93fc90ff1/zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70", size = 5204823, upload-time = "2024-07-15T00:17:55.948Z" }, - { url = "https://files.pythonhosted.org/packages/d8/40/d678db1556e3941d330cd4e95623a63ef235b18547da98fa184cbc028ecf/zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2", size = 5666490, upload-time = "2024-07-15T00:17:58.327Z" }, - { url = "https://files.pythonhosted.org/packages/ed/cc/c89329723d7515898a1fc7ef5d251264078548c505719d13e9511800a103/zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5", size = 5196622, upload-time = "2024-07-15T00:18:00.404Z" }, - { url = "https://files.pythonhosted.org/packages/78/4c/634289d41e094327a94500dfc919e58841b10ea3a9efdfafbac614797ec2/zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274", size = 430620, upload-time = "2024-07-15T00:18:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/a2/e2/0b0c5a0f4f7699fecd92c1ba6278ef9b01f2b0b0dd46f62bfc6729c05659/zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58", size = 495528, upload-time = "2024-07-15T00:18:04.452Z" }, -] From eb25ce420e04aac8b939cc44a52ab11a268cf0fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:20:45 -0600 Subject: [PATCH 009/129] Bump the actions group across 1 directory with 4 updates (#2604) --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/dist.yml | 8 ++++---- .github/workflows/release-python.yml | 2 +- .github/workflows/test-python.yml | 20 ++++++++++---------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b138324bf4..5820c86b97 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -63,6 +63,6 @@ jobs: pip install -e . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 84bf1ba893..b4d4a2e78b 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -91,7 +91,7 @@ jobs: # Free-threading builds: ls wheelhouse/*cp314t*.whl - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: wheel-${{ matrix.buildplat[1] }} path: ./wheelhouse/*.whl @@ -124,7 +124,7 @@ jobs: cd .. python -c "from pymongo import has_c; assert has_c()" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: "sdist" path: ./dist/*.tar.gz @@ -135,13 +135,13 @@ jobs: name: Download Wheels steps: - name: Download all workflow run artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 - name: Flatten directory working-directory: . run: | find . -mindepth 2 -type f -exec mv {} . \; find . -type d -empty -delete - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: all-dist-${{ github.run_id }} path: "./*" diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 6abca9e528..43e500337e 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -75,7 +75,7 @@ jobs: id-token: write steps: - name: Download all the dists - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: all-dist-${{ github.run_id }} path: dist/ diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 4685ba2d92..20323dd925 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -87,7 +87,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: "3.10" @@ -112,7 +112,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: "3.10" @@ -131,7 +131,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: "3.10" @@ -153,7 +153,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -174,7 +174,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: enable-cache: true python-version: "3.10" @@ -214,7 +214,7 @@ jobs: run: | pip install build python -m build --sdist - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: "sdist" path: dist/*.tar.gz @@ -226,7 +226,7 @@ jobs: timeout-minutes: 20 steps: - name: Download sdist - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: sdist/ - name: Unpack SDist @@ -264,7 +264,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 with: python-version: "3.10" - id: setup-mongodb From 63acab96cfe5da378cb05cf07b8fda72d1827cc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:23:32 -0600 Subject: [PATCH 010/129] Bump the actions group with 2 updates (#2608) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dist.yml | 2 +- .github/workflows/zizmor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index b4d4a2e78b..7530e73e41 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -61,7 +61,7 @@ jobs: - name: Set up QEMU if: runner.os == 'Linux' - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: # setup-qemu-action by default uses `tonistiigi/binfmt:latest` image, # which is out of date. This causes seg faults during build. diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index c991de2e6d..6d78564f83 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@da5ac40c5419dcf7f21630fb2f95e725ae8fb9d5 + uses: zizmorcore/zizmor-action@1aba86d8e1245be7a9ca003d46fcc85a76e6aa61 From 44a58f1650c5c4d32056f72867037cf870938c45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:22:00 -0600 Subject: [PATCH 011/129] Bump pyright from 1.1.406 to 1.1.407 (#2603) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jib Co-authored-by: Steven Silvester --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ef7140eddf..a76189c57e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ mockupdb = [ perf = ["simplejson>=3.17.0"] typing = [ "mypy==1.18.2", - "pyright==1.1.406", + "pyright==1.1.407", "typing_extensions", "pip" ] diff --git a/uv.lock b/uv.lock index c021943b07..d31990880c 100644 --- a/uv.lock +++ b/uv.lock @@ -1309,7 +1309,7 @@ pip = [{ name = "pip" }] typing = [ { name = "mypy", specifier = "==1.18.2" }, { name = "pip" }, - { name = "pyright", specifier = "==1.1.406" }, + { name = "pyright", specifier = "==1.1.407" }, { name = "typing-extensions" }, ] @@ -1359,15 +1359,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.406" +version = "1.1.407" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, ] [[package]] From 71e0c950e14854e35d280d64950f132a497ffaca Mon Sep 17 00:00:00 2001 From: thanhnguyen-mdb Date: Thu, 20 Nov 2025 15:02:46 -0600 Subject: [PATCH 012/129] PYTHON-5433 - Added SBOM update automation (#2617) --- .github/workflows/sbom.yml | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/sbom.yml diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 0000000000..fcf39902da --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,88 @@ +name: Generate SBOM + +# This workflow uses cdxgen and publishes an sbom.json artifact. +# It runs on manual trigger or when package files change on main branch, +# and creates a PR with the updated SBOM. +# Internal documentation: go/sbom-scope + +on: + workflow_dispatch: {} + push: + branches: ['master'] + paths: + - 'pyproject.toml' + - 'requirements.txt' + +permissions: + contents: write + pull-requests: write + +jobs: + sbom: + name: Generate SBOM and Create PR + runs-on: ubuntu-latest + concurrency: + group: sbom-${{ github.ref }} + cancel-in-progress: false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Generate SBOM + run: | + python -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + pip install . + npx @cyclonedx/cdxgen -t python --exclude "uv.lock" --exclude "requirements/**" --exclude "requirements.txt" --spec-version 1.5 --no-validate --json-pretty -o sbom.json + env: + FETCH_LICENSE: true + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.json + if-no-files-found: error + + - name: Create Pull Request + uses: peter-evans/create-pull-request@b4733b9419fd47bbfa1807b15627e17cd70b5b22 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: Update SBOM after dependency changes' + branch: auto-update-sbom-${{ github.run_id }} + delete-branch: true + title: 'chore: Update SBOM' + body: | + ## Automated SBOM Update + + This PR was automatically generated because dependency manifest files changed. + + ### Changes + - Updated `sbom.json` to reflect current dependencies + + ### Verification + The SBOM was generated using cdxgen with the current Python environment. + + ### Triggered by + - Commit: ${{ github.sha }} + - Workflow run: ${{ github.run_id }} + + --- + _This PR was created automatically by the [SBOM workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})_ + labels: | + sbom + automated + dependencies + + - name: Cleanup + if: always() + run: rm -rf .venv From 47da699a8737cd63f9b51a3549c7bcf633c99838 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:41:46 -0600 Subject: [PATCH 013/129] chore: Update SBOM (#2619) Co-authored-by: blink1073 <2096628+blink1073@users.noreply.github.com> --- sbom.json | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 10 deletions(-) diff --git a/sbom.json b/sbom.json index 56e27f5361..d2e02eeb71 100644 --- a/sbom.json +++ b/sbom.json @@ -1,11 +1,159 @@ { - "metadata": { - "timestamp": "2024-05-02T17:36:12.698229+00:00" - }, - "components": [], - "serialNumber": "urn:uuid:9876a8a6-060e-486f-b128-910aecf0fe7b", - "version": 1, - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", - "bomFormat": "CycloneDX", - "specVersion": "1.5" - } \ No newline at end of file + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:f91a87bf-a37f-4c1e-805f-142f60b2c960", + "version": 1, + "metadata": { + "timestamp": "2025-11-20T21:30:34Z", + "tools": { + "components": [ + { + "group": "@cyclonedx", + "name": "cdxgen", + "version": "11.11.0", + "purl": "pkg:npm/%40cyclonedx/cdxgen@11.11.0", + "type": "application", + "bom-ref": "pkg:npm/@cyclonedx/cdxgen@11.11.0", + "author": "OWASP Foundation", + "publisher": "OWASP Foundation" + } + ] + }, + "authors": [ + { + "name": "OWASP Foundation" + } + ], + "lifecycles": [ + { + "phase": "build" + } + ], + "component": { + "name": "pymongo", + "description": "PyMongo - the Official MongoDB Python driver", + "authors": [ + { + "name": "The MongoDB Python Team" + } + ], + "tags": [ + "bson", + "gridfs", + "mongo", + "mongodb", + "pymongo" + ], + "properties": [ + { + "name": "cdx:pypi:requiresPython", + "value": ">=3.9" + }, + { + "name": "SrcFile", + "value": "/home/runner/work/mongo-python-driver/mongo-python-driver/pyproject.toml" + } + ], + "type": "application", + "bom-ref": "pkg:pypi/pymongo@latest", + "purl": "pkg:pypi/pymongo@latest", + "version": "latest", + "licenses": [ + { + "license": { + "id": "Apache-2.0", + "url": "https://opensource.org/licenses/Apache-2.0" + } + } + ] + }, + "properties": [ + { + "name": "cdx:bom:componentTypes", + "value": "pypi" + }, + { + "name": "cdx:bom:componentSrcFiles", + "value": "pyproject.toml" + } + ] + }, + "components": [ + { + "group": "", + "name": "pymongo", + "version": "latest", + "purl": "pkg:pypi/pymongo@latest", + "type": "library", + "bom-ref": "pkg:pypi/pymongo@latest", + "properties": [ + { + "name": "SrcFile", + "value": "pyproject.toml" + } + ], + "evidence": { + "identity": { + "field": "purl", + "confidence": 1, + "methods": [ + { + "technique": "instrumentation", + "confidence": 1, + "value": "/home/runner/work/mongo-python-driver/mongo-python-driver/.venv" + } + ] + } + } + }, + { + "author": "Bob Halley ", + "group": "", + "name": "dnspython", + "version": "2.8.0", + "description": "DNS toolkit", + "licenses": [ + { + "license": { + "id": "ISC", + "url": "https://opensource.org/licenses/ISC" + } + } + ], + "purl": "pkg:pypi/dnspython@2.8.0", + "type": "library", + "bom-ref": "pkg:pypi/dnspython@2.8.0", + "properties": [ + { + "name": "SrcFile", + "value": "pyproject.toml" + } + ], + "evidence": { + "identity": { + "field": "purl", + "confidence": 1, + "methods": [ + { + "technique": "instrumentation", + "confidence": 1, + "value": "/home/runner/work/mongo-python-driver/mongo-python-driver/.venv" + } + ] + } + } + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/dnspython@2.8.0", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/pymongo@latest", + "dependsOn": [ + "pkg:pypi/dnspython@2.8.0" + ] + } + ] +} \ No newline at end of file From 0c5eec790b6b0e87c010696ae3470653465d3535 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:13:29 -0500 Subject: [PATCH 014/129] [Spec Resync] 11-10-2025 (#2609) Co-authored-by: Cloud User Co-authored-by: Noah Stapp Co-authored-by: Jib --- .evergreen/remove-unimplemented-tests.sh | 2 +- .evergreen/spec-patch/PYTHON-5445.patch | 26 + .evergreen/spec-patch/PYTHON-5559.patch | 815 ++++++++++++++++++ .../etc/data/lookup/schema-non-csfle.json | 3 + .../spec/unified/fle2v2-CreateCollection.json | 2 +- ...v2-validatorAndPartialFieldExpression.json | 4 +- 6 files changed, 848 insertions(+), 4 deletions(-) create mode 100644 .evergreen/spec-patch/PYTHON-5445.patch create mode 100644 .evergreen/spec-patch/PYTHON-5559.patch create mode 100644 test/client-side-encryption/etc/data/lookup/schema-non-csfle.json diff --git a/.evergreen/remove-unimplemented-tests.sh b/.evergreen/remove-unimplemented-tests.sh index 88ef137f86..9e049de318 100755 --- a/.evergreen/remove-unimplemented-tests.sh +++ b/.evergreen/remove-unimplemented-tests.sh @@ -41,7 +41,7 @@ rm $PYMONGO/test/index_management/index-rawdata.json rm $PYMONGO/test/collection_management/modifyCollection-*.json # PYTHON-5248 - Remove support for MongoDB 4.0 -find /$PYMONGO /test -type f -name 'pre-42-*.json' -delete +find /$PYMONGO/test -type f -name 'pre-42-*.json' -delete # PYTHON-3359 - Remove Database and Collection level timeout override rm $PYMONGO/test/csot/override-collection-timeoutMS.json diff --git a/.evergreen/spec-patch/PYTHON-5445.patch b/.evergreen/spec-patch/PYTHON-5445.patch new file mode 100644 index 0000000000..4ff67399b5 --- /dev/null +++ b/.evergreen/spec-patch/PYTHON-5445.patch @@ -0,0 +1,26 @@ +diff --git a/test/auth/legacy/connection-string.json b/test/auth/legacy/connection-string.json +index 3a099c813..8982b61d5 100644 +--- a/test/auth/legacy/connection-string.json ++++ b/test/auth/legacy/connection-string.json +@@ -440,6 +440,21 @@ + } + } + }, ++ { ++ "description": "should throw an exception if username provided (MONGODB-AWS)", ++ "uri": "mongodb://user@localhost.com/?authMechanism=MONGODB-AWS", ++ "valid": false ++ }, ++ { ++ "description": "should throw an exception if username and password provided (MONGODB-AWS)", ++ "uri": "mongodb://user:pass@localhost.com/?authMechanism=MONGODB-AWS", ++ "valid": false ++ }, ++ { ++ "description": "should throw an exception if AWS_SESSION_TOKEN provided (MONGODB-AWS)", ++ "uri": "mongodb://localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token", ++ "valid": false ++ }, + { + "description": "should recognise the mechanism with test environment (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", diff --git a/.evergreen/spec-patch/PYTHON-5559.patch b/.evergreen/spec-patch/PYTHON-5559.patch new file mode 100644 index 0000000000..29ad8d4829 --- /dev/null +++ b/.evergreen/spec-patch/PYTHON-5559.patch @@ -0,0 +1,815 @@ +diff --git a/test/sessions/snapshot-sessions.json b/test/sessions/snapshot-sessions.json +index 260f8b6f4..8f806ea75 100644 +--- a/test/sessions/snapshot-sessions.json ++++ b/test/sessions/snapshot-sessions.json +@@ -988,6 +988,810 @@ + } + } + ] ++ }, ++ { ++ "description": "Find operation with snapshot and snapshot time", ++ "operations": [ ++ { ++ "name": "find", ++ "object": "collection0", ++ "arguments": { ++ "session": "session0", ++ "filter": {} ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ }, ++ { ++ "_id": 2, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "getSnapshotTime", ++ "object": "session0", ++ "saveResultAsEntity": "savedSnapshotTime" ++ }, ++ { ++ "name": "insertOne", ++ "object": "collection0", ++ "arguments": { ++ "document": { ++ "_id": 3, ++ "x": 33 ++ } ++ } ++ }, ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "session": { ++ "id": "session2", ++ "client": "client0", ++ "sessionOptions": { ++ "snapshot": true, ++ "snapshotTime": "savedSnapshotTime" ++ } ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "find", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "filter": {} ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ }, ++ { ++ "_id": 2, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "find", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "filter": {} ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ }, ++ { ++ "_id": 2, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "find", ++ "object": "collection0", ++ "arguments": { ++ "filter": {} ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ }, ++ { ++ "_id": 2, ++ "x": 11 ++ }, ++ { ++ "_id": 3, ++ "x": 33 ++ } ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$exists": false ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "collection0", ++ "readConcern": { ++ "$$exists": false ++ } ++ }, ++ "databaseName": "database0" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Distinct operation with snapshot and snapshot time", ++ "operations": [ ++ { ++ "name": "distinct", ++ "object": "collection0", ++ "arguments": { ++ "session": "session0", ++ "filter": {}, ++ "fieldName": "x" ++ }, ++ "expectResult": [ ++ 11 ++ ] ++ }, ++ { ++ "name": "getSnapshotTime", ++ "object": "session0", ++ "saveResultAsEntity": "savedSnapshotTime" ++ }, ++ { ++ "name": "insertOne", ++ "object": "collection0", ++ "arguments": { ++ "document": { ++ "_id": 3, ++ "x": 33 ++ } ++ } ++ }, ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "session": { ++ "id": "session2", ++ "client": "client0", ++ "sessionOptions": { ++ "snapshot": true, ++ "snapshotTime": "savedSnapshotTime" ++ } ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "distinct", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "filter": {}, ++ "fieldName": "x" ++ }, ++ "expectResult": [ ++ 11 ++ ] ++ }, ++ { ++ "name": "distinct", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "filter": {}, ++ "fieldName": "x" ++ }, ++ "expectResult": [ ++ 11 ++ ] ++ }, ++ { ++ "name": "distinct", ++ "object": "collection0", ++ "arguments": { ++ "filter": {}, ++ "fieldName": "x" ++ }, ++ "expectResult": [ ++ 11, ++ 33 ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "distinct": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$exists": false ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "distinct": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "distinct": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "distinct": "collection0", ++ "readConcern": { ++ "$$exists": false ++ } ++ }, ++ "databaseName": "database0" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Aggregate operation with snapshot and snapshot time", ++ "operations": [ ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "session": "session0", ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": 1 ++ } ++ } ++ ] ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "getSnapshotTime", ++ "object": "session0", ++ "saveResultAsEntity": "savedSnapshotTime" ++ }, ++ { ++ "name": "findOneAndUpdate", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ }, ++ "update": { ++ "$inc": { ++ "x": 1 ++ } ++ }, ++ "returnDocument": "After" ++ }, ++ "expectResult": { ++ "_id": 1, ++ "x": 12 ++ } ++ }, ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "session": { ++ "id": "session2", ++ "client": "client0", ++ "sessionOptions": { ++ "snapshot": true, ++ "snapshotTime": "savedSnapshotTime" ++ } ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": 1 ++ } ++ } ++ ] ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": 1 ++ } ++ } ++ ] ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": 1 ++ } ++ } ++ ] ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 12 ++ } ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$exists": false ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "$$exists": false ++ } ++ }, ++ "databaseName": "database0" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "countDocuments operation with snapshot and snapshot time", ++ "operations": [ ++ { ++ "name": "countDocuments", ++ "object": "collection0", ++ "arguments": { ++ "session": "session0", ++ "filter": {} ++ }, ++ "expectResult": 2 ++ }, ++ { ++ "name": "getSnapshotTime", ++ "object": "session0", ++ "saveResultAsEntity": "savedSnapshotTime" ++ }, ++ { ++ "name": "insertOne", ++ "object": "collection0", ++ "arguments": { ++ "document": { ++ "_id": 3, ++ "x": 33 ++ } ++ } ++ }, ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "session": { ++ "id": "session2", ++ "client": "client0", ++ "sessionOptions": { ++ "snapshot": true, ++ "snapshotTime": "savedSnapshotTime" ++ } ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "countDocuments", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "filter": {} ++ }, ++ "expectResult": 2 ++ }, ++ { ++ "name": "countDocuments", ++ "object": "collection0", ++ "arguments": { ++ "session": "session2", ++ "filter": {} ++ }, ++ "expectResult": 2 ++ }, ++ { ++ "name": "countDocuments", ++ "object": "collection0", ++ "arguments": { ++ "filter": {} ++ }, ++ "expectResult": 3 ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$exists": false ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ }, ++ "databaseName": "database0" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "$$exists": false ++ } ++ }, ++ "databaseName": "database0" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "Mixed operation with snapshot and snapshotTime", ++ "operations": [ ++ { ++ "name": "find", ++ "object": "collection0", ++ "arguments": { ++ "session": "session0", ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "getSnapshotTime", ++ "object": "session0", ++ "saveResultAsEntity": "savedSnapshotTime" ++ }, ++ { ++ "name": "findOneAndUpdate", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ }, ++ "update": { ++ "$inc": { ++ "x": 1 ++ } ++ }, ++ "returnDocument": "After" ++ }, ++ "expectResult": { ++ "_id": 1, ++ "x": 12 ++ } ++ }, ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "session": { ++ "id": "session2", ++ "client": "client0", ++ "sessionOptions": { ++ "snapshot": true, ++ "snapshotTime": "savedSnapshotTime" ++ } ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "find", ++ "object": "collection0", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 12 ++ } ++ ] ++ }, ++ { ++ "name": "aggregate", ++ "object": "collection0", ++ "arguments": { ++ "pipeline": [ ++ { ++ "$match": { ++ "_id": 1 ++ } ++ } ++ ], ++ "session": "session2" ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "x": 11 ++ } ++ ] ++ }, ++ { ++ "name": "distinct", ++ "object": "collection0", ++ "arguments": { ++ "fieldName": "x", ++ "filter": {}, ++ "session": "session2" ++ }, ++ "expectResult": [ ++ 11 ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$exists": false ++ } ++ } ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "collection0", ++ "readConcern": { ++ "$$exists": false ++ } ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "aggregate": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "distinct": "collection0", ++ "readConcern": { ++ "level": "snapshot", ++ "atClusterTime": { ++ "$$matchesEntity": "savedSnapshotTime" ++ } ++ } ++ } ++ } ++ } ++ ] ++ } ++ ] + } + ] + } diff --git a/test/client-side-encryption/etc/data/lookup/schema-non-csfle.json b/test/client-side-encryption/etc/data/lookup/schema-non-csfle.json new file mode 100644 index 0000000000..3edd12c8f4 --- /dev/null +++ b/test/client-side-encryption/etc/data/lookup/schema-non-csfle.json @@ -0,0 +1,3 @@ +{ + "bsonType": "object" +} diff --git a/test/client-side-encryption/spec/unified/fle2v2-CreateCollection.json b/test/client-side-encryption/spec/unified/fle2v2-CreateCollection.json index 3dfb76c461..81a4c61731 100644 --- a/test/client-side-encryption/spec/unified/fle2v2-CreateCollection.json +++ b/test/client-side-encryption/spec/unified/fle2v2-CreateCollection.json @@ -554,7 +554,7 @@ }, { "name": "assertIndexNotExists", - "object": "db", + "object": "testRunner", "arguments": { "databaseName": "default", "collectionName": "encryptedCollection", diff --git a/test/client-side-encryption/spec/unified/fle2v2-validatorAndPartialFieldExpression.json b/test/client-side-encryption/spec/unified/fle2v2-validatorAndPartialFieldExpression.json index 54cc60a3b1..8d69464400 100644 --- a/test/client-side-encryption/spec/unified/fle2v2-validatorAndPartialFieldExpression.json +++ b/test/client-side-encryption/spec/unified/fle2v2-validatorAndPartialFieldExpression.json @@ -108,7 +108,7 @@ }, { "name": "assertCollectionExists", - "object": "coll", + "object": "testRunner", "arguments": { "databaseName": "default", "collectionName": "encryptedCollection" @@ -248,7 +248,7 @@ }, { "name": "assertIndexExists", - "object": "coll", + "object": "testRunner", "arguments": { "databaseName": "default", "collectionName": "encryptedCollection", From a9c034426b4767f093ea9aca65220648ac4d24e6 Mon Sep 17 00:00:00 2001 From: Kevin Albertson Date: Fri, 21 Nov 2025 11:33:18 -0500 Subject: [PATCH 015/129] PYTHON-5647 extend `ALLOWED_HOSTS` (#2618) --- pymongo/auth_shared.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pymongo/auth_shared.py b/pymongo/auth_shared.py index 5a9a2b6732..605dbf2611 100644 --- a/pymongo/auth_shared.py +++ b/pymongo/auth_shared.py @@ -159,6 +159,8 @@ def _build_credentials_tuple( "localhost", "127.0.0.1", "::1", + "*.mongo.com", + "*.mongodbgov.net", ] allowed_hosts = properties.get("ALLOWED_HOSTS", default_allowed) if properties.get("ALLOWED_HOSTS", None) is not None and human_callback is None: From cef27b18d9731de60d3af3c716a846f66b921b5a Mon Sep 17 00:00:00 2001 From: thanhnguyen-mdb Date: Mon, 24 Nov 2025 10:21:00 -0600 Subject: [PATCH 016/129] PYTHON-5433 - Fix Silkbomb issues (#2622) --- .github/workflows/sbom.yml | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index fcf39902da..373139f274 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -1,6 +1,6 @@ name: Generate SBOM -# This workflow uses cdxgen and publishes an sbom.json artifact. +# This workflow uses cyclonedx-py and publishes an sbom.json artifact. # It runs on manual trigger or when package files change on main branch, # and creates a PR with the updated SBOM. # Internal documentation: go/sbom-scope @@ -42,9 +42,26 @@ jobs: source .venv/bin/activate pip install -r requirements.txt pip install . - npx @cyclonedx/cdxgen -t python --exclude "uv.lock" --exclude "requirements/**" --exclude "requirements.txt" --spec-version 1.5 --no-validate --json-pretty -o sbom.json - env: - FETCH_LICENSE: true + pip uninstall -y pip setuptools + deactivate + python -m venv .venv-sbom + source .venv-sbom/bin/activate + pip install cyclonedx-bom==7.2.1 + cyclonedx-py environment --spec-version 1.5 --output-format JSON --output-file sbom.json .venv + # Add PURL for pymongo (local package doesn't get PURL automatically) + jq '(.components[] | select(.name == "pymongo" and .purl == null)) |= (. + {purl: ("pkg:pypi/pymongo@" + .version)})' sbom.json > sbom.tmp.json && mv sbom.tmp.json sbom.json + + - name: Download CycloneDX CLI + run: | + curl -L -s -o /tmp/cyclonedx "https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.29.1/cyclonedx-linux-x64" + chmod +x /tmp/cyclonedx + + - name: Validate SBOM + run: /tmp/cyclonedx validate --input-file sbom.json --fail-on-errors + + - name: Cleanup + if: always() + run: rm -rf .venv .venv-sbom - name: Upload SBOM artifact uses: actions/upload-artifact@v4 @@ -70,7 +87,7 @@ jobs: - Updated `sbom.json` to reflect current dependencies ### Verification - The SBOM was generated using cdxgen with the current Python environment. + The SBOM was generated using cyclonedx-py v7.2.1 with the current Python environment. ### Triggered by - Commit: ${{ github.sha }} @@ -82,7 +99,3 @@ jobs: sbom automated dependencies - - - name: Cleanup - if: always() - run: rm -rf .venv From 1a434c7c595898cf665b25fba8cc5e5a2c95a6e4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:34:44 -0600 Subject: [PATCH 017/129] chore: Update SBOM (#2623) Co-authored-by: blink1073 <2096628+blink1073@users.noreply.github.com> --- sbom.json | 293 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 168 insertions(+), 125 deletions(-) diff --git a/sbom.json b/sbom.json index d2e02eeb71..7daf0f8070 100644 --- a/sbom.json +++ b/sbom.json @@ -1,159 +1,202 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.5", - "serialNumber": "urn:uuid:f91a87bf-a37f-4c1e-805f-142f60b2c960", - "version": 1, - "metadata": { - "timestamp": "2025-11-20T21:30:34Z", - "tools": { - "components": [ + "components": [ + { + "bom-ref": "dnspython==2.8.0", + "description": "DNS toolkit", + "externalReferences": [ { - "group": "@cyclonedx", - "name": "cdxgen", - "version": "11.11.0", - "purl": "pkg:npm/%40cyclonedx/cdxgen@11.11.0", - "type": "application", - "bom-ref": "pkg:npm/@cyclonedx/cdxgen@11.11.0", - "author": "OWASP Foundation", - "publisher": "OWASP Foundation" - } - ] - }, - "authors": [ - { - "name": "OWASP Foundation" - } - ], - "lifecycles": [ - { - "phase": "build" - } - ], - "component": { - "name": "pymongo", - "description": "PyMongo - the Official MongoDB Python driver", - "authors": [ + "comment": "from packaging metadata Project-URL: documentation", + "type": "documentation", + "url": "https://dnspython.readthedocs.io/en/stable/" + }, { - "name": "The MongoDB Python Team" - } - ], - "tags": [ - "bson", - "gridfs", - "mongo", - "mongodb", - "pymongo" - ], - "properties": [ + "comment": "from packaging metadata Project-URL: issues", + "type": "issue-tracker", + "url": "https://github.com/rthalley/dnspython/issues" + }, { - "name": "cdx:pypi:requiresPython", - "value": ">=3.9" + "comment": "from packaging metadata Project-URL: repository", + "type": "vcs", + "url": "https://github.com/rthalley/dnspython.git" }, { - "name": "SrcFile", - "value": "/home/runner/work/mongo-python-driver/mongo-python-driver/pyproject.toml" + "comment": "from packaging metadata Project-URL: homepage", + "type": "website", + "url": "https://www.dnspython.org" } ], - "type": "application", - "bom-ref": "pkg:pypi/pymongo@latest", - "purl": "pkg:pypi/pymongo@latest", - "version": "latest", "licenses": [ { "license": { - "id": "Apache-2.0", - "url": "https://opensource.org/licenses/Apache-2.0" + "id": "ISC" } } - ] + ], + "name": "dnspython", + "purl": "pkg:pypi/dnspython@2.8.0", + "type": "library", + "version": "2.8.0" }, - "properties": [ - { - "name": "cdx:bom:componentTypes", - "value": "pypi" - }, - { - "name": "cdx:bom:componentSrcFiles", - "value": "pyproject.toml" - } - ] - }, - "components": [ { - "group": "", - "name": "pymongo", - "version": "latest", - "purl": "pkg:pypi/pymongo@latest", - "type": "library", - "bom-ref": "pkg:pypi/pymongo@latest", - "properties": [ + "bom-ref": "pymongo==4.16.0.dev0", + "description": "PyMongo - the Official MongoDB Python driver", + "externalReferences": [ + { + "comment": "PackageSource: Local", + "type": "distribution", + "url": "file:///home/runner/work/mongo-python-driver/mongo-python-driver" + }, + { + "comment": "from packaging metadata Project-URL: Documentation", + "type": "documentation", + "url": "https://www.mongodb.com/docs/languages/python/pymongo-driver/current/" + }, { - "name": "SrcFile", - "value": "pyproject.toml" + "comment": "from packaging metadata Project-URL: Tracker", + "type": "issue-tracker", + "url": "https://jira.mongodb.org/projects/PYTHON/issues" + }, + { + "comment": "from packaging metadata Project-URL: Source", + "type": "other", + "url": "https://github.com/mongodb/mongo-python-driver" + }, + { + "comment": "from packaging metadata Project-URL: Homepage", + "type": "website", + "url": "https://www.mongodb.org" } ], - "evidence": { - "identity": { - "field": "purl", - "confidence": 1, - "methods": [ - { - "technique": "instrumentation", - "confidence": 1, - "value": "/home/runner/work/mongo-python-driver/mongo-python-driver/.venv" - } - ] - } - } - }, - { - "author": "Bob Halley ", - "group": "", - "name": "dnspython", - "version": "2.8.0", - "description": "DNS toolkit", "licenses": [ { "license": { - "id": "ISC", - "url": "https://opensource.org/licenses/ISC" + "id": "Apache-2.0" } } ], - "purl": "pkg:pypi/dnspython@2.8.0", + "name": "pymongo", "type": "library", - "bom-ref": "pkg:pypi/dnspython@2.8.0", - "properties": [ - { - "name": "SrcFile", - "value": "pyproject.toml" - } - ], - "evidence": { - "identity": { - "field": "purl", - "confidence": 1, - "methods": [ - { - "technique": "instrumentation", - "confidence": 1, - "value": "/home/runner/work/mongo-python-driver/mongo-python-driver/.venv" - } - ] - } - } + "version": "4.16.0.dev0", + "purl": "pkg:pypi/pymongo@4.16.0.dev0" } ], "dependencies": [ { - "ref": "pkg:pypi/dnspython@2.8.0", - "dependsOn": [] + "ref": "dnspython==2.8.0" }, { - "ref": "pkg:pypi/pymongo@latest", "dependsOn": [ - "pkg:pypi/dnspython@2.8.0" + "dnspython==2.8.0" + ], + "ref": "pymongo==4.16.0.dev0" + } + ], + "metadata": { + "timestamp": "2025-11-24T16:21:47.249880+00:00", + "tools": { + "components": [ + { + "description": "CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environments", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-bom/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-bom-tool.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python/" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-py", + "type": "application", + "version": "7.2.1" + }, + { + "description": "Python library for CycloneDX", + "externalReferences": [ + { + "type": "build-system", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions" + }, + { + "type": "distribution", + "url": "https://pypi.org/project/cyclonedx-python-lib/" + }, + { + "type": "documentation", + "url": "https://cyclonedx-python-library.readthedocs.io/" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues" + }, + { + "type": "license", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE" + }, + { + "type": "release-notes", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-python-lib/#readme" + } + ], + "group": "CycloneDX", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "name": "cyclonedx-python-lib", + "type": "library", + "version": "11.5.0" + } ] } - ] -} \ No newline at end of file + }, + "serialNumber": "urn:uuid:7a19d697-d41e-4e88-b953-4bccb5d79937", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5" +} From 42cf3407c8878aad280b7ebf56f5b81665a12252 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 24 Nov 2025 11:43:48 -0500 Subject: [PATCH 018/129] PYTHON-5642 - getMore operations should do server selection if the server is unknown (#2621) --- doc/changelog.rst | 7 ++++ pymongo/topology_description.py | 2 +- test/asynchronous/test_server_selection.py | 40 ++++++++++++++++++++-- test/test_server_selection.py | 38 ++++++++++++++++++-- 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index dbf24aaaad..3fd2387f96 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -18,6 +18,13 @@ PyMongo 4.16 brings a number of changes including: Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. - Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions. +Changes in Version 4.15.5 (2025/XX/XX) +-------------------------------------- + +Version 4.15.5 is a bug fix release. + +- Fixed a bug that could cause ``AutoReconnect("connection pool paused")`` errors when cursors fetched more documents from the database after SDAM heartbeat failures. + Changes in Version 4.15.4 (2025/10/21) -------------------------------------- diff --git a/pymongo/topology_description.py b/pymongo/topology_description.py index de67a8f94a..a315c1b885 100644 --- a/pymongo/topology_description.py +++ b/pymongo/topology_description.py @@ -322,7 +322,7 @@ def apply_selector( if address: # Ignore selectors when explicit address is requested. description = self.server_descriptions().get(address) - return [description] if description else [] + return [description] if description and description.is_server_type_known else [] # Primary selection fast path. if self.topology_type == TOPOLOGY_TYPE.ReplicaSetWithPrimary and type(selector) is Primary: diff --git a/test/asynchronous/test_server_selection.py b/test/asynchronous/test_server_selection.py index f570662b85..b704fcea83 100644 --- a/test/asynchronous/test_server_selection.py +++ b/test/asynchronous/test_server_selection.py @@ -17,9 +17,10 @@ import os import sys +import time from pathlib import Path -from pymongo import AsyncMongoClient, ReadPreference +from pymongo import AsyncMongoClient, ReadPreference, monitoring from pymongo.asynchronous.settings import TopologySettings from pymongo.asynchronous.topology import Topology from pymongo.errors import ServerSelectionTimeoutError @@ -30,7 +31,7 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.asynchronous import AsyncIntegrationTest, async_client_context, client_knobs, unittest from test.asynchronous.utils import async_wait_until from test.asynchronous.utils_selection_tests import ( create_selection_tests, @@ -42,6 +43,7 @@ ) from test.utils_shared import ( FunctionCallRecorder, + HeartbeatEventListener, OvertCommandListener, ) @@ -207,6 +209,40 @@ async def test_server_selector_bypassed(self): ) self.assertEqual(selector.call_count, 0) + @async_client_context.require_replica_set + @async_client_context.require_failCommand_appName + async def test_server_selection_getMore_blocks(self): + hb_listener = HeartbeatEventListener() + client = await self.async_rs_client( + event_listeners=[hb_listener], heartbeatFrequencyMS=500, appName="heartbeatFailedClient" + ) + coll = client.db.test + await coll.drop() + docs = [{"x": 1} for _ in range(5)] + await coll.insert_many(docs) + + fail_heartbeat = { + "configureFailPoint": "failCommand", + "mode": {"times": 4}, + "data": { + "failCommands": [HelloCompat.LEGACY_CMD, "hello"], + "closeConnection": True, + "appName": "heartbeatFailedClient", + }, + } + + def hb_failed(event): + return isinstance(event, monitoring.ServerHeartbeatFailedEvent) + + cursor = coll.find({}, batch_size=1) + await cursor.next() # force initial query that will pin the address for the getMore + + async with self.fail_point(fail_heartbeat): + await async_wait_until( + lambda: hb_listener.matching(hb_failed), "published failed event" + ) + self.assertEqual(len(await cursor.to_list()), 4) + if __name__ == "__main__": unittest.main() diff --git a/test/test_server_selection.py b/test/test_server_selection.py index 4384deac2b..d94e9ed0a1 100644 --- a/test/test_server_selection.py +++ b/test/test_server_selection.py @@ -17,9 +17,10 @@ import os import sys +import time from pathlib import Path -from pymongo import MongoClient, ReadPreference +from pymongo import MongoClient, ReadPreference, monitoring from pymongo.errors import ServerSelectionTimeoutError from pymongo.hello import HelloCompat from pymongo.operations import _Op @@ -30,7 +31,7 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest +from test import IntegrationTest, client_context, client_knobs, unittest from test.utils import wait_until from test.utils_selection_tests import ( create_selection_tests, @@ -42,6 +43,7 @@ ) from test.utils_shared import ( FunctionCallRecorder, + HeartbeatEventListener, OvertCommandListener, ) @@ -205,6 +207,38 @@ def test_server_selector_bypassed(self): topology.select_server(writable_server_selector, _Op.TEST, server_selection_timeout=0.1) self.assertEqual(selector.call_count, 0) + @client_context.require_replica_set + @client_context.require_failCommand_appName + def test_server_selection_getMore_blocks(self): + hb_listener = HeartbeatEventListener() + client = self.rs_client( + event_listeners=[hb_listener], heartbeatFrequencyMS=500, appName="heartbeatFailedClient" + ) + coll = client.db.test + coll.drop() + docs = [{"x": 1} for _ in range(5)] + coll.insert_many(docs) + + fail_heartbeat = { + "configureFailPoint": "failCommand", + "mode": {"times": 4}, + "data": { + "failCommands": [HelloCompat.LEGACY_CMD, "hello"], + "closeConnection": True, + "appName": "heartbeatFailedClient", + }, + } + + def hb_failed(event): + return isinstance(event, monitoring.ServerHeartbeatFailedEvent) + + cursor = coll.find({}, batch_size=1) + cursor.next() # force initial query that will pin the address for the getMore + + with self.fail_point(fail_heartbeat): + wait_until(lambda: hb_listener.matching(hb_failed), "published failed event") + self.assertEqual(len(cursor.to_list()), 4) + if __name__ == "__main__": unittest.main() From 881094015b0ab237f75f08c690011bb4d5341e97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:58:34 -0600 Subject: [PATCH 019/129] Bump the actions group with 7 updates (#2620) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/dist.yml | 4 ++-- .github/workflows/sbom.yml | 8 ++++---- .github/workflows/test-python.yml | 34 +++++++++++++++---------------- .github/workflows/zizmor.yml | 4 ++-- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5820c86b97..666ea80aba 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: build-mode: none steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} persist-credentials: false @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 + uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -63,6 +63,6 @@ jobs: pip install -e . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4 + uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 7530e73e41..81c6fa4200 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout pymongo - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false @@ -101,7 +101,7 @@ jobs: name: Make SDist runs-on: macos-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 373139f274..e2788f3618 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" @@ -64,14 +64,14 @@ jobs: run: rm -rf .venv .venv-sbom - name: Upload SBOM artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: sbom path: sbom.json if-no-files-found: error - name: Create Pull Request - uses: peter-evans/create-pull-request@b4733b9419fd47bbfa1807b15627e17cd70b5b22 + uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore: Update SBOM after dependency changes' diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 20323dd925..682a8d98d0 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -22,11 +22,11 @@ jobs: static: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: "3.10" @@ -64,11 +64,11 @@ jobs: name: CPython ${{ matrix.python-version }}-${{ matrix.os }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -83,11 +83,11 @@ jobs: runs-on: ubuntu-latest name: DocTest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: "3.10" @@ -108,11 +108,11 @@ jobs: name: Docs Checks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: "3.10" @@ -127,11 +127,11 @@ jobs: name: Link Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: "3.10" @@ -149,11 +149,11 @@ jobs: matrix: python: ["3.10", "3.11"] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -170,11 +170,11 @@ jobs: runs-on: ubuntu-latest name: Integration Tests steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: enable-cache: true python-version: "3.10" @@ -200,7 +200,7 @@ jobs: runs-on: ubuntu-latest name: "Make an sdist" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-python@v6 @@ -260,11 +260,11 @@ jobs: runs-on: ubuntu-latest name: Test minimum dependencies and Python steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 with: python-version: "3.10" - id: setup-mongodb diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 6d78564f83..00921621d9 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -14,8 +14,8 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@1aba86d8e1245be7a9ca003d46fcc85a76e6aa61 + uses: zizmorcore/zizmor-action@b0e5c0b2b3785bc67b9b6c743fdbd495cda1b4c4 From 3d76c84b2ac700e8b5c0cb37ca3cb25cbe9e3d79 Mon Sep 17 00:00:00 2001 From: Kevin Albertson Date: Tue, 25 Nov 2025 15:27:28 -0500 Subject: [PATCH 020/129] PYTHON-5647 remove redundant entry for `*.mongodbgov.net` (#2625) --- pymongo/auth_shared.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pymongo/auth_shared.py b/pymongo/auth_shared.py index 605dbf2611..715749243f 100644 --- a/pymongo/auth_shared.py +++ b/pymongo/auth_shared.py @@ -160,7 +160,6 @@ def _build_credentials_tuple( "127.0.0.1", "::1", "*.mongo.com", - "*.mongodbgov.net", ] allowed_hosts = properties.get("ALLOWED_HOSTS", default_allowed) if properties.get("ALLOWED_HOSTS", None) is not None and human_callback is None: From 222a55f8cd4bc488a871102f931595a1838c7b13 Mon Sep 17 00:00:00 2001 From: Cal Jacobson Date: Tue, 25 Nov 2025 14:36:33 -0600 Subject: [PATCH 021/129] PYTHON-5653: fix - correct return type annotation for `find_one_and_*` methods to include `None` (#2615) Co-authored-by: Jib Co-authored-by: Casey Clements --- doc/changelog.rst | 2 ++ doc/contributors.rst | 1 + pymongo/asynchronous/collection.py | 28 ++++++++++++++++++++++++---- pymongo/synchronous/collection.py | 28 ++++++++++++++++++++++++---- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 3fd2387f96..42efdacba5 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -17,6 +17,8 @@ PyMongo 4.16 brings a number of changes including: - Removed support for Eventlet. Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. - Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions. +- Fixed return type annotation for ``find_one_and_*`` methods on :class:`~pymongo.asynchronous.collection.AsyncCollection` + and :class:`~pymongo.synchronous.collection.Collection` to include ``None``. Changes in Version 4.15.5 (2025/XX/XX) -------------------------------------- diff --git a/doc/contributors.rst b/doc/contributors.rst index 08296e9595..0bd815ce3f 100644 --- a/doc/contributors.rst +++ b/doc/contributors.rst @@ -107,3 +107,4 @@ The following is a list of people who have contributed to - Jeffrey A. Clark (aclark4life) - Steven Silvester (blink1073) - Noah Stapp (NoahStapp) +- Cal Jacobson (cj81499) diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index e7e2f58031..53b4992493 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -3310,7 +3310,7 @@ async def find_one_and_delete( let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _DocumentType: + ) -> Optional[_DocumentType]: """Finds a single document and deletes it, returning the document. >>> await db.test.count_documents({'x': 1}) @@ -3320,6 +3320,10 @@ async def find_one_and_delete( >>> await db.test.count_documents({'x': 1}) 1 + Returns ``None`` if no document matches the filter. + + >>> await db.test.find_one_and_delete({'_exists': False}) + If multiple documents match *filter*, a *sort* can be applied. >>> async for doc in db.test.find({'x': 1}): @@ -3402,10 +3406,22 @@ async def find_one_and_replace( let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _DocumentType: + ) -> Optional[_DocumentType]: """Finds a single document and replaces it, returning either the original or the replaced document. + >>> await db.test.find_one({'x': 1}) + {'_id': 0, 'x': 1} + >>> await db.test.find_one_and_replace({'x': 1}, {'y': 2}) + {'_id': 0, 'x': 1} + >>> await db.test.find_one({'x': 1}) + >>> await db.test.find_one({'y': 2}) + {'_id': 0, 'y': 2} + + Returns ``None`` if no document matches the filter. + + >>> await db.test.find_one_and_replace({'_exists': False}, {'x': 1}) + The :meth:`find_one_and_replace` method differs from :meth:`find_one_and_update` by replacing the document matched by *filter*, rather than modifying the existing document. @@ -3510,13 +3526,17 @@ async def find_one_and_update( let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _DocumentType: + ) -> Optional[_DocumentType]: """Finds a single document and updates it, returning either the original or the updated document. + >>> await db.test.find_one({'_id': 665}) + {'_id': 665, 'done': False, 'count': 25} >>> await db.test.find_one_and_update( ... {'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}}) - {'_id': 665, 'done': False, 'count': 25}} + {'_id': 665, 'done': False, 'count': 25} + >>> await db.test.find_one({'_id': 665}) + {'_id': 665, 'done': True, 'count': 26} Returns ``None`` if no document matches the filter. diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index 4e5f7d08fb..edc6047330 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -3303,7 +3303,7 @@ def find_one_and_delete( let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _DocumentType: + ) -> Optional[_DocumentType]: """Finds a single document and deletes it, returning the document. >>> db.test.count_documents({'x': 1}) @@ -3313,6 +3313,10 @@ def find_one_and_delete( >>> db.test.count_documents({'x': 1}) 1 + Returns ``None`` if no document matches the filter. + + >>> db.test.find_one_and_delete({'_exists': False}) + If multiple documents match *filter*, a *sort* can be applied. >>> for doc in db.test.find({'x': 1}): @@ -3395,10 +3399,22 @@ def find_one_and_replace( let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _DocumentType: + ) -> Optional[_DocumentType]: """Finds a single document and replaces it, returning either the original or the replaced document. + >>> db.test.find_one({'x': 1}) + {'_id': 0, 'x': 1} + >>> db.test.find_one_and_replace({'x': 1}, {'y': 2}) + {'_id': 0, 'x': 1} + >>> db.test.find_one({'x': 1}) + >>> db.test.find_one({'y': 2}) + {'_id': 0, 'y': 2} + + Returns ``None`` if no document matches the filter. + + >>> db.test.find_one_and_replace({'_exists': False}, {'x': 1}) + The :meth:`find_one_and_replace` method differs from :meth:`find_one_and_update` by replacing the document matched by *filter*, rather than modifying the existing document. @@ -3503,13 +3519,17 @@ def find_one_and_update( let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _DocumentType: + ) -> Optional[_DocumentType]: """Finds a single document and updates it, returning either the original or the updated document. + >>> db.test.find_one({'_id': 665}) + {'_id': 665, 'done': False, 'count': 25} >>> db.test.find_one_and_update( ... {'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}}) - {'_id': 665, 'done': False, 'count': 25}} + {'_id': 665, 'done': False, 'count': 25} + >>> db.test.find_one({'_id': 665}) + {'_id': 665, 'done': True, 'count': 26} Returns ``None`` if no document matches the filter. From 8bf826339110605c27e5f7338c075b3fdb26949e Mon Sep 17 00:00:00 2001 From: Casey Clements Date: Mon, 1 Dec 2025 15:15:09 -0500 Subject: [PATCH 022/129] PYTHON-5656: Fixes broken link to aggregation pipeline docs. (#2627) --- pymongo/asynchronous/database.py | 2 +- pymongo/synchronous/database.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongo/asynchronous/database.py b/pymongo/asynchronous/database.py index 8e0afc9dc9..5aa206ee24 100644 --- a/pymongo/asynchronous/database.py +++ b/pymongo/asynchronous/database.py @@ -698,7 +698,7 @@ async def aggregate( .. versionadded:: 3.9 .. _aggregation pipeline: - https://mongodb.com/docs/manual/reference/operator/aggregation-pipeline + https://www.mongodb.com/docs/manual/core/aggregation-pipeline/ .. _aggregate command: https://mongodb.com/docs/manual/reference/command/aggregate diff --git a/pymongo/synchronous/database.py b/pymongo/synchronous/database.py index 0d129ba972..a453a94265 100644 --- a/pymongo/synchronous/database.py +++ b/pymongo/synchronous/database.py @@ -698,7 +698,7 @@ def aggregate( .. versionadded:: 3.9 .. _aggregation pipeline: - https://mongodb.com/docs/manual/reference/operator/aggregation-pipeline + https://www.mongodb.com/docs/manual/core/aggregation-pipeline/ .. _aggregate command: https://mongodb.com/docs/manual/reference/command/aggregate From 6011df9e37c47618bdb4a831b0a199e4aa16e51b Mon Sep 17 00:00:00 2001 From: Casey Clements Date: Mon, 1 Dec 2025 15:17:35 -0500 Subject: [PATCH 023/129] PYTHON-5643 Add contributor docs for the test.utils_shared.delay function (#2628) --- CONTRIBUTING.md | 5 +++++ test/utils_shared.py | 45 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2cf15a9838..7e35063af6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -387,6 +387,11 @@ If you are running one of the `no-responder` tests, omit the `run-server` step. To run any of the test suites with minimum supported dependencies, pass `--test-min-deps` to `just setup-tests`. +## Testing time-dependent operations + +- `test.utils_shared.delay` - One can trigger an arbitrarily long-running operation on the server using this delay utility + in combination with a `$where` operation. Use this to test behaviors around timeouts or signals. + ## Adding a new test suite - If adding new tests files that should only be run for that test suite, add a pytest marker to the file and add diff --git a/test/utils_shared.py b/test/utils_shared.py index 72fb943fc1..70a195215a 100644 --- a/test/utils_shared.py +++ b/test/utils_shared.py @@ -377,7 +377,50 @@ def oid_generated_on_process(oid): def delay(sec): - return """function() { sleep(%f * 1000); return true; }""" % sec + """Along with a ``$where`` operator, this triggers an arbitrarily long-running + operation on the server. + + This can be useful in time-sensitive tests (e.g., timeouts, signals). + Note that you must have at least one document in the collection or the + server may decide not to sleep at all. + + Example + ------- + + .. code-block:: python + + db.coll.insert_one({"x": 1}) + db.test.find_one({"x": 1}) + # {'x': 1, '_id': ObjectId('54f4e12bfba5220aa4d6dee8')} + + # The following will wait 2.5 seconds before returning. + db.test.find_one({"$where": delay(2.5)}) + # {'x': 1, '_id': ObjectId('54f4e12bfba5220aa4d6dee8')} + + Using ``delay`` to provoke a KeyboardInterrupt + ---------------------------------------------- + + .. code-block:: python + + import signal + + # Raise KeyboardInterrupt in 1 second + def sigalarm(num, frame): + raise KeyboardInterrupt + + + signal.signal(signal.SIGALRM, sigalarm) + signal.alarm(1) + + raised = False + try: + clxn.find_one({"$where": delay(1.5)}) + except KeyboardInterrupt: + raised = True + + assert raised + """ + return "function() { sleep(%f * 1000); return true; }" % sec def camel_to_snake(camel): From bd6decb8c073593beef802b9a82a386917eb7140 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:45:03 -0500 Subject: [PATCH 024/129] Bump zizmorcore/zizmor-action from b0e5c0b2b3785bc67b9b6c743fdbd495cda1b4c4 to c0e2b1c877e25a91d1d747c438d49199cad29698 in the actions group (#2630) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jib --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 00921621d9..e3b54cabd9 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@b0e5c0b2b3785bc67b9b6c743fdbd495cda1b4c4 + uses: zizmorcore/zizmor-action@c0e2b1c877e25a91d1d747c438d49199cad29698 From 44baec9e9cc2349e0cffec755066f0460aec0481 Mon Sep 17 00:00:00 2001 From: Jib Date: Thu, 4 Dec 2025 10:49:30 -0500 Subject: [PATCH 025/129] PYTHON-5401: Revise pull request template for better structure (#2626) --- .github/pull_request_template.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8185a38836..2e1a132c95 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,38 +1,33 @@ -[Issue Key](https://jira.mongodb.org/browse/%7BISSUE_KEY%7D) -## Summary - +[JIRA TICKET] ## Changes in this PR -## Testing Plan +## Test Plan -### Screenshots (optional) - - ## Checklist + ### Checklist for Author - [ ] Did you update the changelog (if necessary)? -- [ ] Is the intention of the code captured in relevant tests? -- [ ] If there are new TODOs, has a related JIRA ticket been created? +- [ ] Is there test coverage? +- [ ] Is any followup work tracked in a JIRA ticket? If so, add link(s). -### Checklist for Reviewer {@primary_reviewer} +### Checklist for Reviewer - [ ] Does the title of the PR reference a JIRA Ticket? - [ ] Do you fully understand the implementation? (Would you be comfortable explaining how this code works to someone else?) -- [ ] Have you checked for spelling & grammar errors? - [ ] Is all relevant documentation (README or docstring) updated? - -## Focus Areas for Reviewer (optional) - From 3093a7c7cbd048a84a7014c03a6e295955397eb0 Mon Sep 17 00:00:00 2001 From: Kevin Albertson Date: Thu, 4 Dec 2025 11:58:10 -0500 Subject: [PATCH 026/129] PYTHON-5664 extract using `tar` command (#2636) --- .evergreen/scripts/setup_tests.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 7908836f50..939423ffcc 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -1,12 +1,10 @@ from __future__ import annotations import base64 -import io import os import platform import shutil import stat -import tarfile from pathlib import Path from urllib import request @@ -117,9 +115,10 @@ def setup_libmongocrypt(): LOGGER.info(f"Fetching {url}...") with request.urlopen(request.Request(url), timeout=15.0) as response: # noqa: S310 if response.status == 200: - fileobj = io.BytesIO(response.read()) - with tarfile.open("libmongocrypt.tar.gz", fileobj=fileobj) as fid: - fid.extractall(Path.cwd() / "libmongocrypt") + with Path("libmongocrypt.tar.gz").open("wb") as f: + f.write(response.read()) + Path("libmongocrypt").mkdir() + run_command("tar -xzf libmongocrypt.tar.gz -C libmongocrypt") LOGGER.info(f"Fetching {url}... done.") run_command("ls -la libmongocrypt") From 2195866ba734c1ea92299cabc900b30ddc7e6ce0 Mon Sep 17 00:00:00 2001 From: Casey Clements Date: Fri, 5 Dec 2025 11:39:22 -0500 Subject: [PATCH 027/129] PYTHON-5355 Addition of API to move to and from NumPy ndarrays and BSON BinaryVectors (#2590) Co-authored-by: Jib Co-authored-by: Noah Stapp --- .evergreen/generated_configs/functions.yml | 13 ++ .evergreen/generated_configs/tasks.yml | 22 +++ .evergreen/generated_configs/variants.yml | 39 ++++ .evergreen/scripts/generate_config.py | 39 ++++ bson/binary.py | 214 +++++++++++++++------ doc/changelog.rst | 1 + justfile | 18 +- test/test_bson.py | 62 ++++++ 8 files changed, 338 insertions(+), 70 deletions(-) diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index 4b53ac6ac8..bd983abb3e 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -239,6 +239,19 @@ functions: working_dir: src type: test + # Test numpy + test numpy: + - command: subprocess.exec + params: + binary: bash + args: + - .evergreen/just.sh + - test-numpy + working_dir: src + include_expansions_in_env: + - TOOLCHAIN_VERSION + type: test + # Upload coverage upload coverage: - command: ec2.assume_role diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index cb7ae1e6c9..e3f5492ac9 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -4768,6 +4768,28 @@ tasks: - noauth - pypy + # Test numpy tests + - name: test-numpy-python3.10 + commands: + - func: test numpy + vars: + TOOLCHAIN_VERSION: "3.10" + tags: + - binary + - vector + - python-3.10 + - test-numpy + - name: test-numpy-python3.14 + commands: + - func: test numpy + vars: + TOOLCHAIN_VERSION: "3.14" + tags: + - binary + - vector + - python-3.14 + - test-numpy + # Test standard auth tests - name: test-standard-auth-v4.2-python3.10-auth-ssl-sharded-cluster commands: diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 4efa338991..8261f08236 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -621,3 +621,42 @@ buildvariants: - rhel87-small expansions: STORAGE_ENGINE: inmemory + + # Test numpy tests + - name: test-numpy-rhel8 + tasks: + - name: .test-numpy + display_name: Test Numpy RHEL8 + run_on: + - rhel87-small + tags: [binary, vector, pr] + - name: test-numpy-macos + tasks: + - name: .test-numpy + display_name: Test Numpy macOS + run_on: + - macos-14 + tags: [binary, vector] + - name: test-numpy-macos-arm64 + tasks: + - name: .test-numpy + display_name: Test Numpy macOS Arm64 + run_on: + - macos-14-arm64 + tags: [binary, vector] + - name: test-numpy-win64 + tasks: + - name: .test-numpy + display_name: Test Numpy Win64 + run_on: + - windows-64-vsMulti-small + tags: [binary, vector] + - name: test-numpy-win32 + tasks: + - name: .test-numpy + display_name: Test Numpy Win32 + run_on: + - windows-64-vsMulti-small + expansions: + IS_WIN32: "1" + tags: [binary, vector] diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 26580cfae1..5937d72964 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -339,6 +339,37 @@ def create_disable_test_commands_variants(): return [create_variant(tasks, display_name, host=host, expansions=expansions)] +def create_test_numpy_tasks(): + tasks = [] + for python in MIN_MAX_PYTHON: + tags = ["binary", "vector", f"python-{python}", "test-numpy"] + task_name = get_task_name("test-numpy", python=python) + test_func = FunctionCall(func="test numpy", vars=dict(TOOLCHAIN_VERSION=python)) + tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) + return tasks + + +def create_test_numpy_variants() -> list[BuildVariant]: + variants = [] + base_display_name = "Test Numpy" + + # Test a subset on each of the other platforms. + for host_name in ("rhel8", "macos", "macos-arm64", "win64", "win32"): + tasks = [".test-numpy"] + host = HOSTS[host_name] + tags = ["binary", "vector"] + if host_name == "rhel8": + tags.append("pr") + expansions = dict() + if host_name == "win32": + expansions["IS_WIN32"] = "1" + display_name = get_variant_name(base_display_name, host) + variant = create_variant(tasks, display_name, host=host, tags=tags, expansions=expansions) + variants.append(variant) + + return variants + + def create_oidc_auth_variants(): variants = [] for host_name in ["ubuntu22", "macos", "win64"]: @@ -1140,6 +1171,14 @@ def create_run_tests_func(): return "run tests", [setup_cmd, test_cmd] +def create_test_numpy_func(): + includes = ["TOOLCHAIN_VERSION"] + test_cmd = get_subprocess_exec( + include_expansions_in_env=includes, args=[".evergreen/just.sh", "test-numpy"] + ) + return "test numpy", [test_cmd] + + def create_cleanup_func(): cmd = get_subprocess_exec(args=[".evergreen/scripts/cleanup.sh"]) return "cleanup", [cmd] diff --git a/bson/binary.py b/bson/binary.py index 48eb12b0ac..aa91de4ede 100644 --- a/bson/binary.py +++ b/bson/binary.py @@ -65,6 +65,9 @@ from array import array as _array from mmap import mmap as _mmap + import numpy as np + import numpy.typing as npt + class UuidRepresentation: UNSPECIFIED = 0 @@ -234,13 +237,20 @@ class BinaryVector: __slots__ = ("data", "dtype", "padding") - def __init__(self, data: Sequence[float | int], dtype: BinaryVectorDtype, padding: int = 0): + def __init__( + self, + data: Union[Sequence[float | int], npt.NDArray[np.number]], + dtype: BinaryVectorDtype, + padding: int = 0, + ): """ :param data: Sequence of numbers representing the mathematical vector. :param dtype: The data type stored in binary :param padding: The number of bits in the final byte that are to be ignored when a vector element's size is less than a byte and the length of the vector is not a multiple of 8. + (Padding is equivalent to a negative value of `count` in + `numpy.unpackbits `_) """ self.data = data self.dtype = dtype @@ -424,10 +434,20 @@ def from_vector( ) -> Binary: ... + @classmethod + @overload + def from_vector( + cls: Type[Binary], + vector: npt.NDArray[np.number], + dtype: BinaryVectorDtype, + padding: int = 0, + ) -> Binary: + ... + @classmethod def from_vector( cls: Type[Binary], - vector: Union[BinaryVector, list[int], list[float]], + vector: Union[BinaryVector, list[int], list[float], npt.NDArray[np.number]], dtype: Optional[BinaryVectorDtype] = None, padding: Optional[int] = None, ) -> Binary: @@ -459,34 +479,72 @@ def from_vector( vector = vector.data # type: ignore padding = 0 if padding is None else padding - if dtype == BinaryVectorDtype.INT8: # pack ints in [-128, 127] as signed int8 - format_str = "b" - if padding: - raise ValueError(f"padding does not apply to {dtype=}") - elif dtype == BinaryVectorDtype.PACKED_BIT: # pack ints in [0, 255] as unsigned uint8 - format_str = "B" - if 0 <= padding > 7: - raise ValueError(f"{padding=}. It must be in [0,1, ..7].") - if padding and not vector: - raise ValueError("Empty vector with non-zero padding.") - elif dtype == BinaryVectorDtype.FLOAT32: # pack floats as float32 - format_str = "f" - if padding: - raise ValueError(f"padding does not apply to {dtype=}") - else: - raise NotImplementedError("%s not yet supported" % dtype) - + if not isinstance(dtype, BinaryVectorDtype): + raise TypeError( + "dtype must be a bson.BinaryVectorDtype of BinaryVectorDType.INT8, PACKED_BIT, FLOAT32" + ) metadata = struct.pack(" 7: + raise ValueError(f"{padding=}. It must be in [0,1, ..7].") + if padding and not vector: + raise ValueError("Empty vector with non-zero padding.") + elif dtype == BinaryVectorDtype.FLOAT32: # pack floats as float32 + format_str = "f" + if padding: + raise ValueError(f"padding does not apply to {dtype=}") + else: + raise NotImplementedError("%s not yet supported" % dtype) + data = struct.pack(f"<{len(vector)}{format_str}", *vector) + else: # vector is numpy array or incorrect type. + try: + import numpy as np + except ImportError as exc: + raise ImportError( + "Failed to create binary from vector. Check type. If numpy array, numpy must be installed." + ) from exc + if not isinstance(vector, np.ndarray): + raise TypeError( + "Could not create Binary. Vector must be a BinaryVector, list[int], list[float] or numpy ndarray." + ) + if vector.ndim != 1: + raise ValueError( + "from_numpy_vector only supports 1D arrays as it creates a single vector." + ) + + if dtype == BinaryVectorDtype.FLOAT32: + vector = vector.astype(np.dtype("float32"), copy=False) + elif dtype == BinaryVectorDtype.INT8: + if vector.min() >= -128 and vector.max() <= 127: + vector = vector.astype(np.dtype("int8"), copy=False) + else: + raise ValueError("Values found outside INT8 range.") + elif dtype == BinaryVectorDtype.PACKED_BIT: + if vector.min() >= 0 and vector.max() <= 127: + vector = vector.astype(np.dtype("uint8"), copy=False) + else: + raise ValueError("Values found outside UINT8 range.") + else: + raise NotImplementedError("%s not yet supported" % dtype) + data = vector.tobytes() + if padding and len(vector) and not (data[-1] & ((1 << padding) - 1)) == 0: raise ValueError( "Vector has a padding P, but bits in the final byte lower than P are non-zero. They must be zero." ) return cls(metadata + data, subtype=VECTOR_SUBTYPE) - def as_vector(self) -> BinaryVector: - """From the Binary, create a list of numbers, along with dtype and padding. + def as_vector(self, return_numpy: bool = False) -> BinaryVector: + """From the Binary, create a list or 1-d numpy array of numbers, along with dtype and padding. + :param return_numpy: If True, BinaryVector.data will be a one-dimensional numpy array. By default, it is a list. :return: BinaryVector .. versionadded:: 4.10 @@ -495,54 +553,84 @@ def as_vector(self) -> BinaryVector: if self.subtype != VECTOR_SUBTYPE: raise ValueError(f"Cannot decode subtype {self.subtype} as a vector") - position = 0 - dtype, padding = struct.unpack_from(" 7 or padding < 0: - raise ValueError(f"Corrupt data. Padding ({padding}) must be between 0 and 7.") - dtype_format = "B" - format_string = f"<{n_values}{dtype_format}" - unpacked_uint8s = list(struct.unpack_from(format_string, self, position)) - if padding and n_values and unpacked_uint8s[-1] & (1 << padding) - 1 != 0: - warnings.warn( - "Vector has a padding P, but bits in the final byte lower than P are non-zero. For pymongo>=5.0, they must be zero.", - DeprecationWarning, - stacklevel=2, - ) - return BinaryVector(unpacked_uint8s, dtype, padding) - - else: - raise NotImplementedError("Binary Vector dtype %s not yet supported" % dtype.name) + if not return_numpy: + if dtype == BinaryVectorDtype.INT8: + dtype_format = "b" + format_string = f"<{n_bytes}{dtype_format}" + vector = list(struct.unpack_from(format_string, self, offset)) + return BinaryVector(vector, dtype, padding) + + elif dtype == BinaryVectorDtype.FLOAT32: + n_values = n_bytes // 4 + if n_bytes % 4: + raise ValueError( + "Corrupt data. N bytes for a float32 vector must be a multiple of 4." + ) + dtype_format = "f" + format_string = f"<{n_values}{dtype_format}" + vector = list(struct.unpack_from(format_string, self, offset)) + return BinaryVector(vector, dtype, padding) + + elif dtype == BinaryVectorDtype.PACKED_BIT: + # data packed as uint8 + if padding and not n_bytes: + raise ValueError("Corrupt data. Vector has a padding P, but no data.") + if padding > 7 or padding < 0: + raise ValueError(f"Corrupt data. Padding ({padding}) must be between 0 and 7.") + dtype_format = "B" + format_string = f"<{n_bytes}{dtype_format}" + unpacked_uint8s = list(struct.unpack_from(format_string, self, offset)) + if padding and n_bytes and unpacked_uint8s[-1] & (1 << padding) - 1 != 0: + warnings.warn( + "Vector has a padding P, but bits in the final byte lower than P are non-zero. For pymongo>=5.0, they must be zero.", + DeprecationWarning, + stacklevel=2, + ) + return BinaryVector(unpacked_uint8s, dtype, padding) + + else: + raise NotImplementedError("Binary Vector dtype %s not yet supported" % dtype.name) + else: # create a numpy array + try: + import numpy as np + except ImportError as exc: + raise ImportError( + "Converting binary to numpy.ndarray requires numpy to be installed." + ) from exc + if dtype == BinaryVectorDtype.INT8: + data = np.frombuffer(self[offset:], dtype="int8") + elif dtype == BinaryVectorDtype.FLOAT32: + if n_bytes % 4: + raise ValueError( + "Corrupt data. N bytes for a float32 vector must be a multiple of 4." + ) + data = np.frombuffer(self[offset:], dtype="float32") + elif dtype == BinaryVectorDtype.PACKED_BIT: + # data packed as uint8 + if padding and not n_bytes: + raise ValueError("Corrupt data. Vector has a padding P, but no data.") + if padding > 7 or padding < 0: + raise ValueError(f"Corrupt data. Padding ({padding}) must be between 0 and 7.") + data = np.frombuffer(self[offset:], dtype="uint8") + if padding and np.unpackbits(data[-1])[-padding:].sum() > 0: + warnings.warn( + "Vector has a padding P, but bits in the final byte lower than P are non-zero. For pymongo>=5.0, they must be zero.", + DeprecationWarning, + stacklevel=2, + ) + else: + raise NotImplementedError("Binary Vector dtype %s not yet supported" % dtype.name) + return BinaryVector(data, dtype, padding) @property def subtype(self) -> int: diff --git a/doc/changelog.rst b/doc/changelog.rst index 42efdacba5..e205f2cc12 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -19,6 +19,7 @@ PyMongo 4.16 brings a number of changes including: - Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions. - Fixed return type annotation for ``find_one_and_*`` methods on :class:`~pymongo.asynchronous.collection.AsyncCollection` and :class:`~pymongo.synchronous.collection.Collection` to include ``None``. +- Added support for NumPy 1D-arrays in :class:`bson.binary.BinaryVector`. Changes in Version 4.15.5 (2025/XX/XX) -------------------------------------- diff --git a/justfile b/justfile index 92bdee5be3..082b6ea170 100644 --- a/justfile +++ b/justfile @@ -2,7 +2,7 @@ set shell := ["bash", "-c"] # Commonly used command segments. -typing_run := "uv run --group typing --extra aws --extra encryption --extra ocsp --extra snappy --extra test --extra zstd" +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" doc_build := "./doc/_build" mypy_args := "--install-types --non-interactive" @@ -38,14 +38,14 @@ typing: && resync [group('typing')] typing-mypy: && resync - {{typing_run}} mypy {{mypy_args}} bson gridfs tools pymongo - {{typing_run}} mypy {{mypy_args}} --config-file mypy_test.ini test - {{typing_run}} mypy {{mypy_args}} test/test_typing.py test/test_typing_strict.py + {{typing_run}} python -m mypy {{mypy_args}} bson gridfs tools pymongo + {{typing_run}} python -m mypy {{mypy_args}} --config-file mypy_test.ini test + {{typing_run}} python -m mypy {{mypy_args}} test/test_typing.py test/test_typing_strict.py [group('typing')] typing-pyright: && resync - {{typing_run}} pyright test/test_typing.py test/test_typing_strict.py - {{typing_run}} pyright -p strict_pyrightconfig.json test/test_typing_strict.py + {{typing_run}} python -m pyright test/test_typing.py test/test_typing_strict.py + {{typing_run}} python -m pyright -p strict_pyrightconfig.json test/test_typing_strict.py [group('lint')] lint *args="": && resync @@ -57,7 +57,11 @@ lint-manual *args="": && resync [group('test')] test *args="-v --durations=5 --maxfail=10": && resync - uv run --extra test pytest {{args}} + uv run --extra test python -m pytest {{args}} + +[group('test')] +test-numpy: && resync + uv run --extra test --with numpy python -m pytest test/test_bson.py [group('test')] run-tests *args: && resync diff --git a/test/test_bson.py b/test/test_bson.py index f792db1e89..f49ae680e0 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -19,6 +19,7 @@ import array import collections import datetime +import importlib.util import mmap import os import pickle @@ -71,6 +72,8 @@ from bson.timestamp import Timestamp from bson.tz_util import FixedOffset, utc +_NUMPY_AVAILABLE = importlib.util.find_spec("numpy") is not None + class NotADict(abc.MutableMapping): """Non-dict type that implements the mapping protocol.""" @@ -871,6 +874,65 @@ def test_binaryvector_equality(self): BinaryVector([1], BinaryVectorDtype.INT8), BinaryVector([2], BinaryVectorDtype.INT8) ) + @unittest.skipIf(not _NUMPY_AVAILABLE, "numpy optional-dependency not installed.") + def test_vector_from_numpy(self): + """Follows test_vector except for input type numpy.ndarray""" + # Simple data values could be treated as any of our BinaryVectorDtypes + import numpy as np + + arr = np.array([2, 3]) + # INT8 + binary_vector_int8 = Binary.from_vector(arr, BinaryVectorDtype.INT8) + # as_vector + vector = binary_vector_int8.as_vector() + assert isinstance(vector, BinaryVector) + assert vector.data == arr.tolist() + # as_numpy_vector + vector_np = binary_vector_int8.as_vector(return_numpy=True) + assert isinstance(vector_np, BinaryVector) + assert isinstance(vector_np.data, np.ndarray) + assert np.all(vector.data == arr) + # PACKED_BIT + binary_vector_uint8 = Binary.from_vector(arr, BinaryVectorDtype.PACKED_BIT) + # as_vector + vector = binary_vector_uint8.as_vector() + assert isinstance(vector, BinaryVector) + assert vector.data == arr.tolist() + # as_numpy_vector + vector_np = binary_vector_uint8.as_vector(return_numpy=True) + assert isinstance(vector_np, BinaryVector) + assert isinstance(vector_np.data, np.ndarray) + assert np.all(vector_np.data == arr) + # FLOAT32 + binary_vector_float32 = Binary.from_vector(arr, BinaryVectorDtype.FLOAT32) + # as_vector + vector = binary_vector_float32.as_vector() + assert isinstance(vector, BinaryVector) + assert vector.data == arr.tolist() + # as_numpy_vector + vector_np = binary_vector_float32.as_vector(return_numpy=True) + assert isinstance(vector_np, BinaryVector) + assert isinstance(vector_np.data, np.ndarray) + assert np.all(vector_np.data == arr) + + # Invalid cases + with self.assertRaises(ValueError): + Binary.from_vector(np.array([-1]), BinaryVectorDtype.PACKED_BIT) + with self.assertRaises(ValueError): + Binary.from_vector(np.array([128]), BinaryVectorDtype.PACKED_BIT) + with self.assertRaises(ValueError): + Binary.from_vector(np.array([-198]), BinaryVectorDtype.INT8) + + # Unexpected cases + # Creating a vector of INT8 from a list of doubles will be caught by struct.pack + # Numpy's default behavior is to cast to the type requested. + list_floats = [-1.1, 1.1] + cast_bin = Binary.from_vector(np.array(list_floats), BinaryVectorDtype.INT8) + vector = cast_bin.as_vector() + vector_np = cast_bin.as_vector(return_numpy=True) + assert vector.data != list_floats + assert vector.data == vector_np.data.tolist() == [-1, 1] + def test_unicode_regex(self): """Tests we do not get a segfault for C extension on unicode RegExs. This had been happening. From e7aab567bf0d2e5a7240ae34abc77ab26072d1f1 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 10 Dec 2025 09:06:49 -0600 Subject: [PATCH 028/129] PYTHON-4783 Remove reference to RHEL7 in tests (#2643) --- .evergreen/scripts/generate_config_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index a32092f5b2..e098ff8008 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -63,7 +63,6 @@ class Host: HOSTS["macos-arm64"] = Host("macos-arm64", "macos-14-arm64", "macOS Arm64", dict()) HOSTS["ubuntu20"] = Host("ubuntu20", "ubuntu2004-small", "Ubuntu-20", dict()) HOSTS["ubuntu22"] = Host("ubuntu22", "ubuntu2204-small", "Ubuntu-22", dict()) -HOSTS["rhel7"] = Host("rhel7", "rhel79-small", "RHEL7", dict()) HOSTS["perf"] = Host("perf", "rhel90-dbx-perf-large", "", dict()) HOSTS["debian11"] = Host("debian11", "debian11-small", "Debian11", dict()) DEFAULT_HOST = HOSTS["rhel8"] From 49e59d41b2937e26960dacc643b9b6957f0eaeed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:37:29 -0600 Subject: [PATCH 029/129] PYTHON-5661 Bump mypy from 1.18.2 to 1.19.0 (#2629) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester Co-authored-by: Steven Silvester --- pymongo/asynchronous/topology.py | 9 +- pymongo/synchronous/topology.py | 9 +- pyproject.toml | 2 +- test/__init__.py | 8 +- test/asynchronous/__init__.py | 8 +- test/asynchronous/test_change_stream.py | 8 +- test/asynchronous/test_encryption.py | 2 +- test/test_change_stream.py | 8 +- test/test_encryption.py | 2 +- uv.lock | 166 ++++++++++++++++++------ 10 files changed, 160 insertions(+), 62 deletions(-) diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index 283aabc690..f7b1c8d91c 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -111,7 +111,7 @@ def __init__(self, topology_settings: TopologySettings): self._publish_tp = self._listeners is not None and self._listeners.enabled_for_topology # Create events queue if there are publishers. - self._events = None + self._events: queue.Queue[Any] | None = None self.__events_executor: Any = None if self._publish_server or self._publish_tp: @@ -126,6 +126,7 @@ def __init__(self, topology_settings: TopologySettings): if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._events.put((self._listeners.publish_topology_opened, (self._topology_id,))) self._settings = topology_settings topology_description = TopologyDescription( @@ -143,6 +144,7 @@ def __init__(self, topology_settings: TopologySettings): ) if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_topology_description_changed, @@ -161,6 +163,7 @@ def __init__(self, topology_settings: TopologySettings): for seed in topology_settings.seeds: if self._publish_server: assert self._events is not None + assert self._listeners is not None self._events.put((self._listeners.publish_server_opened, (seed, self._topology_id))) if _SDAM_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( @@ -491,6 +494,7 @@ async def _process_change( suppress_event = sd_old == server_description if self._publish_server and not suppress_event: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_server_description_changed, @@ -503,6 +507,7 @@ async def _process_change( if self._publish_tp and not suppress_event: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_topology_description_changed, @@ -570,6 +575,7 @@ async def _process_srv_update(self, seedlist: list[tuple[str, Any]]) -> None: if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_topology_description_changed, @@ -723,6 +729,7 @@ async def close(self) -> None: # Publish only after releasing the lock. if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._description = TopologyDescription( TOPOLOGY_TYPE.Unknown, {}, diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index a4ca0e6e0f..38acf67780 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -111,7 +111,7 @@ def __init__(self, topology_settings: TopologySettings): self._publish_tp = self._listeners is not None and self._listeners.enabled_for_topology # Create events queue if there are publishers. - self._events = None + self._events: queue.Queue[Any] | None = None self.__events_executor: Any = None if self._publish_server or self._publish_tp: @@ -126,6 +126,7 @@ def __init__(self, topology_settings: TopologySettings): if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._events.put((self._listeners.publish_topology_opened, (self._topology_id,))) self._settings = topology_settings topology_description = TopologyDescription( @@ -143,6 +144,7 @@ def __init__(self, topology_settings: TopologySettings): ) if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_topology_description_changed, @@ -161,6 +163,7 @@ def __init__(self, topology_settings: TopologySettings): for seed in topology_settings.seeds: if self._publish_server: assert self._events is not None + assert self._listeners is not None self._events.put((self._listeners.publish_server_opened, (seed, self._topology_id))) if _SDAM_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( @@ -491,6 +494,7 @@ def _process_change( suppress_event = sd_old == server_description if self._publish_server and not suppress_event: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_server_description_changed, @@ -503,6 +507,7 @@ def _process_change( if self._publish_tp and not suppress_event: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_topology_description_changed, @@ -570,6 +575,7 @@ def _process_srv_update(self, seedlist: list[tuple[str, Any]]) -> None: if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._events.put( ( self._listeners.publish_topology_description_changed, @@ -721,6 +727,7 @@ def close(self) -> None: # Publish only after releasing the lock. if self._publish_tp: assert self._events is not None + assert self._listeners is not None self._description = TopologyDescription( TOPOLOGY_TYPE.Unknown, {}, diff --git a/pyproject.toml b/pyproject.toml index a76189c57e..9c2832c09e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ mockupdb = [ ] perf = ["simplejson>=3.17.0"] typing = [ - "mypy==1.18.2", + "mypy==1.19.0", "pyright==1.1.407", "typing_extensions", "pip" diff --git a/test/__init__.py b/test/__init__.py index 1ee2c283d6..8540c442e0 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -482,7 +482,7 @@ def create_user(self, dbname, user, pwd=None, roles=None, **kwargs): def drop_user(self, dbname, user): self.client[dbname].command("dropUser", user, writeConcern={"w": self.w}) - def require_connection(self, func): + def require_connection(self, func: Any) -> Any: """Run a test only if we can connect to MongoDB.""" return self._require( lambda: True, # _require checks if we're connected @@ -552,7 +552,7 @@ def require_no_fips(self, func): lambda: not self.fips_enabled, "Test cannot run on a FIPS-enabled host", func=func ) - def require_replica_set(self, func): + def require_replica_set(self, func: Any) -> Any: """Run a test only if the client is connected to a replica set.""" return self._require(lambda: self.is_rs, "Not connected to a replica set", func=func) @@ -638,7 +638,7 @@ def require_load_balancer(self, func): lambda: self.load_balancer, "Must be connected to a load balancer", func=func ) - def require_no_load_balancer(self, func): + def require_no_load_balancer(self, func: Any) -> Any: """Run a test only if the client is not connected to a load balancer.""" return self._require( lambda: not self.load_balancer, "Must not be connected to a load balancer", func=func @@ -687,7 +687,7 @@ def require_test_commands(self, func): lambda: self.test_commands_enabled, "Test commands must be enabled", func=func ) - def require_failCommand_fail_point(self, func): + def require_failCommand_fail_point(self, func: Any) -> Any: """Run a test only if the server supports the failCommand fail point. """ diff --git a/test/asynchronous/__init__.py b/test/asynchronous/__init__.py index 78d0576add..4dde0acf1f 100644 --- a/test/asynchronous/__init__.py +++ b/test/asynchronous/__init__.py @@ -482,7 +482,7 @@ async def create_user(self, dbname, user, pwd=None, roles=None, **kwargs): async def drop_user(self, dbname, user): await self.client[dbname].command("dropUser", user, writeConcern={"w": self.w}) - def require_connection(self, func): + def require_connection(self, func: Any) -> Any: """Run a test only if we can connect to MongoDB.""" return self._require( lambda: True, # _require checks if we're connected @@ -552,7 +552,7 @@ def require_no_fips(self, func): lambda: not self.fips_enabled, "Test cannot run on a FIPS-enabled host", func=func ) - def require_replica_set(self, func): + def require_replica_set(self, func: Any) -> Any: """Run a test only if the client is connected to a replica set.""" return self._require(lambda: self.is_rs, "Not connected to a replica set", func=func) @@ -638,7 +638,7 @@ def require_load_balancer(self, func): lambda: self.load_balancer, "Must be connected to a load balancer", func=func ) - def require_no_load_balancer(self, func): + def require_no_load_balancer(self, func: Any) -> Any: """Run a test only if the client is not connected to a load balancer.""" return self._require( lambda: not self.load_balancer, "Must not be connected to a load balancer", func=func @@ -687,7 +687,7 @@ def require_test_commands(self, func): lambda: self.test_commands_enabled, "Test commands must be enabled", func=func ) - def require_failCommand_fail_point(self, func): + def require_failCommand_fail_point(self, func: Any) -> Any: """Run a test only if the server supports the failCommand fail point. """ diff --git a/test/asynchronous/test_change_stream.py b/test/asynchronous/test_change_stream.py index 3fb8b517f3..89a215f143 100644 --- a/test/asynchronous/test_change_stream.py +++ b/test/asynchronous/test_change_stream.py @@ -771,8 +771,8 @@ async def test_split_large_change(self): class TestClusterAsyncChangeStream(TestAsyncChangeStreamBase, APITestsMixin): dbs: list - @async_client_context.require_version_min(4, 2, 0) - @async_client_context.require_change_streams + @async_client_context.require_version_min(4, 2, 0) # type:ignore[untyped-decorator] + @async_client_context.require_change_streams # type:ignore[untyped-decorator] async def asyncSetUp(self) -> None: await super().asyncSetUp() self.dbs = [self.db, self.client.pymongo_test_2] @@ -831,8 +831,8 @@ async def test_full_pipeline(self): class TestAsyncDatabaseAsyncChangeStream(TestAsyncChangeStreamBase, APITestsMixin): - @async_client_context.require_version_min(4, 2, 0) - @async_client_context.require_change_streams + @async_client_context.require_version_min(4, 2, 0) # type:ignore[untyped-decorator] + @async_client_context.require_change_streams # type:ignore[untyped-decorator] async def asyncSetUp(self) -> None: await super().asyncSetUp() diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 74c0136ad0..2b4c28d09d 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -233,7 +233,7 @@ class AsyncEncryptionIntegrationTest(AsyncIntegrationTest): """Base class for encryption integration tests.""" @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - @async_client_context.require_version_min(4, 2, -1) + @async_client_context.require_version_min(4, 2, -1) # type:ignore[untyped-decorator] async def asyncSetUp(self) -> None: await super().asyncSetUp() diff --git a/test/test_change_stream.py b/test/test_change_stream.py index ad51f91873..0b2a9e76b8 100644 --- a/test/test_change_stream.py +++ b/test/test_change_stream.py @@ -757,8 +757,8 @@ def test_split_large_change(self): class TestClusterChangeStream(TestChangeStreamBase, APITestsMixin): dbs: list - @client_context.require_version_min(4, 2, 0) - @client_context.require_change_streams + @client_context.require_version_min(4, 2, 0) # type:ignore[untyped-decorator] + @client_context.require_change_streams # type:ignore[untyped-decorator] def setUp(self) -> None: super().setUp() self.dbs = [self.db, self.client.pymongo_test_2] @@ -817,8 +817,8 @@ def test_full_pipeline(self): class TestDatabaseChangeStream(TestChangeStreamBase, APITestsMixin): - @client_context.require_version_min(4, 2, 0) - @client_context.require_change_streams + @client_context.require_version_min(4, 2, 0) # type:ignore[untyped-decorator] + @client_context.require_change_streams # type:ignore[untyped-decorator] def setUp(self) -> None: super().setUp() diff --git a/test/test_encryption.py b/test/test_encryption.py index 04e61b7bad..9e165e6f54 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -233,7 +233,7 @@ class EncryptionIntegrationTest(IntegrationTest): """Base class for encryption integration tests.""" @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - @client_context.require_version_min(4, 2, -1) + @client_context.require_version_min(4, 2, -1) # type:ignore[untyped-decorator] def setUp(self) -> None: super().setUp() diff --git a/uv.lock b/uv.lock index d31990880c..a182e983b7 100644 --- a/uv.lock +++ b/uv.lock @@ -969,6 +969,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "librt" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/66/79a14e672256ef58144a24eb49adb338ec02de67ff4b45320af6504682ab/librt-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2682162855a708e3270eba4b92026b93f8257c3e65278b456c77631faf0f4f7a", size = 54707, upload-time = "2025-12-06T19:03:10.881Z" }, + { url = "https://files.pythonhosted.org/packages/58/fa/b709c65a9d5eab85f7bcfe0414504d9775aaad6e78727a0327e175474caa/librt-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:440c788f707c061d237c1e83edf6164ff19f5c0f823a3bf054e88804ebf971ec", size = 56670, upload-time = "2025-12-06T19:03:12.107Z" }, + { url = "https://files.pythonhosted.org/packages/3a/56/0685a0772ec89ddad4c00e6b584603274c3d818f9a68e2c43c4eb7b39ee9/librt-0.7.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399938edbd3d78339f797d685142dd8a623dfaded023cf451033c85955e4838a", size = 161045, upload-time = "2025-12-06T19:03:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d9/863ada0c5ce48aefb89df1555e392b2209fcb6daee4c153c031339b9a89b/librt-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1975eda520957c6e0eb52d12968dd3609ffb7eef05d4223d097893d6daf1d8a7", size = 169532, upload-time = "2025-12-06T19:03:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/a0/71da6c8724fd16c31749905ef1c9e11de206d9301b5be984bf2682b4efb3/librt-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9da128d0edf990cf0d2ca011b02cd6f639e79286774bd5b0351245cbb5a6e51", size = 183277, upload-time = "2025-12-06T19:03:16.446Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/9c97bf2f8338ba1914de233ea312bba2bbd7c59f43f807b3e119796bab18/librt-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19acfde38cb532a560b98f473adc741c941b7a9bc90f7294bc273d08becb58b", size = 179045, upload-time = "2025-12-06T19:03:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/ceea067f489e904cb4ddcca3c9b06ba20229bc3fa7458711e24a5811f162/librt-0.7.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b4f57f7a0c65821c5441d98c47ff7c01d359b1e12328219709bdd97fdd37f90", size = 173521, upload-time = "2025-12-06T19:03:19.17Z" }, + { url = "https://files.pythonhosted.org/packages/7a/41/6cb18f5da9c89ed087417abb0127a445a50ad4eaf1282ba5b52588187f47/librt-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:256793988bff98040de23c57cf36e1f4c2f2dc3dcd17537cdac031d3b681db71", size = 193592, upload-time = "2025-12-06T19:03:20.637Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3c/fcef208746584e7c78584b7aedc617130c4a4742cb8273361bbda8b183b5/librt-0.7.3-cp310-cp310-win32.whl", hash = "sha256:fcb72249ac4ea81a7baefcbff74df7029c3cb1cf01a711113fa052d563639c9c", size = 47201, upload-time = "2025-12-06T19:03:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bf/d8a6c35d1b2b789a4df9b3ddb1c8f535ea373fde2089698965a8f0d62138/librt-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4887c29cadbdc50640179e3861c276325ff2986791e6044f73136e6e798ff806", size = 54371, upload-time = "2025-12-06T19:03:23.231Z" }, + { url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" }, + { url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" }, + { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, + { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, + { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, + { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, + { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, + { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" }, + { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" }, + { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" }, + { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" }, + { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" }, + { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" }, + { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" }, + { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/e1/70/b3f19e3bb34f44e218c8271dc0b2b14eb6b183fbccbececf94c71e2b5e69/librt-0.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd8551aa21df6c60baa2624fd086ae7486bdde00c44097b32e1d1b1966e365e0", size = 54850, upload-time = "2025-12-06T19:04:32.742Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/6599ed7726aaa9b5bacea206d5861b94e76866240e2f394a59594bf3db46/librt-0.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6eb9295c730e26b849ed1f4022735f36863eb46b14b6e10604c1c39b8b5efaea", size = 56797, upload-time = "2025-12-06T19:04:34.193Z" }, + { url = "https://files.pythonhosted.org/packages/33/83/216db13224a6f688787f456909bbc50f9d951c0f4bea8ba38a2eb931d581/librt-0.7.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3edbf257c40d21a42615e9e332a6b10a8bacaaf58250aed8552a14a70efd0d65", size = 159681, upload-time = "2025-12-06T19:04:35.554Z" }, + { url = "https://files.pythonhosted.org/packages/83/23/0a490c8ba3bc90090647ac7b9b3c63c16af7378bcabe3ff4c7d7890d66e5/librt-0.7.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b29e97273bd6999e2bfe9fe3531b1f4f64effd28327bced048a33e49b99674a", size = 168505, upload-time = "2025-12-06T19:04:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/5e/16/b47c60805285caa06728d61d933fdd6db5b7321f375ce496cb7fdbeb1a44/librt-0.7.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e40520c37926166c24d0c2e0f3bc3a5f46646c34bdf7b4ea9747c297d6ee809", size = 182234, upload-time = "2025-12-06T19:04:37.889Z" }, + { url = "https://files.pythonhosted.org/packages/2d/2f/bef211d7f0d55fa2484d2c644b2cdae8c9c5eec050754b0516e6582ad452/librt-0.7.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6bdd9adfca615903578d2060ee8a6eb1c24eaf54919ff0ddc820118e5718931b", size = 178276, upload-time = "2025-12-06T19:04:39.408Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/5a3e7762b086b62fabb31fd4deaaf3ba888cfdd3b8f2e3247f076c18a6ff/librt-0.7.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f57aca20e637750a2c18d979f7096e2c2033cc40cf7ed201494318de1182f135", size = 172602, upload-time = "2025-12-06T19:04:40.619Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/533d5bfd5b377eb03ed54101814b530fc1f9bbe0e79971c641a3f15bfb33/librt-0.7.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cad9971881e4fec00d96af7eaf4b63aa7a595696fc221808b0d3ce7ca9743258", size = 192741, upload-time = "2025-12-06T19:04:41.738Z" }, + { url = "https://files.pythonhosted.org/packages/9f/69/0b87ce8e95f65ebc864f390f1139b8fe9fac6fb64b797307447b1719610c/librt-0.7.3-cp39-cp39-win32.whl", hash = "sha256:170cdb8436188347af17bf9cccf3249ba581c933ed56d926497119d4cf730cec", size = 47154, upload-time = "2025-12-06T19:04:42.96Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/070dee0add2d6e742be4d8b965d5a37c24562b43e8ef7deba8ed5b5d3c0f/librt-0.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:b278a9248a4e3260fee3db7613772ca9ab6763a129d6d6f29555e2f9b168216d", size = 54339, upload-time = "2025-12-06T19:04:44.415Z" }, +] + [[package]] name = "markupsafe" version = "3.0.2" @@ -1047,53 +1130,54 @@ dependencies = [ [[package]] name = "mypy" -version = "1.18.2" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, - { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, - { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, - { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, - { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, - { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, - { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, - { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, - { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, - { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, - { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, - { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, - { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, - { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, - { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, - { url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" }, - { url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" }, - { url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" }, - { url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" }, - { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" }, + { url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, + { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, + { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, + { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, + { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/b4/59/a7748ef43446163a93159d82bb270c6c4f3d94c1fcbdd2a29a7e439e74d7/mypy-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0dde5cb375cb94deff0d4b548b993bec52859d1651e073d63a1386d392a95495", size = 13094255, upload-time = "2025-11-28T15:47:14.282Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0b/92ebf5abc83f559a35dcba3bd9227726b04b04178f1e521f38e647b930eb/mypy-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1cf9c59398db1c68a134b0b5354a09a1e124523f00bacd68e553b8bd16ff3299", size = 12161414, upload-time = "2025-11-28T15:45:03.302Z" }, + { url = "https://files.pythonhosted.org/packages/aa/03/19412f0a786722055a52c01b4c5d71e5b5443a89f6bbcdd445408240e217/mypy-1.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3210d87b30e6af9c8faed61be2642fcbe60ef77cec64fa1ef810a630a4cf671c", size = 12756782, upload-time = "2025-11-28T15:46:49.522Z" }, + { url = "https://files.pythonhosted.org/packages/cb/85/395d53c9098b251414b0448cdadcd3277523ff36f5abda6d26ff945dbdb3/mypy-1.19.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2c1101ab41d01303103ab6ef82cbbfedb81c1a060c868fa7cc013d573d37ab5", size = 13503492, upload-time = "2025-11-28T15:48:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/dd/33/1ab1113e3778617ae7aba66b4b537f90512bd279ff65b6c984fb91fbb2d3/mypy-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ea4fd21bb48f0da49e6d3b37ef6bd7e8228b9fe41bbf4d80d9364d11adbd43c", size = 13787703, upload-time = "2025-11-28T15:48:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2d/8b0821b3e0d538de1ad96c86502256c7326274d5cb74e0b373efaada273f/mypy-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:16f76ff3f3fd8137aadf593cb4607d82634fca675e8211ad75c43d86033ee6c6", size = 10049225, upload-time = "2025-11-28T15:45:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, ] [[package]] @@ -1307,7 +1391,7 @@ mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mo perf = [{ name = "simplejson", specifier = ">=3.17.0" }] pip = [{ name = "pip" }] typing = [ - { name = "mypy", specifier = "==1.18.2" }, + { name = "mypy", specifier = "==1.19.0" }, { name = "pip" }, { name = "pyright", specifier = "==1.1.407" }, { name = "typing-extensions" }, From ae88b5a08f6d8a99a04195c83cc16eaf12e02017 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 10 Dec 2025 13:40:24 -0600 Subject: [PATCH 030/129] PYTHON-5530 Reduce usage of legacy test runner (#2642) --- test/asynchronous/test_encryption.py | 121 ------------------------- test/asynchronous/test_transactions.py | 12 +-- test/test_encryption.py | 121 ------------------------- test/test_transactions.py | 12 +-- 4 files changed, 4 insertions(+), 262 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 2b4c28d09d..9cd0944ce7 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -33,7 +33,6 @@ from test.asynchronous import AsyncIntegrationTest, AsyncPyMongoTestCase, async_client_context from test.asynchronous.test_bulk import AsyncBulkTestBase from test.asynchronous.utils import flaky -from test.asynchronous.utils_spec_runner import AsyncSpecRunner, AsyncSpecTestCreator from threading import Thread from typing import Any, Dict, Mapping, Optional @@ -55,7 +54,6 @@ ) from test.asynchronous.test_bulk import AsyncBulkTestBase from test.asynchronous.unified_format import generate_test_classes -from test.asynchronous.utils_spec_runner import AsyncSpecRunner from test.helpers_shared import ( ALL_KMS_PROVIDERS, AWS_CREDS, @@ -624,125 +622,6 @@ async def test_with_statement(self): } -class AsyncTestSpec(AsyncSpecRunner): - @classmethod - @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - async def _setup_class(cls): - await super()._setup_class() - - def parse_auto_encrypt_opts(self, opts): - """Parse clientOptions.autoEncryptOpts.""" - opts = camel_to_snake_args(opts) - kms_providers = opts["kms_providers"] - if "aws" in kms_providers: - kms_providers["aws"] = AWS_CREDS - if not any(AWS_CREDS.values()): - self.skipTest("AWS environment credentials are not set") - if "awsTemporary" in kms_providers: - kms_providers["aws"] = AWS_TEMP_CREDS - del kms_providers["awsTemporary"] - if not any(AWS_TEMP_CREDS.values()): - self.skipTest("AWS Temp environment credentials are not set") - if "awsTemporaryNoSessionToken" in kms_providers: - kms_providers["aws"] = AWS_TEMP_NO_SESSION_CREDS - del kms_providers["awsTemporaryNoSessionToken"] - if not any(AWS_TEMP_NO_SESSION_CREDS.values()): - self.skipTest("AWS Temp environment credentials are not set") - if "azure" in kms_providers: - kms_providers["azure"] = AZURE_CREDS - if not any(AZURE_CREDS.values()): - self.skipTest("Azure environment credentials are not set") - if "gcp" in kms_providers: - kms_providers["gcp"] = GCP_CREDS - if not any(AZURE_CREDS.values()): - self.skipTest("GCP environment credentials are not set") - if "kmip" in kms_providers: - kms_providers["kmip"] = KMIP_CREDS - opts["kms_tls_options"] = DEFAULT_KMS_TLS - if "key_vault_namespace" not in opts: - opts["key_vault_namespace"] = "keyvault.datakeys" - if "extra_options" in opts: - opts.update(camel_to_snake_args(opts.pop("extra_options"))) - - opts = dict(opts) - return AutoEncryptionOpts(**opts) - - def parse_client_options(self, opts): - """Override clientOptions parsing to support autoEncryptOpts.""" - encrypt_opts = opts.pop("autoEncryptOpts", None) - if encrypt_opts: - opts["auto_encryption_opts"] = self.parse_auto_encrypt_opts(encrypt_opts) - - return super().parse_client_options(opts) - - def get_object_name(self, op): - """Default object is collection.""" - return op.get("object", "collection") - - def maybe_skip_scenario(self, test): - super().maybe_skip_scenario(test) - desc = test["description"].lower() - if ( - "timeoutms applied to listcollections to get collection schema" in desc - and sys.platform in ("win32", "darwin") - ): - self.skipTest("PYTHON-3706 flaky test on Windows/macOS") - if "type=symbol" in desc: - self.skipTest("PyMongo does not support the symbol type") - if "timeoutms applied to listcollections to get collection schema" in desc and not _IS_SYNC: - self.skipTest("PYTHON-4844 flaky test on async") - - async def setup_scenario(self, scenario_def): - """Override a test's setup.""" - key_vault_data = scenario_def["key_vault_data"] - encrypted_fields = scenario_def["encrypted_fields"] - json_schema = scenario_def["json_schema"] - data = scenario_def["data"] - coll = async_client_context.client.get_database("keyvault", codec_options=OPTS)["datakeys"] - await coll.delete_many({}) - if key_vault_data: - await coll.insert_many(key_vault_data) - - db_name = self.get_scenario_db_name(scenario_def) - coll_name = self.get_scenario_coll_name(scenario_def) - db = async_client_context.client.get_database(db_name, codec_options=OPTS) - await db.drop_collection(coll_name, encrypted_fields=encrypted_fields) - wc = WriteConcern(w="majority") - kwargs: Dict[str, Any] = {} - if json_schema: - kwargs["validator"] = {"$jsonSchema": json_schema} - kwargs["codec_options"] = OPTS - if not data: - kwargs["write_concern"] = wc - if encrypted_fields: - kwargs["encryptedFields"] = encrypted_fields - await db.create_collection(coll_name, **kwargs) - coll = db[coll_name] - if data: - # Load data. - await coll.with_options(write_concern=wc).insert_many(scenario_def["data"]) - - def allowable_errors(self, op): - """Override expected error classes.""" - errors = super().allowable_errors(op) - # An updateOne test expects encryption to error when no $ operator - # appears but pymongo raises a client side ValueError in this case. - if op["name"] == "updateOne": - errors += (ValueError,) - return errors - - -def create_test(scenario_def, test, name): - @async_client_context.require_test_commands - async def run_scenario(self): - await self.run_scenario(scenario_def, test) - - return run_scenario - - -test_creator = AsyncSpecTestCreator(create_test, AsyncTestSpec, os.path.join(SPEC_PATH, "legacy")) -test_creator.create_tests() - if _HAVE_PYMONGOCRYPT: globals().update( generate_test_classes( diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 29c5d26423..23f121fafd 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -18,7 +18,6 @@ import asyncio import sys from io import BytesIO -from test.asynchronous.utils_spec_runner import AsyncSpecRunner from gridfs.asynchronous.grid_file import AsyncGridFS, AsyncGridFSBucket from pymongo.asynchronous.pool import PoolState @@ -61,15 +60,8 @@ UNPIN_TEST_MAX_ATTEMPTS = 50 -class AsyncTransactionsBase(AsyncSpecRunner): - def maybe_skip_scenario(self, test): - super().maybe_skip_scenario(test) - if ( - "secondary" in self.id() - and not async_client_context.is_mongos - and not async_client_context.has_secondaries - ): - raise unittest.SkipTest("No secondaries") +class AsyncTransactionsBase(AsyncIntegrationTest): + pass class TestTransactions(AsyncTransactionsBase): diff --git a/test/test_encryption.py b/test/test_encryption.py index 9e165e6f54..b0f046d63f 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -33,7 +33,6 @@ from test import IntegrationTest, PyMongoTestCase, client_context from test.test_bulk import BulkTestBase from test.utils import flaky -from test.utils_spec_runner import SpecRunner, SpecTestCreator from threading import Thread from typing import Any, Dict, Mapping, Optional @@ -75,7 +74,6 @@ is_greenthread_patched, wait_until, ) -from test.utils_spec_runner import SpecRunner from bson import BSON, DatetimeMS, Decimal128, encode, json_util from bson.binary import UUID_SUBTYPE, Binary, UuidRepresentation @@ -622,125 +620,6 @@ def test_with_statement(self): } -class TestSpec(SpecRunner): - @classmethod - @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - def _setup_class(cls): - super()._setup_class() - - def parse_auto_encrypt_opts(self, opts): - """Parse clientOptions.autoEncryptOpts.""" - opts = camel_to_snake_args(opts) - kms_providers = opts["kms_providers"] - if "aws" in kms_providers: - kms_providers["aws"] = AWS_CREDS - if not any(AWS_CREDS.values()): - self.skipTest("AWS environment credentials are not set") - if "awsTemporary" in kms_providers: - kms_providers["aws"] = AWS_TEMP_CREDS - del kms_providers["awsTemporary"] - if not any(AWS_TEMP_CREDS.values()): - self.skipTest("AWS Temp environment credentials are not set") - if "awsTemporaryNoSessionToken" in kms_providers: - kms_providers["aws"] = AWS_TEMP_NO_SESSION_CREDS - del kms_providers["awsTemporaryNoSessionToken"] - if not any(AWS_TEMP_NO_SESSION_CREDS.values()): - self.skipTest("AWS Temp environment credentials are not set") - if "azure" in kms_providers: - kms_providers["azure"] = AZURE_CREDS - if not any(AZURE_CREDS.values()): - self.skipTest("Azure environment credentials are not set") - if "gcp" in kms_providers: - kms_providers["gcp"] = GCP_CREDS - if not any(AZURE_CREDS.values()): - self.skipTest("GCP environment credentials are not set") - if "kmip" in kms_providers: - kms_providers["kmip"] = KMIP_CREDS - opts["kms_tls_options"] = DEFAULT_KMS_TLS - if "key_vault_namespace" not in opts: - opts["key_vault_namespace"] = "keyvault.datakeys" - if "extra_options" in opts: - opts.update(camel_to_snake_args(opts.pop("extra_options"))) - - opts = dict(opts) - return AutoEncryptionOpts(**opts) - - def parse_client_options(self, opts): - """Override clientOptions parsing to support autoEncryptOpts.""" - encrypt_opts = opts.pop("autoEncryptOpts", None) - if encrypt_opts: - opts["auto_encryption_opts"] = self.parse_auto_encrypt_opts(encrypt_opts) - - return super().parse_client_options(opts) - - def get_object_name(self, op): - """Default object is collection.""" - return op.get("object", "collection") - - def maybe_skip_scenario(self, test): - super().maybe_skip_scenario(test) - desc = test["description"].lower() - if ( - "timeoutms applied to listcollections to get collection schema" in desc - and sys.platform in ("win32", "darwin") - ): - self.skipTest("PYTHON-3706 flaky test on Windows/macOS") - if "type=symbol" in desc: - self.skipTest("PyMongo does not support the symbol type") - if "timeoutms applied to listcollections to get collection schema" in desc and not _IS_SYNC: - self.skipTest("PYTHON-4844 flaky test on async") - - def setup_scenario(self, scenario_def): - """Override a test's setup.""" - key_vault_data = scenario_def["key_vault_data"] - encrypted_fields = scenario_def["encrypted_fields"] - json_schema = scenario_def["json_schema"] - data = scenario_def["data"] - coll = client_context.client.get_database("keyvault", codec_options=OPTS)["datakeys"] - coll.delete_many({}) - if key_vault_data: - coll.insert_many(key_vault_data) - - db_name = self.get_scenario_db_name(scenario_def) - coll_name = self.get_scenario_coll_name(scenario_def) - db = client_context.client.get_database(db_name, codec_options=OPTS) - db.drop_collection(coll_name, encrypted_fields=encrypted_fields) - wc = WriteConcern(w="majority") - kwargs: Dict[str, Any] = {} - if json_schema: - kwargs["validator"] = {"$jsonSchema": json_schema} - kwargs["codec_options"] = OPTS - if not data: - kwargs["write_concern"] = wc - if encrypted_fields: - kwargs["encryptedFields"] = encrypted_fields - db.create_collection(coll_name, **kwargs) - coll = db[coll_name] - if data: - # Load data. - coll.with_options(write_concern=wc).insert_many(scenario_def["data"]) - - def allowable_errors(self, op): - """Override expected error classes.""" - errors = super().allowable_errors(op) - # An updateOne test expects encryption to error when no $ operator - # appears but pymongo raises a client side ValueError in this case. - if op["name"] == "updateOne": - errors += (ValueError,) - return errors - - -def create_test(scenario_def, test, name): - @client_context.require_test_commands - def run_scenario(self): - self.run_scenario(scenario_def, test) - - return run_scenario - - -test_creator = SpecTestCreator(create_test, TestSpec, os.path.join(SPEC_PATH, "legacy")) -test_creator.create_tests() - if _HAVE_PYMONGOCRYPT: globals().update( generate_test_classes( diff --git a/test/test_transactions.py b/test/test_transactions.py index 37e1a249e0..feefcada2a 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -18,7 +18,6 @@ import asyncio import sys from io import BytesIO -from test.utils_spec_runner import SpecRunner from gridfs.synchronous.grid_file import GridFS, GridFSBucket from pymongo.server_selectors import writable_server_selector @@ -61,15 +60,8 @@ UNPIN_TEST_MAX_ATTEMPTS = 50 -class TransactionsBase(SpecRunner): - def maybe_skip_scenario(self, test): - super().maybe_skip_scenario(test) - if ( - "secondary" in self.id() - and not client_context.is_mongos - and not client_context.has_secondaries - ): - raise unittest.SkipTest("No secondaries") +class TransactionsBase(IntegrationTest): + pass class TestTransactions(TransactionsBase): From ab8b99a00546bf873963285e3aabd09b9d0fe525 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:49:27 -0800 Subject: [PATCH 031/129] [Spec Resync] 12-01-2025 (#2632) Co-authored-by: Cloud User Co-authored-by: Jeffrey A. Clark Co-authored-by: Iris Ho --- .evergreen/spec-patch/PYTHON-5517.patch | 473 ++++++++++++++++++++++++ CONTRIBUTING.md | 1 + 2 files changed, 474 insertions(+) create mode 100644 .evergreen/spec-patch/PYTHON-5517.patch diff --git a/.evergreen/spec-patch/PYTHON-5517.patch b/.evergreen/spec-patch/PYTHON-5517.patch new file mode 100644 index 0000000000..bc2e194271 --- /dev/null +++ b/.evergreen/spec-patch/PYTHON-5517.patch @@ -0,0 +1,473 @@ +diff --git a/test/connection_monitoring/pool-create-min-size-error.json b/test/connection_monitoring/pool-create-min-size-error.json +index 1c744b85..5c8ad02d 100644 +--- a/test/connection_monitoring/pool-create-min-size-error.json ++++ b/test/connection_monitoring/pool-create-min-size-error.json +@@ -17,7 +17,7 @@ + "isMaster", + "hello" + ], +- "closeConnection": true, ++ "errorCode": 91, + "appName": "poolCreateMinSizeErrorTest" + } + }, +diff --git a/test/discovery_and_monitoring/unified/backpressure-network-error-fail.json b/test/discovery_and_monitoring/unified/backpressure-network-error-fail.json +new file mode 100644 +index 00000000..f41b7645 +--- /dev/null ++++ b/test/discovery_and_monitoring/unified/backpressure-network-error-fail.json +@@ -0,0 +1,140 @@ ++{ ++ "description": "backpressure-network-error-fail", ++ "schemaVersion": "1.17", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "serverless": "forbid", ++ "topologies": [ ++ "single", ++ "replicaset", ++ "sharded" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "setupClient", ++ "useMultipleMongoses": false ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "backpressure-network-error-fail", ++ "databaseName": "sdam-tests", ++ "documents": [ ++ { ++ "_id": 1 ++ }, ++ { ++ "_id": 2 ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "apply backpressure on network connection errors during connection establishment", ++ "operations": [ ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "serverHeartbeatSucceededEvent", ++ "poolClearedEvent" ++ ], ++ "uriOptions": { ++ "retryWrites": false, ++ "heartbeatFrequencyMS": 1000000, ++ "serverMonitoringMode": "poll", ++ "appname": "backpressureNetworkErrorFailTest" ++ } ++ } ++ }, ++ { ++ "database": { ++ "id": "database", ++ "client": "client", ++ "databaseName": "sdam-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection", ++ "database": "database", ++ "collectionName": "backpressure-network-error-fail" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "waitForEvent", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client", ++ "event": { ++ "serverHeartbeatSucceededEvent": {} ++ }, ++ "count": 1 ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "setupClient", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": "alwaysOn", ++ "data": { ++ "failCommands": [ ++ "isMaster", ++ "hello" ++ ], ++ "appName": "backpressureNetworkErrorFailTest", ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "name": "insertMany", ++ "object": "collection", ++ "arguments": { ++ "documents": [ ++ { ++ "_id": 3 ++ }, ++ { ++ "_id": 4 ++ } ++ ] ++ }, ++ "expectError": { ++ "isError": true, ++ "errorLabelsContain": [ ++ "SystemOverloadedError", ++ "RetryableError" ++ ] ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client", ++ "eventType": "cmap", ++ "events": [] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail.json b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail.json +new file mode 100644 +index 00000000..a97c7a32 +--- /dev/null ++++ b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail.json +@@ -0,0 +1,143 @@ ++{ ++ "description": "backpressure-network-timeout-error", ++ "schemaVersion": "1.17", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "serverless": "forbid", ++ "topologies": [ ++ "single", ++ "replicaset", ++ "sharded" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "setupClient", ++ "useMultipleMongoses": false ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "backpressure-network-timeout-error", ++ "databaseName": "sdam-tests", ++ "documents": [ ++ { ++ "_id": 1 ++ }, ++ { ++ "_id": 2 ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "apply backpressure on network timeout error during connection establishment", ++ "operations": [ ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "serverDescriptionChangedEvent", ++ "poolClearedEvent" ++ ], ++ "uriOptions": { ++ "retryWrites": false, ++ "heartbeatFrequencyMS": 1000000, ++ "appname": "backpressureNetworkTimeoutErrorTest", ++ "serverMonitoringMode": "poll", ++ "connectTimeoutMS": 250, ++ "socketTimeoutMS": 250 ++ } ++ } ++ }, ++ { ++ "database": { ++ "id": "database", ++ "client": "client", ++ "databaseName": "sdam-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection", ++ "database": "database", ++ "collectionName": "backpressure-network-timeout-error" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "waitForEvent", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client", ++ "event": { ++ "serverDescriptionChangedEvent": {} ++ }, ++ "count": 1 ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "setupClient", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": "alwaysOn", ++ "data": { ++ "failCommands": [ ++ "isMaster", ++ "hello" ++ ], ++ "blockConnection": true, ++ "blockTimeMS": 500, ++ "appName": "backpressureNetworkTimeoutErrorTest" ++ } ++ } ++ } ++ }, ++ { ++ "name": "insertMany", ++ "object": "collection", ++ "arguments": { ++ "documents": [ ++ { ++ "_id": 3 ++ }, ++ { ++ "_id": 4 ++ } ++ ] ++ }, ++ "expectError": { ++ "isError": true, ++ "errorLabelsContain": [ ++ "SystemOverloadedError", ++ "RetryableError" ++ ] ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client", ++ "eventType": "cmap", ++ "events": [] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json +new file mode 100644 +index 00000000..35a49c13 +--- /dev/null ++++ b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json +@@ -0,0 +1,106 @@ ++{ ++ "description": "backpressure-server-description-unchanged-on-min-pool-size-population-error", ++ "schemaVersion": "1.17", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "serverless": "forbid", ++ "topologies": [ ++ "single" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "setupClient", ++ "useMultipleMongoses": false ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "the server description is not changed on handshake error during minPoolSize population", ++ "operations": [ ++ { ++ "name": "createEntities", ++ "object": "testRunner", ++ "arguments": { ++ "entities": [ ++ { ++ "client": { ++ "id": "client", ++ "observeEvents": [ ++ "serverDescriptionChangedEvent", ++ "connectionClosedEvent" ++ ], ++ "uriOptions": { ++ "appname": "authErrorTest", ++ "minPoolSize": 5, ++ "maxConnecting": 1, ++ "serverMonitoringMode": "poll", ++ "heartbeatFrequencyMS": 1000000 ++ } ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "name": "failPoint", ++ "object": "testRunner", ++ "arguments": { ++ "client": "setupClient", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "skip": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "hello", ++ "isMaster" ++ ], ++ "appName": "authErrorTest", ++ "closeConnection": true ++ } ++ } ++ } ++ }, ++ { ++ "name": "waitForEvent", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client", ++ "event": { ++ "serverDescriptionChangedEvent": {} ++ }, ++ "count": 1 ++ } ++ }, ++ { ++ "name": "waitForEvent", ++ "object": "testRunner", ++ "arguments": { ++ "client": "client", ++ "event": { ++ "connectionClosedEvent": {} ++ }, ++ "count": 1 ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client", ++ "eventType": "sdam", ++ "events": [ ++ { ++ "serverDescriptionChangedEvent": {} ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/load_balancer/sdam-error-handling.json b/test/load_balancer/sdam-error-handling.json +index 5892dcac..41bc90be 100644 +--- a/test/load_balancer/sdam-error-handling.json ++++ b/test/load_balancer/sdam-error-handling.json +@@ -282,7 +282,7 @@ + "isMaster", + "hello" + ], +- "closeConnection": true, ++ "errorCode": 11600, + "appName": "lbSDAMErrorTestClient" + } + } +@@ -297,7 +297,7 @@ + } + }, + "expectError": { +- "isClientError": true ++ "isError": true + } + } + ], +diff --git a/test/discovery_and_monitoring/errors/error_handling_handshake.json b/test/discovery_and_monitoring/errors/error_handling_handshake.json +index 56ca7d11..bf83f46f 100644 +--- a/test/discovery_and_monitoring/errors/error_handling_handshake.json ++++ b/test/discovery_and_monitoring/errors/error_handling_handshake.json +@@ -97,14 +97,22 @@ + "outcome": { + "servers": { + "a:27017": { +- "type": "Unknown", +- "topologyVersion": null, ++ "type": "RSPrimary", ++ "setName": "rs", ++ "topologyVersion": { ++ "processId": { ++ "$oid": "000000000000000000000001" ++ }, ++ "counter": { ++ "$numberLong": "1" ++ } ++ }, + "pool": { +- "generation": 1 ++ "generation": 0 + } + } + }, +- "topologyType": "ReplicaSetNoPrimary", ++ "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs" + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e35063af6..69f3ecca9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -487,6 +487,7 @@ results into the patch file. For example: the imaginary, unimplemented PYTHON-1234 ticket has associated spec test changes. To add those changes to `PYTHON-1234.patch`), do the following: ```bash git diff HEAD~1 path/to/file >> .evergreen/spec-patch/PYTHON-1234.patch +``` #### Running Locally Both `resync-all-specs.sh` and `resync-all-specs.py` can be run locally (and won't generate a PR). From 1496b8d2ff244a0638ad46c50b7b9f403c77817d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:59:49 -0800 Subject: [PATCH 032/129] Bump the actions group with 3 updates (#2637) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Iris <58442094+sleepyStick@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/sbom.yml | 2 +- .github/workflows/test-python.yml | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 666ea80aba..48ad4183fb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 + uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -63,6 +63,6 @@ jobs: pip install -e . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4 + uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index e2788f3618..8ae77d4575 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -71,7 +71,7 @@ jobs: if-no-files-found: error - name: Create Pull Request - uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore: Update SBOM after dependency changes' diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 682a8d98d0..0403802dde 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -87,7 +87,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: "3.10" @@ -112,7 +112,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: "3.10" @@ -131,7 +131,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: "3.10" @@ -153,7 +153,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -174,7 +174,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: enable-cache: true python-version: "3.10" @@ -264,7 +264,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7 + uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 with: python-version: "3.10" - id: setup-mongodb From a9923507c5c8b3ab7a176d372b7fe6374f6ea7c8 Mon Sep 17 00:00:00 2001 From: "mongodb-dbx-release-bot[bot]" <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:32:47 +0000 Subject: [PATCH 033/129] BUMP 4.16.0.dev1 Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com> --- pymongo/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/_version.py b/pymongo/_version.py index c6ba82ab13..22a0463fdb 100644 --- a/pymongo/_version.py +++ b/pymongo/_version.py @@ -18,7 +18,7 @@ import re from typing import List, Tuple, Union -__version__ = "4.16.0.dev0" +__version__ = "4.16.0.dev1" def get_version_tuple(version: str) -> Tuple[Union[int, str], ...]: From 37632e70d6e371d047d188da5f2864fa1b715053 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark" Date: Wed, 10 Dec 2025 22:29:00 -0500 Subject: [PATCH 034/129] PYTHON-5669 setup-tests.sh should support --active (#2648) --- .evergreen/scripts/setup-tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/setup-tests.sh b/.evergreen/scripts/setup-tests.sh index 1074c7eaaf..858906a39e 100755 --- a/.evergreen/scripts/setup-tests.sh +++ b/.evergreen/scripts/setup-tests.sh @@ -12,6 +12,7 @@ set -eu # TEST_CRYPT_SHARED If non-empty, install crypt_shared lib. # MONGODB_API_VERSION The mongodb api version to use in tests. # MONGODB_URI If non-empty, use as the MONGODB_URI in tests. +# USE_ACTIVE_VENV If non-empty, use the active virtual environment. SCRIPT_DIR=$(dirname ${BASH_SOURCE:-$0}) @@ -21,5 +22,5 @@ if [ -f $SCRIPT_DIR/env.sh ]; then fi echo "Setting up tests with args \"$*\"..." -uv run $SCRIPT_DIR/setup_tests.py "$@" +uv run ${USE_ACTIVE_VENV:+--active} "$SCRIPT_DIR/setup_tests.py" "$@" echo "Setting up tests with args \"$*\"... done." From da6d3d9e629de9fa1a8b86a2c9edc7529d5131e6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 11 Dec 2025 06:18:38 -0600 Subject: [PATCH 035/129] PYTHON-5673 Only update sbom when core dependencies change (#2647) --- .github/workflows/sbom.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 8ae77d4575..c99196dc87 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -10,7 +10,6 @@ on: push: branches: ['master'] paths: - - 'pyproject.toml' - 'requirements.txt' permissions: From 2f7946f523d7579565e2d4fcfad9ae1d630c4c8d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 11 Dec 2025 09:58:53 -0600 Subject: [PATCH 036/129] PYTHON-4099 Add contributing docs for memory profiling (#2646) --- CONTRIBUTING.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69f3ecca9a..ec2b6ab1e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -525,8 +525,10 @@ Use this generated file as a starting point for the completed conversion. The script is used like so: `python tools/convert_test_to_async.py [test_file.py]` -## Generating a flame graph using py-spy +## CPU profiling + To profile a test script and generate a flame graph, follow these steps: + 1. Install `py-spy` if you haven't already: ```bash pip install py-spy @@ -536,6 +538,26 @@ To profile a test script and generate a flame graph, follow these steps: (Note: on macOS you will need to run this command using `sudo` to allow `py-spy` to attach to the Python process.) 4. If you need to include native code (for example the C extensions), profiling should be done on a Linux system, as macOS and Windows do not support the `--native` option of `py-spy`. Creating an ubuntu Evergreen spawn host and using `scp` to copy the flamegraph `.svg` file back to your local machine is the best way to do this. +5. You can then view the flamegraph using an SVG viewer like a browser. + +## Memory profiling + +To test for a memory leak or any memory-related issues, the current best tool is [memray](https://bloomberg.github.io/memray/overview.html). +In order to include code from our C extensions, it must be run in native mode, on Linux. +To do so, either spin up an Ubuntu docker container or an Ubuntu Evergreen spawn host. + +From the spawn host or Ubuntu image, do the following: + +1. Install `memray` if you haven't already: + ```bash + pip install memray + ``` +2. Inside your test script, perform any required setup and then loop over the code you want to profile for improved sampling. +3. Run memray with the script under test with the `--native` flag, e.g. `python -m memray run --native -o test.bin `. +4. Generate the flamegraph with `python -m memray flamegraph -o test.html test.bin`. + See the [docs](https://bloomberg.github.io/memray/flamegraph.html) for more options. +5. Then, from the host computer, use either scp or docker cp to copy the flamegraph, e.g. `scp ubuntu@ec2-3-82-52-49.compute-1.amazonaws.com:/home/ubuntu/test.html .`. +6. You can then view the flamegraph html in a browser. ## Dependabot updates From 27ac7bd7172c107905b508670db9b795f84756c9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 12 Dec 2025 12:36:11 -0600 Subject: [PATCH 037/129] PYTHON-2517 Remove any Jython specific code or workarounds (#2641) --- pymongo/asynchronous/pool.py | 17 ----------------- pymongo/message.py | 2 -- pymongo/pool_options.py | 19 ------------------- pymongo/pool_shared.py | 6 ++---- pymongo/socket_checker.py | 5 +---- pymongo/synchronous/pool.py | 17 ----------------- test/asynchronous/test_client.py | 11 +---------- test/asynchronous/test_database.py | 2 +- test/asynchronous/utils_spec_runner.py | 4 +--- test/test_bson.py | 4 ---- test/test_bson_corpus.py | 5 ----- test/test_client.py | 11 +---------- test/test_database.py | 2 +- test/utils_spec_runner.py | 4 +--- 14 files changed, 9 insertions(+), 100 deletions(-) diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index f521091e3c..4e1e7c0638 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -104,21 +104,6 @@ from pymongo.typings import _Address, _CollationIn from pymongo.write_concern import WriteConcern -try: - from fcntl import F_GETFD, F_SETFD, FD_CLOEXEC, fcntl - - def _set_non_inheritable_non_atomic(fd: int) -> None: - """Set the close-on-exec flag on the given file descriptor.""" - flags = fcntl(fd, F_GETFD) - fcntl(fd, F_SETFD, flags | FD_CLOEXEC) - -except ImportError: - # Windows, various platforms we don't claim to support - # (Jython, IronPython, ..), systems that don't provide - # everything we need from fcntl, etc. - def _set_non_inheritable_non_atomic(fd: int) -> None: # noqa: ARG001 - """Dummy function for platforms that don't provide fcntl.""" - _IS_SYNC = False @@ -706,8 +691,6 @@ class PoolState: CLOSED = 3 -# Do *not* explicitly inherit from object or Jython won't call __del__ -# https://bugs.jython.org/issue1057 class Pool: def __init__( self, diff --git a/pymongo/message.py b/pymongo/message.py index 0f3aaaba77..b0d1ceb105 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -1298,8 +1298,6 @@ def _batched_write_command_impl( # Start of payload buf.seek(-1, 2) - # Work around some Jython weirdness. - buf.truncate() try: buf.write(_OP_MAP[operation]) except KeyError: diff --git a/pymongo/pool_options.py b/pymongo/pool_options.py index a5d76007b0..d4c00ac46c 100644 --- a/pymongo/pool_options.py +++ b/pymongo/pool_options.py @@ -79,17 +79,6 @@ # Windows patch level (e.g. 10.0.17763-SP0). "version": ".".join(map(str, _ver[:3])) + f"-SP{_ver[-1] or '0'}", } -elif sys.platform.startswith("java"): - _name, _ver, _arch = platform.java_ver()[-1] - _METADATA["os"] = { - # Linux, Windows 7, Mac OS X, etc. - "type": _name, - "name": _name, - # x86, x86_64, AMD64, etc. - "architecture": _arch, - # Linux kernel version, OSX version, etc. - "version": _ver, - } else: # Get potential alias (e.g. SunOS 5.11 becomes Solaris 2.11) _aliased = platform.system_alias(platform.system(), platform.release(), platform.version()) @@ -108,14 +97,6 @@ "(Python %s)" % ".".join(map(str, sys.version_info)), ) ) -elif sys.platform.startswith("java"): - _METADATA["platform"] = " ".join( - ( - platform.python_implementation(), - ".".join(map(str, sys.version_info)), - "(%s)" % " ".join((platform.system(), platform.release())), - ) - ) else: _METADATA["platform"] = " ".join( (platform.python_implementation(), ".".join(map(str, sys.version_info))) diff --git a/pymongo/pool_shared.py b/pymongo/pool_shared.py index 8db26ccead..a6f434885b 100644 --- a/pymongo/pool_shared.py +++ b/pymongo/pool_shared.py @@ -237,8 +237,7 @@ async def _async_create_connection(address: _Address, options: PoolOptions) -> s else: # This likely means we tried to connect to an IPv6 only # host with an OS/kernel or Python interpreter that doesn't - # support IPv6. The test case is Jython2.5.1 which doesn't - # support IPv6 at all. + # support IPv6. raise OSError("getaddrinfo failed") @@ -418,8 +417,7 @@ def _create_connection(address: _Address, options: PoolOptions) -> socket.socket else: # This likely means we tried to connect to an IPv6 only # host with an OS/kernel or Python interpreter that doesn't - # support IPv6. The test case is Jython2.5.1 which doesn't - # support IPv6 at all. + # support IPv6. raise OSError("getaddrinfo failed") diff --git a/pymongo/socket_checker.py b/pymongo/socket_checker.py index 78861854ab..a2f65ebc38 100644 --- a/pymongo/socket_checker.py +++ b/pymongo/socket_checker.py @@ -17,12 +17,9 @@ import errno import select -import sys from typing import Any, Optional, cast -# PYTHON-2320: Jython does not fully support poll on SSL sockets, -# https://bugs.jython.org/issue2900 -_HAVE_POLL = hasattr(select, "poll") and not sys.platform.startswith("java") +_HAVE_POLL = hasattr(select, "poll") _SelectError = getattr(select, "error", OSError) diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 66258fda18..89d2080bc8 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -104,21 +104,6 @@ from pymongo.typings import _Address, _CollationIn from pymongo.write_concern import WriteConcern -try: - from fcntl import F_GETFD, F_SETFD, FD_CLOEXEC, fcntl - - def _set_non_inheritable_non_atomic(fd: int) -> None: - """Set the close-on-exec flag on the given file descriptor.""" - flags = fcntl(fd, F_GETFD) - fcntl(fd, F_SETFD, flags | FD_CLOEXEC) - -except ImportError: - # Windows, various platforms we don't claim to support - # (Jython, IronPython, ..), systems that don't provide - # everything we need from fcntl, etc. - def _set_non_inheritable_non_atomic(fd: int) -> None: # noqa: ARG001 - """Dummy function for platforms that don't provide fcntl.""" - _IS_SYNC = True @@ -704,8 +689,6 @@ class PoolState: CLOSED = 3 -# Do *not* explicitly inherit from object or Jython won't call __del__ -# https://bugs.jython.org/issue1057 class Pool: def __init__( self, diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index 6794605339..15b32c4e99 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -1066,9 +1066,6 @@ async def test_close(self): await coll.count_documents({}) async def test_close_kills_cursors(self): - if sys.platform.startswith("java"): - # We can't figure out how to make this test reliable with Jython. - raise SkipTest("Can't test with Jython") test_client = await self.async_rs_or_single_client() # Kill any cursors possibly queued up by previous tests. gc.collect() @@ -1088,7 +1085,7 @@ async def test_close_kills_cursors(self): cursor = await coll.aggregate([], batchSize=10) self.assertTrue(bool(await anext(cursor))) del cursor - # Required for PyPy, Jython and other Python implementations that + # Required for PyPy and other Python implementations that # don't use reference counting garbage collection. gc.collect() @@ -1456,12 +1453,6 @@ async def test_contextlib(self): @async_client_context.require_sync def test_interrupt_signal(self): - if sys.platform.startswith("java"): - # We can't figure out how to raise an exception on a thread that's - # blocked on a socket, whether that's the main thread or a worker, - # without simply killing the whole thread in Jython. This suggests - # PYTHON-294 can't actually occur in Jython. - raise SkipTest("Can't test interrupts in Jython") if is_greenthread_patched(): raise SkipTest("Can't reliably test interrupts with green threads") diff --git a/test/asynchronous/test_database.py b/test/asynchronous/test_database.py index b49183a852..c1edb7a296 100644 --- a/test/asynchronous/test_database.py +++ b/test/asynchronous/test_database.py @@ -475,7 +475,7 @@ async def test_id_ordering(self): # when you iterate key/value pairs in a document. # This isn't reliable since python dicts don't # guarantee any particular order. This will never - # work right in Jython or any Python or environment + # work right in any Python or environment # with hash randomization enabled (e.g. tox). db = self.client.pymongo_test await db.test.drop() diff --git a/test/asynchronous/utils_spec_runner.py b/test/asynchronous/utils_spec_runner.py index 496c28a045..63e7e9e150 100644 --- a/test/asynchronous/utils_spec_runner.py +++ b/test/asynchronous/utils_spec_runner.py @@ -597,9 +597,7 @@ async def run_test_ops(self, sessions, collection, test): def parse_client_options(self, opts): """Allow encryption spec to override a clientOptions parsing.""" - # Convert test['clientOptions'] to dict to avoid a Jython bug using - # "**" with ScenarioDict. - return dict(opts) + return opts async def setup_scenario(self, scenario_def): """Allow specs to override a test's setup.""" diff --git a/test/test_bson.py b/test/test_bson.py index f49ae680e0..ffc02965fb 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -135,10 +135,6 @@ def assertInvalid(self, data): self.assertRaises(InvalidBSON, decode, data) def check_encode_then_decode(self, doc_class=dict, decoder=decode, encoder=encode): - # Work around http://bugs.jython.org/issue1728 - if sys.platform.startswith("java"): - doc_class = SON - def helper(doc): self.assertEqual(doc, (decoder(encoder(doc_class(doc))))) self.assertEqual(doc, decoder(encoder(doc))) diff --git a/test/test_bson_corpus.py b/test/test_bson_corpus.py index 504025e766..3370c18bda 100644 --- a/test/test_bson_corpus.py +++ b/test/test_bson_corpus.py @@ -160,11 +160,6 @@ def run_test(self): self.assertIsInstance(decoded_bson[test_key], _DEPRECATED_BSON_TYPES[bson_type]) continue - # Jython can't handle NaN with a payload from - # struct.(un)pack if endianness is specified in the format string. - if not (sys.platform.startswith("java") and description == "NaN with payload"): - # Test round-tripping canonical bson. - self.assertEqual(encode_bson(decoded_bson), cB, description) self.assertJsonEqual(encode_extjson(decoded_bson), cEJ) # Test round-tripping canonical extended json. diff --git a/test/test_client.py b/test/test_client.py index 9d201c663b..d07ce32365 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -1039,9 +1039,6 @@ def test_close(self): coll.count_documents({}) def test_close_kills_cursors(self): - if sys.platform.startswith("java"): - # We can't figure out how to make this test reliable with Jython. - raise SkipTest("Can't test with Jython") test_client = self.rs_or_single_client() # Kill any cursors possibly queued up by previous tests. gc.collect() @@ -1061,7 +1058,7 @@ def test_close_kills_cursors(self): cursor = coll.aggregate([], batchSize=10) self.assertTrue(bool(next(cursor))) del cursor - # Required for PyPy, Jython and other Python implementations that + # Required for PyPy and other Python implementations that # don't use reference counting garbage collection. gc.collect() @@ -1415,12 +1412,6 @@ def test_contextlib(self): @client_context.require_sync def test_interrupt_signal(self): - if sys.platform.startswith("java"): - # We can't figure out how to raise an exception on a thread that's - # blocked on a socket, whether that's the main thread or a worker, - # without simply killing the whole thread in Jython. This suggests - # PYTHON-294 can't actually occur in Jython. - raise SkipTest("Can't test interrupts in Jython") if is_greenthread_patched(): raise SkipTest("Can't reliably test interrupts with green threads") diff --git a/test/test_database.py b/test/test_database.py index ebbf6e55c6..25c3275238 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -470,7 +470,7 @@ def test_id_ordering(self): # when you iterate key/value pairs in a document. # This isn't reliable since python dicts don't # guarantee any particular order. This will never - # work right in Jython or any Python or environment + # work right in any Python or environment # with hash randomization enabled (e.g. tox). db = self.client.pymongo_test db.test.drop() diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index 46adeaefb5..9bf155e8f3 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -597,9 +597,7 @@ def run_test_ops(self, sessions, collection, test): def parse_client_options(self, opts): """Allow encryption spec to override a clientOptions parsing.""" - # Convert test['clientOptions'] to dict to avoid a Jython bug using - # "**" with ScenarioDict. - return dict(opts) + return opts def setup_scenario(self, scenario_def): """Allow specs to override a test's setup.""" From f813437154eda64e7af73d02dc2340b128b18e19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 06:17:06 -0600 Subject: [PATCH 038/129] Bump the actions group with 6 updates (#2651) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/dist.yml | 8 ++++---- .github/workflows/release-python.yml | 2 +- .github/workflows/sbom.yml | 4 ++-- .github/workflows/test-python.yml | 20 ++++++++++---------- .github/workflows/zizmor.yml | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 48ad4183fb..55dd016a87 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -63,6 +63,6 @@ jobs: pip install -e . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4 + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 81c6fa4200..2d36211dac 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -91,7 +91,7 @@ jobs: # Free-threading builds: ls wheelhouse/*cp314t*.whl - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: wheel-${{ matrix.buildplat[1] }} path: ./wheelhouse/*.whl @@ -124,7 +124,7 @@ jobs: cd .. python -c "from pymongo import has_c; assert has_c()" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: "sdist" path: ./dist/*.tar.gz @@ -135,13 +135,13 @@ jobs: name: Download Wheels steps: - name: Download all workflow run artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 - name: Flatten directory working-directory: . run: | find . -mindepth 2 -type f -exec mv {} . \; find . -type d -empty -delete - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: all-dist-${{ github.run_id }} path: "./*" diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 43e500337e..e3dd1edb1c 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -75,7 +75,7 @@ jobs: id-token: write steps: - name: Download all the dists - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: all-dist-${{ github.run_id }} path: dist/ diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index c99196dc87..d443c49505 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -63,14 +63,14 @@ jobs: run: rm -rf .venv .venv-sbom - name: Upload SBOM artifact - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: sbom path: sbom.json if-no-files-found: error - name: Create Pull Request - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 + uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore: Update SBOM after dependency changes' diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 0403802dde..c35fad7c35 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -87,7 +87,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: "3.10" @@ -112,7 +112,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: "3.10" @@ -131,7 +131,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: "3.10" @@ -153,7 +153,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -174,7 +174,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: enable-cache: true python-version: "3.10" @@ -214,7 +214,7 @@ jobs: run: | pip install build python -m build --sdist - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: "sdist" path: dist/*.tar.gz @@ -226,7 +226,7 @@ jobs: timeout-minutes: 20 steps: - name: Download sdist - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: path: sdist/ - name: Unpack SDist @@ -264,7 +264,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7 + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: python-version: "3.10" - id: setup-mongodb diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index e3b54cabd9..40c32b0957 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@c0e2b1c877e25a91d1d747c438d49199cad29698 + uses: zizmorcore/zizmor-action@cb3d8e846e148d1111d90b03375b9c03deceda37 From 0cfba4994d8d1ed1ca18fcfdb89c413d38ec1175 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 16 Dec 2025 12:21:45 -0500 Subject: [PATCH 039/129] PYTHON-5662 - Add support for server selection's deprioritized servers to all topologies (#2639) --- pymongo/asynchronous/mongo_client.py | 2 +- pymongo/asynchronous/topology.py | 37 ++++--- pymongo/server_selectors.py | 6 +- pymongo/synchronous/mongo_client.py | 2 +- pymongo/synchronous/topology.py | 37 ++++--- pymongo/topology_description.py | 38 ++++++- test/asynchronous/utils_selection_tests.py | 39 +++++-- .../read/DeprioritizedNearest.json | 62 +++++++++++ .../read/DeprioritizedPrimary.json | 39 +++++++ .../read/DeprioritizedPrimaryPreferred.json | 62 +++++++++++ .../read/DeprioritizedSecondary.json | 62 +++++++++++ .../read/DeprioritizedSecondaryPreferred.json | 62 +++++++++++ .../DeprioritizedAllPrimaryPreferred.json | 84 +++++++++++++++ .../DeprioritizedAllSecondaryPreferred.json | 100 ++++++++++++++++++ .../read/DeprioritizedNearest.json | 78 ++++++++++++++ .../read/DeprioritizedPrimary.json | 65 ++++++++++++ .../read/DeprioritizedPrimaryPreferred.json | 84 +++++++++++++++ .../read/DeprioritizedSecondary.json | 86 +++++++++++++++ .../read/DeprioritizedSecondaryPreferred.json | 78 ++++++++++++++ .../DeprioritizedSecondaryPreferred.json | 70 ++++++++++++ .../Sharded/read/DeprioritizedNearest.json | 47 ++++++++ .../Sharded/read/DeprioritizedPrimary.json | 42 ++++++++ .../read/DeprioritizedPrimaryPreferred.json | 47 ++++++++ .../Sharded/read/DeprioritizedSecondary.json | 47 ++++++++ .../read/DeprioritizedSecondaryPreferred.json | 47 ++++++++ .../Sharded/write/DeprioritizedNearest.json | 47 ++++++++ .../Sharded/write/DeprioritizedPrimary.json | 42 ++++++++ .../write/DeprioritizedPrimaryPreferred.json | 47 ++++++++ .../Sharded/write/DeprioritizedSecondary.json | 47 ++++++++ .../DeprioritizedSecondaryPreferred.json | 47 ++++++++ .../Single/read/Deprioritized.json | 54 ++++++++++ .../Single/write/Deprioritized.json | 54 ++++++++++ test/test_topology.py | 19 +--- test/utils_selection_tests.py | 43 ++++++-- 34 files changed, 1651 insertions(+), 72 deletions(-) create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearest.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimary.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferred.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondary.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferred.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllPrimaryPreferred.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllSecondaryPreferred.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearest.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimary.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferred.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondary.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferred.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/write/DeprioritizedSecondaryPreferred.json create mode 100644 test/server_selection/server_selection/Sharded/read/DeprioritizedNearest.json create mode 100644 test/server_selection/server_selection/Sharded/read/DeprioritizedPrimary.json create mode 100644 test/server_selection/server_selection/Sharded/read/DeprioritizedPrimaryPreferred.json create mode 100644 test/server_selection/server_selection/Sharded/read/DeprioritizedSecondary.json create mode 100644 test/server_selection/server_selection/Sharded/read/DeprioritizedSecondaryPreferred.json create mode 100644 test/server_selection/server_selection/Sharded/write/DeprioritizedNearest.json create mode 100644 test/server_selection/server_selection/Sharded/write/DeprioritizedPrimary.json create mode 100644 test/server_selection/server_selection/Sharded/write/DeprioritizedPrimaryPreferred.json create mode 100644 test/server_selection/server_selection/Sharded/write/DeprioritizedSecondary.json create mode 100644 test/server_selection/server_selection/Sharded/write/DeprioritizedSecondaryPreferred.json create mode 100644 test/server_selection/server_selection/Single/read/Deprioritized.json create mode 100644 test/server_selection/server_selection/Single/write/Deprioritized.json diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 2a8ff43392..bc51b7d8cc 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -2825,7 +2825,7 @@ async def run(self) -> T: if self._last_error is None: self._last_error = exc - if self._client.topology_description.topology_type == TOPOLOGY_TYPE.Sharded: + if self._server is not None: self._deprioritized_servers.append(self._server) def _is_not_eligible_for_retry(self) -> bool: diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index f7b1c8d91c..c171848cac 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -268,6 +268,7 @@ async def select_servers( server_selection_timeout: Optional[float] = None, address: Optional[_Address] = None, operation_id: Optional[int] = None, + deprioritized_servers: Optional[list[Server]] = None, ) -> list[Server]: """Return a list of Servers matching selector, or time out. @@ -295,7 +296,12 @@ async def select_servers( async with self._lock: server_descriptions = await self._select_servers_loop( - selector, server_timeout, operation, operation_id, address + selector, + server_timeout, + operation, + operation_id, + address, + deprioritized_servers=deprioritized_servers, ) return [ @@ -309,6 +315,7 @@ async def _select_servers_loop( operation: str, operation_id: Optional[int], address: Optional[_Address], + deprioritized_servers: Optional[list[Server]] = None, ) -> list[ServerDescription]: """select_servers() guts. Hold the lock when calling this.""" now = time.monotonic() @@ -327,7 +334,12 @@ async def _select_servers_loop( ) server_descriptions = self._description.apply_selector( - selector, address, custom_selector=self._settings.server_selector + selector, + address, + custom_selector=self._settings.server_selector, + deprioritized_servers=[server.description for server in deprioritized_servers] + if deprioritized_servers + else None, ) while not server_descriptions: @@ -388,9 +400,13 @@ async def _select_server( operation_id: Optional[int] = None, ) -> Server: servers = await self.select_servers( - selector, operation, server_selection_timeout, address, operation_id + selector, + operation, + server_selection_timeout, + address, + operation_id, + deprioritized_servers, ) - servers = _filter_servers(servers, deprioritized_servers) if len(servers) == 1: return servers[0] server1, server2 = random.sample(servers, 2) @@ -1119,16 +1135,3 @@ def _is_stale_server_description(current_sd: ServerDescription, new_sd: ServerDe if current_tv["processId"] != new_tv["processId"]: return False return current_tv["counter"] > new_tv["counter"] - - -def _filter_servers( - candidates: list[Server], deprioritized_servers: Optional[list[Server]] = None -) -> list[Server]: - """Filter out deprioritized servers from a list of server candidates.""" - if not deprioritized_servers: - return candidates - - filtered = [server for server in candidates if server not in deprioritized_servers] - - # If not possible to pick a prioritized server, return the original list - return filtered or candidates diff --git a/pymongo/server_selectors.py b/pymongo/server_selectors.py index 0d1425ab31..ee116b84ed 100644 --- a/pymongo/server_selectors.py +++ b/pymongo/server_selectors.py @@ -34,16 +34,16 @@ class Selection: @classmethod def from_topology_description(cls, topology_description: TopologyDescription) -> Selection: - known_servers = topology_description.known_servers + candidate_servers = topology_description.candidate_servers primary = None - for sd in known_servers: + for sd in candidate_servers: if sd.server_type == SERVER_TYPE.RSPrimary: primary = sd break return Selection( topology_description, - topology_description.known_servers, + topology_description.candidate_servers, topology_description.common_wire_version, primary, ) diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index fea2d6daef..139fe9c50d 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -2815,7 +2815,7 @@ def run(self) -> T: if self._last_error is None: self._last_error = exc - if self._client.topology_description.topology_type == TOPOLOGY_TYPE.Sharded: + if self._server is not None: self._deprioritized_servers.append(self._server) def _is_not_eligible_for_retry(self) -> bool: diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index 38acf67780..38e916b1e7 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -268,6 +268,7 @@ def select_servers( server_selection_timeout: Optional[float] = None, address: Optional[_Address] = None, operation_id: Optional[int] = None, + deprioritized_servers: Optional[list[Server]] = None, ) -> list[Server]: """Return a list of Servers matching selector, or time out. @@ -295,7 +296,12 @@ def select_servers( with self._lock: server_descriptions = self._select_servers_loop( - selector, server_timeout, operation, operation_id, address + selector, + server_timeout, + operation, + operation_id, + address, + deprioritized_servers=deprioritized_servers, ) return [ @@ -309,6 +315,7 @@ def _select_servers_loop( operation: str, operation_id: Optional[int], address: Optional[_Address], + deprioritized_servers: Optional[list[Server]] = None, ) -> list[ServerDescription]: """select_servers() guts. Hold the lock when calling this.""" now = time.monotonic() @@ -327,7 +334,12 @@ def _select_servers_loop( ) server_descriptions = self._description.apply_selector( - selector, address, custom_selector=self._settings.server_selector + selector, + address, + custom_selector=self._settings.server_selector, + deprioritized_servers=[server.description for server in deprioritized_servers] + if deprioritized_servers + else None, ) while not server_descriptions: @@ -388,9 +400,13 @@ def _select_server( operation_id: Optional[int] = None, ) -> Server: servers = self.select_servers( - selector, operation, server_selection_timeout, address, operation_id + selector, + operation, + server_selection_timeout, + address, + operation_id, + deprioritized_servers, ) - servers = _filter_servers(servers, deprioritized_servers) if len(servers) == 1: return servers[0] server1, server2 = random.sample(servers, 2) @@ -1117,16 +1133,3 @@ def _is_stale_server_description(current_sd: ServerDescription, new_sd: ServerDe if current_tv["processId"] != new_tv["processId"]: return False return current_tv["counter"] > new_tv["counter"] - - -def _filter_servers( - candidates: list[Server], deprioritized_servers: Optional[list[Server]] = None -) -> list[Server]: - """Filter out deprioritized servers from a list of server candidates.""" - if not deprioritized_servers: - return candidates - - filtered = [server for server in candidates if server not in deprioritized_servers] - - # If not possible to pick a prioritized server, return the original list - return filtered or candidates diff --git a/pymongo/topology_description.py b/pymongo/topology_description.py index a315c1b885..074bbc7fca 100644 --- a/pymongo/topology_description.py +++ b/pymongo/topology_description.py @@ -85,6 +85,7 @@ def __init__( self._server_descriptions = server_descriptions self._max_set_version = max_set_version self._max_election_id = max_election_id + self._candidate_servers = list(self._server_descriptions.values()) # The heartbeat_frequency is used in staleness estimates. self._topology_settings = topology_settings @@ -248,6 +249,11 @@ def readable_servers(self) -> list[ServerDescription]: """List of readable Servers.""" return [s for s in self._server_descriptions.values() if s.is_readable] + @property + def candidate_servers(self) -> list[ServerDescription]: + """List of Servers excluding deprioritized servers.""" + return self._candidate_servers + @property def common_wire_version(self) -> Optional[int]: """Minimum of all servers' max wire versions, or None.""" @@ -283,11 +289,27 @@ def _apply_local_threshold(self, selection: Optional[Selection]) -> list[ServerD if (cast(float, s.round_trip_time) - fastest) <= threshold ] + def _filter_servers( + self, deprioritized_servers: Optional[list[ServerDescription]] = None + ) -> None: + """Filter out deprioritized servers from a list of server candidates.""" + if not deprioritized_servers: + self._candidate_servers = self.known_servers + else: + deprioritized_addresses = {sd.address for sd in deprioritized_servers} + filtered = [ + server + for server in self.known_servers + if server.address not in deprioritized_addresses + ] + self._candidate_servers = filtered or self.known_servers + def apply_selector( self, selector: Any, address: Optional[_Address] = None, custom_selector: Optional[_ServerSelector] = None, + deprioritized_servers: Optional[list[ServerDescription]] = None, ) -> list[ServerDescription]: """List of servers matching the provided selector(s). @@ -324,14 +346,23 @@ def apply_selector( description = self.server_descriptions().get(address) return [description] if description and description.is_server_type_known else [] + self._filter_servers(deprioritized_servers) # Primary selection fast path. if self.topology_type == TOPOLOGY_TYPE.ReplicaSetWithPrimary and type(selector) is Primary: - for sd in self._server_descriptions.values(): + for sd in self._candidate_servers: if sd.server_type == SERVER_TYPE.RSPrimary: sds = [sd] if custom_selector: sds = custom_selector(sds) return sds + # All primaries are deprioritized + if deprioritized_servers: + for sd in deprioritized_servers: + if sd.server_type == SERVER_TYPE.RSPrimary: + sds = [sd] + if custom_selector: + sds = custom_selector(sds) + return sds # No primary found, return an empty list. return [] @@ -339,6 +370,11 @@ def apply_selector( # Ignore read preference for sharded clusters. if self.topology_type != TOPOLOGY_TYPE.Sharded: selection = selector(selection) + # No suitable servers found, apply preference again but include deprioritized servers. + if not selection and deprioritized_servers: + self._filter_servers(None) + selection = Selection.from_topology_description(self) + selection = selector(selection) # Apply custom selector followed by localThresholdMS. if custom_selector is not None and selection: diff --git a/test/asynchronous/utils_selection_tests.py b/test/asynchronous/utils_selection_tests.py index d6b92fadb4..9d4e6a3894 100644 --- a/test/asynchronous/utils_selection_tests.py +++ b/test/asynchronous/utils_selection_tests.py @@ -35,7 +35,7 @@ from bson import json_util from pymongo.asynchronous.settings import TopologySettings from pymongo.asynchronous.topology import Topology -from pymongo.common import HEARTBEAT_FREQUENCY +from pymongo.common import HEARTBEAT_FREQUENCY, clean_node from pymongo.errors import AutoReconnect, ConfigurationError from pymongo.operations import _Op from pymongo.server_selectors import writable_server_selector @@ -95,12 +95,21 @@ async def run_scenario(self): # "Eligible servers" is defined in the server selection spec as # the set of servers matching both the ReadPreference's mode # and tag sets. - top_latency = await create_topology(scenario_def) + top_suitable = await create_topology(scenario_def, local_threshold_ms=1000000) # "In latency window" is defined in the server selection # spec as the subset of suitable_servers that falls within the # allowable latency window. - top_suitable = await create_topology(scenario_def, local_threshold_ms=1000000) + top_latency = await create_topology(scenario_def) + + top_suitable_deprioritized_servers = [ + top_suitable.get_server_by_address(clean_node(server["address"])) + for server in scenario_def.get("deprioritized_servers", []) + ] + top_latency_deprioritized_servers = [ + top_latency.get_server_by_address(clean_node(server["address"])) + for server in scenario_def.get("deprioritized_servers", []) + ] # Create server selector. if scenario_def.get("operation") == "write": @@ -120,21 +129,37 @@ async def run_scenario(self): # Select servers. if not scenario_def.get("suitable_servers"): with self.assertRaises(AutoReconnect): - await top_suitable.select_server(pref, _Op.TEST, server_selection_timeout=0) + await top_suitable.select_server( + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_suitable_deprioritized_servers, + ) return if not scenario_def["in_latency_window"]: with self.assertRaises(AutoReconnect): - await top_latency.select_server(pref, _Op.TEST, server_selection_timeout=0) + await top_latency.select_server( + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_latency_deprioritized_servers, + ) return actual_suitable_s = await top_suitable.select_servers( - pref, _Op.TEST, server_selection_timeout=0 + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_suitable_deprioritized_servers, ) actual_latency_s = await top_latency.select_servers( - pref, _Op.TEST, server_selection_timeout=0 + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_latency_deprioritized_servers, ) expected_suitable_servers = {} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearest.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearest.json new file mode 100644 index 0000000000..f569c39d6c --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearest.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimary.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimary.json new file mode 100644 index 0000000000..8ec76964e9 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimary.json @@ -0,0 +1,39 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Primary" + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [], + "in_latency_window": [] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferred.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferred.json new file mode 100644 index 0000000000..4ab48d0b66 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferred.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondary.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondary.json new file mode 100644 index 0000000000..202a277487 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondary.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Secondary", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferred.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferred.json new file mode 100644 index 0000000000..d37e5545da --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferred.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllPrimaryPreferred.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllPrimaryPreferred.json new file mode 100644 index 0000000000..9f415203b3 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllPrimaryPreferred.json @@ -0,0 +1,84 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + {} + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllSecondaryPreferred.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllSecondaryPreferred.json new file mode 100644 index 0000000000..f8d321b067 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedAllSecondaryPreferred.json @@ -0,0 +1,100 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + {} + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearest.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearest.json new file mode 100644 index 0000000000..5b9d0fa538 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearest.json @@ -0,0 +1,78 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimary.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimary.json new file mode 100644 index 0000000000..a1ef5b200f --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimary.json @@ -0,0 +1,65 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Primary" + }, + "deprioritized_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferred.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferred.json new file mode 100644 index 0000000000..7b6da395c5 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferred.json @@ -0,0 +1,84 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + {} + ] + }, + "deprioritized_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondary.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondary.json new file mode 100644 index 0000000000..f3dc03db09 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondary.json @@ -0,0 +1,86 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Secondary", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferred.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferred.json new file mode 100644 index 0000000000..1fd6c654f0 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferred.json @@ -0,0 +1,78 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/write/DeprioritizedSecondaryPreferred.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/write/DeprioritizedSecondaryPreferred.json new file mode 100644 index 0000000000..4669ee57d5 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/write/DeprioritizedSecondaryPreferred.json @@ -0,0 +1,70 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/read/DeprioritizedNearest.json b/test/server_selection/server_selection/Sharded/read/DeprioritizedNearest.json new file mode 100644 index 0000000000..1aa730cd21 --- /dev/null +++ b/test/server_selection/server_selection/Sharded/read/DeprioritizedNearest.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 10, + "type": "Mongos" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 10, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 10, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/read/DeprioritizedPrimary.json b/test/server_selection/server_selection/Sharded/read/DeprioritizedPrimary.json new file mode 100644 index 0000000000..8629c84a2b --- /dev/null +++ b/test/server_selection/server_selection/Sharded/read/DeprioritizedPrimary.json @@ -0,0 +1,42 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Primary" + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/read/DeprioritizedPrimaryPreferred.json b/test/server_selection/server_selection/Sharded/read/DeprioritizedPrimaryPreferred.json new file mode 100644 index 0000000000..800264cfde --- /dev/null +++ b/test/server_selection/server_selection/Sharded/read/DeprioritizedPrimaryPreferred.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/read/DeprioritizedSecondary.json b/test/server_selection/server_selection/Sharded/read/DeprioritizedSecondary.json new file mode 100644 index 0000000000..42d1e227f1 --- /dev/null +++ b/test/server_selection/server_selection/Sharded/read/DeprioritizedSecondary.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Secondary", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/read/DeprioritizedSecondaryPreferred.json b/test/server_selection/server_selection/Sharded/read/DeprioritizedSecondaryPreferred.json new file mode 100644 index 0000000000..eaab0b3af5 --- /dev/null +++ b/test/server_selection/server_selection/Sharded/read/DeprioritizedSecondaryPreferred.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/write/DeprioritizedNearest.json b/test/server_selection/server_selection/Sharded/write/DeprioritizedNearest.json new file mode 100644 index 0000000000..5cc2cc5477 --- /dev/null +++ b/test/server_selection/server_selection/Sharded/write/DeprioritizedNearest.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 10, + "type": "Mongos" + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 10, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 10, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/write/DeprioritizedPrimary.json b/test/server_selection/server_selection/Sharded/write/DeprioritizedPrimary.json new file mode 100644 index 0000000000..a936658939 --- /dev/null +++ b/test/server_selection/server_selection/Sharded/write/DeprioritizedPrimary.json @@ -0,0 +1,42 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "Primary" + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/write/DeprioritizedPrimaryPreferred.json b/test/server_selection/server_selection/Sharded/write/DeprioritizedPrimaryPreferred.json new file mode 100644 index 0000000000..5dc3fa292d --- /dev/null +++ b/test/server_selection/server_selection/Sharded/write/DeprioritizedPrimaryPreferred.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/write/DeprioritizedSecondary.json b/test/server_selection/server_selection/Sharded/write/DeprioritizedSecondary.json new file mode 100644 index 0000000000..8b5cc5251c --- /dev/null +++ b/test/server_selection/server_selection/Sharded/write/DeprioritizedSecondary.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "Secondary", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Sharded/write/DeprioritizedSecondaryPreferred.json b/test/server_selection/server_selection/Sharded/write/DeprioritizedSecondaryPreferred.json new file mode 100644 index 0000000000..dcff289cc2 --- /dev/null +++ b/test/server_selection/server_selection/Sharded/write/DeprioritizedSecondaryPreferred.json @@ -0,0 +1,47 @@ +{ + "topology_description": { + "type": "Sharded", + "servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + }, + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "g:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "suitable_servers": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ], + "in_latency_window": [ + { + "address": "h:27017", + "avg_rtt_ms": 5, + "type": "Mongos" + } + ] +} diff --git a/test/server_selection/server_selection/Single/read/Deprioritized.json b/test/server_selection/server_selection/Single/read/Deprioritized.json new file mode 100644 index 0000000000..c76fc6c771 --- /dev/null +++ b/test/server_selection/server_selection/Single/read/Deprioritized.json @@ -0,0 +1,54 @@ +{ + "topology_description": { + "type": "Single", + "servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ] +} diff --git a/test/server_selection/server_selection/Single/write/Deprioritized.json b/test/server_selection/server_selection/Single/write/Deprioritized.json new file mode 100644 index 0000000000..099076f2da --- /dev/null +++ b/test/server_selection/server_selection/Single/write/Deprioritized.json @@ -0,0 +1,54 @@ +{ + "topology_description": { + "type": "Single", + "servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "Standalone", + "tags": { + "data_center": "dc" + } + } + ] +} diff --git a/test/test_topology.py b/test/test_topology.py index d3bbcd9060..f2e7a91cda 100644 --- a/test/test_topology.py +++ b/test/test_topology.py @@ -38,7 +38,7 @@ from pymongo.synchronous.pool import PoolOptions from pymongo.synchronous.server import Server from pymongo.synchronous.settings import TopologySettings -from pymongo.synchronous.topology import Topology, _ErrorContext, _filter_servers +from pymongo.synchronous.topology import Topology, _ErrorContext from pymongo.topology_description import TOPOLOGY_TYPE @@ -733,23 +733,6 @@ def test_unexpected_load_balancer(self): self.assertNotIn(("a", 27017), t.description.server_descriptions()) self.assertEqual(t.description.topology_type_name, "Unknown") - def test_filtered_server_selection(self): - s1 = Server(ServerDescription(("localhost", 27017)), pool=object(), monitor=object()) # type: ignore[arg-type] - s2 = Server(ServerDescription(("localhost2", 27017)), pool=object(), monitor=object()) # type: ignore[arg-type] - servers = [s1, s2] - - result = _filter_servers(servers, deprioritized_servers=[s2]) - self.assertEqual(result, [s1]) - - result = _filter_servers(servers, deprioritized_servers=[s1, s2]) - self.assertEqual(result, servers) - - result = _filter_servers(servers, deprioritized_servers=[]) - self.assertEqual(result, servers) - - result = _filter_servers(servers) - self.assertEqual(result, servers) - def wait_for_primary(topology): """Wait for a Topology to discover a writable server. diff --git a/test/utils_selection_tests.py b/test/utils_selection_tests.py index 2772f06070..2798e49eb0 100644 --- a/test/utils_selection_tests.py +++ b/test/utils_selection_tests.py @@ -33,7 +33,7 @@ from test.utils_shared import parse_read_preference from bson import json_util -from pymongo.common import HEARTBEAT_FREQUENCY +from pymongo.common import HEARTBEAT_FREQUENCY, clean_node from pymongo.errors import AutoReconnect, ConfigurationError from pymongo.operations import _Op from pymongo.server_selectors import writable_server_selector @@ -95,12 +95,21 @@ def run_scenario(self): # "Eligible servers" is defined in the server selection spec as # the set of servers matching both the ReadPreference's mode # and tag sets. - top_latency = create_topology(scenario_def) + top_suitable = create_topology(scenario_def, local_threshold_ms=1000000) # "In latency window" is defined in the server selection # spec as the subset of suitable_servers that falls within the # allowable latency window. - top_suitable = create_topology(scenario_def, local_threshold_ms=1000000) + top_latency = create_topology(scenario_def) + + top_suitable_deprioritized_servers = [ + top_suitable.get_server_by_address(clean_node(server["address"])) + for server in scenario_def.get("deprioritized_servers", []) + ] + top_latency_deprioritized_servers = [ + top_latency.get_server_by_address(clean_node(server["address"])) + for server in scenario_def.get("deprioritized_servers", []) + ] # Create server selector. if scenario_def.get("operation") == "write": @@ -120,18 +129,38 @@ def run_scenario(self): # Select servers. if not scenario_def.get("suitable_servers"): with self.assertRaises(AutoReconnect): - top_suitable.select_server(pref, _Op.TEST, server_selection_timeout=0) + top_suitable.select_server( + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_suitable_deprioritized_servers, + ) return if not scenario_def["in_latency_window"]: with self.assertRaises(AutoReconnect): - top_latency.select_server(pref, _Op.TEST, server_selection_timeout=0) + top_latency.select_server( + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_latency_deprioritized_servers, + ) return - actual_suitable_s = top_suitable.select_servers(pref, _Op.TEST, server_selection_timeout=0) - actual_latency_s = top_latency.select_servers(pref, _Op.TEST, server_selection_timeout=0) + actual_suitable_s = top_suitable.select_servers( + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_suitable_deprioritized_servers, + ) + actual_latency_s = top_latency.select_servers( + pref, + _Op.TEST, + server_selection_timeout=0, + deprioritized_servers=top_latency_deprioritized_servers, + ) expected_suitable_servers = {} for server in scenario_def["suitable_servers"]: From f9f48bab95ced066facfead59804613e0501ad52 Mon Sep 17 00:00:00 2001 From: Jib Date: Tue, 16 Dec 2025 14:29:15 -0500 Subject: [PATCH 040/129] PYTHON-5433: Create an sbom-requirements.txt file to capture optional dependencies (#2649) --- .github/workflows/sbom.yml | 10 ++-- tools/generate_sbom_requirements.py | 74 +++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tools/generate_sbom_requirements.py diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index d443c49505..c5d055bdcb 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -11,6 +11,9 @@ on: branches: ['master'] paths: - 'requirements.txt' + - 'requirements/**.txt' + - '!requirements/docs.txt' + - '!requirements/test.txt' permissions: contents: write @@ -39,7 +42,8 @@ jobs: run: | python -m venv .venv source .venv/bin/activate - pip install -r requirements.txt + python tools/generate_sbom_requirements.py + pip install -r sbom-requirements.txt pip install . pip uninstall -y pip setuptools deactivate @@ -60,7 +64,7 @@ jobs: - name: Cleanup if: always() - run: rm -rf .venv .venv-sbom + run: rm -rf .venv .venv-sbom sbom-requirements.txt - name: Upload SBOM artifact uses: actions/upload-artifact@v6 @@ -76,7 +80,7 @@ jobs: commit-message: 'chore: Update SBOM after dependency changes' branch: auto-update-sbom-${{ github.run_id }} delete-branch: true - title: 'chore: Update SBOM' + title: 'Automation: Update SBOM' body: | ## Automated SBOM Update diff --git a/tools/generate_sbom_requirements.py b/tools/generate_sbom_requirements.py new file mode 100644 index 0000000000..131605ab88 --- /dev/null +++ b/tools/generate_sbom_requirements.py @@ -0,0 +1,74 @@ +""" +Concatenate requirements files into sbom-requirements.txt at repository root. + +- 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 +- Writes output to sbom-requirements.txt (overwrites) +""" +from __future__ import annotations + +from pathlib import Path + +EXCLUDED_NAMES = {"docs.txt", "test.txt"} + + +def collect_files(root: Path) -> list[Path]: + """Collect requirement files to include in SBOM requirements. + + Args: + root (Path): The root directory of the repository. + + Returns: + list[Path]: A list of Paths to requirement files to include in the SBOM. + """ + files = [] + + # requirements.txt + all requirements/**/*.txt + for p in [root / "requirements.txt", *root.glob("requirements/**/*.txt")]: + if p.is_file() and p.name not in EXCLUDED_NAMES: + files.append(p) + + return files + + +def write_combined_req_files(root: Path, files: list[Path], outname: str) -> Path: + """Concatenate requirement files and write to outname file. + + Args: + root (Path): The root directory of the repository. + files (list[Path]): A list of Paths to requirement files to include in the SBOM. + outname (str): The name of the output file. + + Raises: + RuntimeError: If writing to the output file fails. + + Returns: + Path: The path to the output file. + """ + outpath = root / outname + try: + with outpath.open("w", encoding="utf-8") as f: + for p in files: + with p.open("r", encoding="utf-8") as r: + content = r.read().rstrip() + if content: + f.write(content + "\n") + return outpath + except Exception as e: + raise RuntimeError(f"Failed to write {outpath}: {e}") from e + + +def main(): + root = Path(__file__).parent.parent.resolve() + + files = collect_files(root) + if not files: + raise FileNotFoundError(f"No requirement files found from {root}") + + outpath = write_combined_req_files(root, files, "sbom-requirements.txt") + print(f"Wrote concatenated requirements to: {outpath}") + + +if __name__ == "__main__": + main() From 0ce7686c641753f05323c8029edaffffc8ca3b29 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 16 Dec 2025 13:30:30 -0600 Subject: [PATCH 041/129] PYTHON-5563 Fix unified test discovery (#2644) --- test/asynchronous/test_auth_oidc.py | 5 ++--- test/asynchronous/test_auth_spec.py | 5 ++--- test/asynchronous/test_change_stream.py | 7 ++---- test/asynchronous/test_client_metadata.py | 12 ++-------- .../test_collection_management.py | 12 ++-------- test/asynchronous/test_command_logging.py | 11 ++-------- test/asynchronous/test_command_monitoring.py | 11 ++-------- test/asynchronous/test_connection_logging.py | 11 ++-------- test/asynchronous/test_crud_unified.py | 10 ++------- test/asynchronous/test_csot.py | 10 ++------- .../test_discovery_and_monitoring.py | 11 ++-------- test/asynchronous/test_encryption.py | 8 ++----- test/asynchronous/test_gridfs_spec.py | 10 ++------- test/asynchronous/test_index_management.py | 10 ++------- test/asynchronous/test_load_balancer.py | 10 ++------- .../test_read_write_concern_spec.py | 8 ++----- .../test_retryable_reads_unified.py | 10 ++------- .../test_retryable_writes_unified.py | 12 ++++------ test/asynchronous/test_run_command.py | 11 ++-------- .../test_server_selection_logging.py | 10 ++------- test/asynchronous/test_sessions_unified.py | 11 ++-------- .../asynchronous/test_transactions_unified.py | 22 +++++-------------- test/asynchronous/test_unified_format.py | 12 +++++----- .../test_versioned_api_integration.py | 11 ++-------- test/asynchronous/unified_format.py | 14 ++++++++++++ test/test_auth_oidc.py | 5 ++--- test/test_auth_spec.py | 5 ++--- test/test_change_stream.py | 7 ++---- test/test_client_metadata.py | 12 ++-------- test/test_collection_management.py | 12 ++-------- test/test_command_logging.py | 11 ++-------- test/test_command_monitoring.py | 11 ++-------- test/test_connection_logging.py | 11 ++-------- test/test_crud_unified.py | 10 ++------- test/test_csot.py | 10 ++------- test/test_discovery_and_monitoring.py | 11 ++-------- test/test_encryption.py | 8 ++----- test/test_gridfs_spec.py | 10 ++------- test/test_index_management.py | 10 ++------- test/test_load_balancer.py | 10 ++------- test/test_read_write_concern_spec.py | 8 ++----- test/test_retryable_reads_unified.py | 10 ++------- test/test_retryable_writes_unified.py | 12 ++++------ test/test_run_command.py | 11 ++-------- test/test_server_selection_logging.py | 10 ++------- test/test_sessions_unified.py | 11 ++-------- test/test_transactions_unified.py | 22 +++++-------------- test/test_unified_format.py | 12 +++++----- test/test_versioned_api_integration.py | 11 ++-------- test/unified_format.py | 14 ++++++++++++ 50 files changed, 142 insertions(+), 386 deletions(-) diff --git a/test/asynchronous/test_auth_oidc.py b/test/asynchronous/test_auth_oidc.py index 639c155e73..ff604f55ae 100644 --- a/test/asynchronous/test_auth_oidc.py +++ b/test/asynchronous/test_auth_oidc.py @@ -30,7 +30,7 @@ sys.path[0:0] = [""] -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import EventListener, OvertCommandListener from bson import SON @@ -54,14 +54,13 @@ _IS_SYNC = False ROOT = Path(__file__).parent.parent.resolve() -TEST_PATH = ROOT / "auth" / "unified" ENVIRON = os.environ.get("OIDC_ENV", "test") DOMAIN = os.environ.get("OIDC_DOMAIN", "") TOKEN_DIR = os.environ.get("OIDC_TOKEN_DIR", "") TOKEN_FILE = os.environ.get("OIDC_TOKEN_FILE", "") # Generate unified tests. -globals().update(generate_test_classes(str(TEST_PATH), module=__name__)) +globals().update(generate_test_classes(get_test_path("auth", "unified"), module=__name__)) pytestmark = pytest.mark.auth_oidc diff --git a/test/asynchronous/test_auth_spec.py b/test/asynchronous/test_auth_spec.py index 7c659c6d93..a40687348c 100644 --- a/test/asynchronous/test_auth_spec.py +++ b/test/asynchronous/test_auth_spec.py @@ -27,7 +27,7 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from pymongo import AsyncMongoClient from pymongo.auth_oidc_shared import OIDCCallback @@ -35,8 +35,7 @@ pytestmark = pytest.mark.auth _IS_SYNC = False - -_TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "auth") +_TEST_PATH = get_test_path("auth") class TestAuthSpec(AsyncPyMongoTestCase): diff --git a/test/asynchronous/test_change_stream.py b/test/asynchronous/test_change_stream.py index 89a215f143..3a34319eaa 100644 --- a/test/asynchronous/test_change_stream.py +++ b/test/asynchronous/test_change_stream.py @@ -35,7 +35,7 @@ async_client_context, unittest, ) -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( AllowListEventListener, EventListener, @@ -1143,12 +1143,9 @@ def asyncTearDown(self): self.listener.reset() -_TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "change_streams") - - globals().update( generate_test_classes( - os.path.join(_TEST_PATH, "unified"), + get_test_path("change_streams", "unified"), module=__name__, ) ) diff --git a/test/asynchronous/test_client_metadata.py b/test/asynchronous/test_client_metadata.py index 2f175cceed..45c1bd1b3b 100644 --- a/test/asynchronous/test_client_metadata.py +++ b/test/asynchronous/test_client_metadata.py @@ -19,7 +19,7 @@ import time import unittest from test.asynchronous import AsyncIntegrationTest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import CMAPListener from typing import Any, Optional @@ -40,16 +40,8 @@ _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "handshake", "unified") -else: - _TEST_PATH = os.path.join( - pathlib.Path(__file__).resolve().parent.parent, "handshake", "unified" - ) - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("handshake", "unified"), module=__name__)) def _get_handshake_driver_info(request): diff --git a/test/asynchronous/test_collection_management.py b/test/asynchronous/test_collection_management.py index c0edf91581..7a142dc65b 100644 --- a/test/asynchronous/test_collection_management.py +++ b/test/asynchronous/test_collection_management.py @@ -22,20 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "collection_management") -else: - _TEST_PATH = os.path.join( - pathlib.Path(__file__).resolve().parent.parent, "collection_management" - ) - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("collection_management"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_command_logging.py b/test/asynchronous/test_command_logging.py index f9b459c152..831dd0c109 100644 --- a/test/asynchronous/test_command_logging.py +++ b/test/asynchronous/test_command_logging.py @@ -22,20 +22,13 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "command_logging") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "command_logging") - - globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("command_logging"), module=__name__, ) ) diff --git a/test/asynchronous/test_command_monitoring.py b/test/asynchronous/test_command_monitoring.py index 311fd1fdc1..a04ba449ae 100644 --- a/test/asynchronous/test_command_monitoring.py +++ b/test/asynchronous/test_command_monitoring.py @@ -22,20 +22,13 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "command_monitoring") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "command_monitoring") - - globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("command_monitoring"), module=__name__, ) ) diff --git a/test/asynchronous/test_connection_logging.py b/test/asynchronous/test_connection_logging.py index 945c6c59b5..4d03391dd2 100644 --- a/test/asynchronous/test_connection_logging.py +++ b/test/asynchronous/test_connection_logging.py @@ -22,20 +22,13 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "connection_logging") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "connection_logging") - - globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("connection_logging"), module=__name__, ) ) diff --git a/test/asynchronous/test_crud_unified.py b/test/asynchronous/test_crud_unified.py index 8b1f9b8e38..94e47a26e7 100644 --- a/test/asynchronous/test_crud_unified.py +++ b/test/asynchronous/test_crud_unified.py @@ -22,18 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "crud", "unified") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "crud", "unified") - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("crud", "unified"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_csot.py b/test/asynchronous/test_csot.py index a978d1ccc0..547ee20a54 100644 --- a/test/asynchronous/test_csot.py +++ b/test/asynchronous/test_csot.py @@ -22,7 +22,7 @@ sys.path[0:0] = [""] from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.asynchronous.utils import flaky import pymongo @@ -31,14 +31,8 @@ _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "csot") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "csot") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("csot"), module=__name__)) class TestCSOT(AsyncIntegrationTest): diff --git a/test/asynchronous/test_discovery_and_monitoring.py b/test/asynchronous/test_discovery_and_monitoring.py index 5820d00c48..0bbf471d87 100644 --- a/test/asynchronous/test_discovery_and_monitoring.py +++ b/test/asynchronous/test_discovery_and_monitoring.py @@ -40,7 +40,7 @@ unittest, ) from test.asynchronous.pymongo_mocks import DummyMonitor -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.asynchronous.utils import ( async_get_pool, ) @@ -76,14 +76,7 @@ _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - SDAM_PATH = os.path.join(Path(__file__).resolve().parent, "discovery_and_monitoring") -else: - SDAM_PATH = os.path.join( - Path(__file__).resolve().parent.parent, - "discovery_and_monitoring", - ) +SDAM_PATH = get_test_path("discovery_and_monitoring") async def create_mock_topology(uri, monitor_class=DummyMonitor): diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 9cd0944ce7..3f71b35e3d 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -53,7 +53,7 @@ unittest, ) from test.asynchronous.test_bulk import AsyncBulkTestBase -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.helpers_shared import ( ALL_KMS_PROVIDERS, AWS_CREDS, @@ -273,11 +273,7 @@ def unmanaged_create_client_encryption( # Location of JSON test files. -if _IS_SYNC: - BASE = os.path.join(pathlib.Path(__file__).resolve().parent, "client-side-encryption") -else: - BASE = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "client-side-encryption") - +BASE = get_test_path("client-side-encryption") SPEC_PATH = os.path.join(BASE, "spec") OPTS = CodecOptions() diff --git a/test/asynchronous/test_gridfs_spec.py b/test/asynchronous/test_gridfs_spec.py index f3dc14fbdc..ab1c8a0ebb 100644 --- a/test/asynchronous/test_gridfs_spec.py +++ b/test/asynchronous/test_gridfs_spec.py @@ -22,18 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "gridfs") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "gridfs") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("gridfs"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_index_management.py b/test/asynchronous/test_index_management.py index 890788fc56..ac096ec099 100644 --- a/test/asynchronous/test_index_management.py +++ b/test/asynchronous/test_index_management.py @@ -28,7 +28,7 @@ sys.path[0:0] = [""] from test.asynchronous import AsyncIntegrationTest, AsyncPyMongoTestCase, unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import AllowListEventListener, OvertCommandListener from pymongo.errors import OperationFailure @@ -40,12 +40,6 @@ pytestmark = pytest.mark.search_index -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "index_management") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "index_management") - _NAME = "test-search-index" @@ -370,7 +364,7 @@ async def test_case_7(self): globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("index_management"), module=__name__, ) ) diff --git a/test/asynchronous/test_load_balancer.py b/test/asynchronous/test_load_balancer.py index 17d85841f9..8e1ee3e797 100644 --- a/test/asynchronous/test_load_balancer.py +++ b/test/asynchronous/test_load_balancer.py @@ -30,7 +30,7 @@ sys.path[0:0] = [""] from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( async_wait_until, create_async_event, @@ -40,14 +40,8 @@ pytestmark = pytest.mark.load_balancer -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "load_balancer") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "load_balancer") - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("load_balancer"), module=__name__)) class TestLB(AsyncIntegrationTest): diff --git a/test/asynchronous/test_read_write_concern_spec.py b/test/asynchronous/test_read_write_concern_spec.py index b5cb32932f..2d08de7804 100644 --- a/test/asynchronous/test_read_write_concern_spec.py +++ b/test/asynchronous/test_read_write_concern_spec.py @@ -24,7 +24,7 @@ sys.path[0:0] = [""] from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import OvertCommandListener from pymongo import DESCENDING @@ -42,11 +42,7 @@ _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "read_write_concern") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "read_write_concern") +TEST_PATH = get_test_path("read_write_concern") class TestReadWriteConcernSpec(AsyncIntegrationTest): diff --git a/test/asynchronous/test_retryable_reads_unified.py b/test/asynchronous/test_retryable_reads_unified.py index e62d606810..3de8aa96a3 100644 --- a/test/asynchronous/test_retryable_reads_unified.py +++ b/test/asynchronous/test_retryable_reads_unified.py @@ -22,21 +22,15 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "retryable_reads/unified") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "retryable_reads/unified") - # Generate unified tests. # PyMongo does not support MapReduce, ListDatabaseObjects or ListCollectionObjects. globals().update( generate_test_classes( - TEST_PATH, + get_test_path("retryable_reads", "unified"), module=__name__, expected_failures=["ListDatabaseObjects .*", "ListCollectionObjects .*", "MapReduce .*"], ) diff --git a/test/asynchronous/test_retryable_writes_unified.py b/test/asynchronous/test_retryable_writes_unified.py index bb493e6010..7d33c5252a 100644 --- a/test/asynchronous/test_retryable_writes_unified.py +++ b/test/asynchronous/test_retryable_writes_unified.py @@ -22,18 +22,14 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "retryable_writes/unified") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "retryable_writes/unified") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update( + generate_test_classes(get_test_path("retryable_writes", "unified"), module=__name__) +) if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_run_command.py b/test/asynchronous/test_run_command.py index 3ac8c32706..cfd1adfab7 100644 --- a/test/asynchronous/test_run_command.py +++ b/test/asynchronous/test_run_command.py @@ -18,20 +18,13 @@ import os import unittest from pathlib import Path -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "run_command") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "run_command") - - globals().update( generate_test_classes( - os.path.join(TEST_PATH, "unified"), + get_test_path("run_command", "unified"), module=__name__, ) ) diff --git a/test/asynchronous/test_server_selection_logging.py b/test/asynchronous/test_server_selection_logging.py index 6b0975318a..6f3ea207f4 100644 --- a/test/asynchronous/test_server_selection_logging.py +++ b/test/asynchronous/test_server_selection_logging.py @@ -22,20 +22,14 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "server_selection_logging") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "server_selection_logging") - globals().update( generate_test_classes( - TEST_PATH, + get_test_path("server_selection_logging"), module=__name__, ) ) diff --git a/test/asynchronous/test_sessions_unified.py b/test/asynchronous/test_sessions_unified.py index b4cbac5704..ee2b4d418a 100644 --- a/test/asynchronous/test_sessions_unified.py +++ b/test/asynchronous/test_sessions_unified.py @@ -22,19 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "sessions") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "sessions") - - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("sessions"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_transactions_unified.py b/test/asynchronous/test_transactions_unified.py index 8e5b1ae181..5f9b5d0225 100644 --- a/test/asynchronous/test_transactions_unified.py +++ b/test/asynchronous/test_transactions_unified.py @@ -22,7 +22,7 @@ sys.path[0:0] = [""] from test import client_context, unittest -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False @@ -31,25 +31,13 @@ def setUpModule(): pass -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "transactions/unified") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "transactions/unified") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) - -# Location of JSON test specifications for transactions-convenient-api. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "transactions-convenient-api/unified") -else: - TEST_PATH = os.path.join( - Path(__file__).resolve().parent.parent, "transactions-convenient-api/unified" - ) +globals().update(generate_test_classes(get_test_path("transactions/unified"), module=__name__)) # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update( + generate_test_classes(get_test_path("transactions-convenient-api/unified"), module=__name__) +) if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_unified_format.py b/test/asynchronous/test_unified_format.py index 58a1ea3326..8136641236 100644 --- a/test/asynchronous/test_unified_format.py +++ b/test/asynchronous/test_unified_format.py @@ -21,18 +21,18 @@ sys.path[0:0] = [""] from test import UnitTest, unittest -from test.asynchronous.unified_format import MatchEvaluatorUtil, generate_test_classes +from test.asynchronous.unified_format import ( + MatchEvaluatorUtil, + generate_test_classes, + get_test_path, +) from bson import ObjectId _IS_SYNC = False # Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "unified-test-format") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "unified-test-format") - +TEST_PATH = get_test_path("unified-test-format") globals().update( generate_test_classes( diff --git a/test/asynchronous/test_versioned_api_integration.py b/test/asynchronous/test_versioned_api_integration.py index 0f6b544465..7228b945ab 100644 --- a/test/asynchronous/test_versioned_api_integration.py +++ b/test/asynchronous/test_versioned_api_integration.py @@ -16,7 +16,7 @@ import os import sys from pathlib import Path -from test.asynchronous.unified_format import generate_test_classes +from test.asynchronous.unified_format import generate_test_classes, get_test_path sys.path[0:0] = [""] @@ -27,15 +27,8 @@ _IS_SYNC = False -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "versioned-api") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "versioned-api") - - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("versioned-api"), module=__name__)) class TestServerApiIntegration(AsyncIntegrationTest): diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index 0c9e8c10c8..e308833367 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -29,6 +29,7 @@ import traceback from collections import defaultdict from inspect import iscoroutinefunction +from pathlib import Path from test.asynchronous import ( AsyncIntegrationTest, async_client_context, @@ -1564,6 +1565,14 @@ async def test_case(self): } +def get_test_path(*args): + if _IS_SYNC: + root_dir = Path(__file__).resolve().parent + else: + root_dir = Path(__file__).resolve().parent.parent + return os.path.join(root_dir, *args) + + def generate_test_classes( test_path, module=__name__, @@ -1596,10 +1605,12 @@ class SpecTestBase(with_metaclass(UnifiedSpecTestMeta)): # type: ignore return base + found_any = False for dirpath, _, filenames in os.walk(test_path): dirname = os.path.split(dirpath)[-1] for filename in filenames: + found_any = True fpath = os.path.join(dirpath, filename) with open(fpath) as scenario_stream: # Use tz_aware=False to match how CodecOptions decodes @@ -1637,4 +1648,7 @@ class SpecTestBase(with_metaclass(UnifiedSpecTestMeta)): # type: ignore continue raise + if not found_any: + raise ValueError(f"No test files found in {test_path}") + return test_klasses diff --git a/test/test_auth_oidc.py b/test/test_auth_oidc.py index 877a5ca981..1defe82006 100644 --- a/test/test_auth_oidc.py +++ b/test/test_auth_oidc.py @@ -30,7 +30,7 @@ sys.path[0:0] = [""] -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import EventListener, OvertCommandListener from bson import SON @@ -54,14 +54,13 @@ _IS_SYNC = True ROOT = Path(__file__).parent.parent.resolve() -TEST_PATH = ROOT / "auth" / "unified" ENVIRON = os.environ.get("OIDC_ENV", "test") DOMAIN = os.environ.get("OIDC_DOMAIN", "") TOKEN_DIR = os.environ.get("OIDC_TOKEN_DIR", "") TOKEN_FILE = os.environ.get("OIDC_TOKEN_FILE", "") # Generate unified tests. -globals().update(generate_test_classes(str(TEST_PATH), module=__name__)) +globals().update(generate_test_classes(get_test_path("auth", "unified"), module=__name__)) pytestmark = pytest.mark.auth_oidc diff --git a/test/test_auth_spec.py b/test/test_auth_spec.py index ac6411cd89..93c5e7666d 100644 --- a/test/test_auth_spec.py +++ b/test/test_auth_spec.py @@ -27,7 +27,7 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from pymongo import MongoClient from pymongo.auth_oidc_shared import OIDCCallback @@ -35,8 +35,7 @@ pytestmark = pytest.mark.auth _IS_SYNC = True - -_TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "auth") +_TEST_PATH = get_test_path("auth") class TestAuthSpec(PyMongoTestCase): diff --git a/test/test_change_stream.py b/test/test_change_stream.py index 0b2a9e76b8..f1d01458da 100644 --- a/test/test_change_stream.py +++ b/test/test_change_stream.py @@ -35,7 +35,7 @@ client_context, unittest, ) -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( AllowListEventListener, EventListener, @@ -1123,12 +1123,9 @@ def tearDown(self): self.listener.reset() -_TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "change_streams") - - globals().update( generate_test_classes( - os.path.join(_TEST_PATH, "unified"), + get_test_path("change_streams", "unified"), module=__name__, ) ) diff --git a/test/test_client_metadata.py b/test/test_client_metadata.py index a94c5aa25e..5f103f739a 100644 --- a/test/test_client_metadata.py +++ b/test/test_client_metadata.py @@ -19,7 +19,7 @@ import time import unittest from test import IntegrationTest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import CMAPListener from typing import Any, Optional @@ -40,16 +40,8 @@ _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "handshake", "unified") -else: - _TEST_PATH = os.path.join( - pathlib.Path(__file__).resolve().parent.parent, "handshake", "unified" - ) - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("handshake", "unified"), module=__name__)) def _get_handshake_driver_info(request): diff --git a/test/test_collection_management.py b/test/test_collection_management.py index 063c20df8f..deb43677a6 100644 --- a/test/test_collection_management.py +++ b/test/test_collection_management.py @@ -22,20 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "collection_management") -else: - _TEST_PATH = os.path.join( - pathlib.Path(__file__).resolve().parent.parent, "collection_management" - ) - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("collection_management"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/test_command_logging.py b/test/test_command_logging.py index cf865920ca..17bc319d9c 100644 --- a/test/test_command_logging.py +++ b/test/test_command_logging.py @@ -22,20 +22,13 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "command_logging") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "command_logging") - - globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("command_logging"), module=__name__, ) ) diff --git a/test/test_command_monitoring.py b/test/test_command_monitoring.py index 4f5ef06f28..eaa2af5ee8 100644 --- a/test/test_command_monitoring.py +++ b/test/test_command_monitoring.py @@ -22,20 +22,13 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "command_monitoring") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "command_monitoring") - - globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("command_monitoring"), module=__name__, ) ) diff --git a/test/test_connection_logging.py b/test/test_connection_logging.py index 253193cc43..9f5da0c436 100644 --- a/test/test_connection_logging.py +++ b/test/test_connection_logging.py @@ -22,20 +22,13 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "connection_logging") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "connection_logging") - - globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("connection_logging"), module=__name__, ) ) diff --git a/test/test_crud_unified.py b/test/test_crud_unified.py index 1b1abf3600..45af155bd7 100644 --- a/test/test_crud_unified.py +++ b/test/test_crud_unified.py @@ -22,18 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "crud", "unified") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "crud", "unified") - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("crud", "unified"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/test_csot.py b/test/test_csot.py index 981af1ed03..d6dec51d37 100644 --- a/test/test_csot.py +++ b/test/test_csot.py @@ -22,7 +22,7 @@ sys.path[0:0] = [""] from test import IntegrationTest, client_context, unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils import flaky import pymongo @@ -31,14 +31,8 @@ _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "csot") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "csot") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("csot"), module=__name__)) class TestCSOT(IntegrationTest): diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index 67a82996bd..8375d63e97 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -40,7 +40,7 @@ unittest, ) from test.pymongo_mocks import DummyMonitor -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils import ( get_pool, ) @@ -76,14 +76,7 @@ _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - SDAM_PATH = os.path.join(Path(__file__).resolve().parent, "discovery_and_monitoring") -else: - SDAM_PATH = os.path.join( - Path(__file__).resolve().parent.parent, - "discovery_and_monitoring", - ) +SDAM_PATH = get_test_path("discovery_and_monitoring") def create_mock_topology(uri, monitor_class=DummyMonitor): diff --git a/test/test_encryption.py b/test/test_encryption.py index b0f046d63f..4da307689c 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -65,7 +65,7 @@ LOCAL_MASTER_KEY, ) from test.test_bulk import BulkTestBase -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( AllowListEventListener, OvertCommandListener, @@ -273,11 +273,7 @@ def unmanaged_create_client_encryption( # Location of JSON test files. -if _IS_SYNC: - BASE = os.path.join(pathlib.Path(__file__).resolve().parent, "client-side-encryption") -else: - BASE = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "client-side-encryption") - +BASE = get_test_path("client-side-encryption") SPEC_PATH = os.path.join(BASE, "spec") OPTS = CodecOptions() diff --git a/test/test_gridfs_spec.py b/test/test_gridfs_spec.py index e84e19725e..8e1a37364e 100644 --- a/test/test_gridfs_spec.py +++ b/test/test_gridfs_spec.py @@ -22,18 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "gridfs") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "gridfs") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("gridfs"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/test_index_management.py b/test/test_index_management.py index dea8c0e2be..2d723bb4a3 100644 --- a/test/test_index_management.py +++ b/test/test_index_management.py @@ -28,7 +28,7 @@ sys.path[0:0] = [""] from test import IntegrationTest, PyMongoTestCase, unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import AllowListEventListener, OvertCommandListener from pymongo.errors import OperationFailure @@ -40,12 +40,6 @@ pytestmark = pytest.mark.search_index -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "index_management") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "index_management") - _NAME = "test-search-index" @@ -370,7 +364,7 @@ def test_case_7(self): globals().update( generate_test_classes( - _TEST_PATH, + get_test_path("index_management"), module=__name__, ) ) diff --git a/test/test_load_balancer.py b/test/test_load_balancer.py index 472ef51da3..41663d988b 100644 --- a/test/test_load_balancer.py +++ b/test/test_load_balancer.py @@ -30,7 +30,7 @@ sys.path[0:0] = [""] from test import IntegrationTest, client_context, unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( create_event, wait_until, @@ -40,14 +40,8 @@ pytestmark = pytest.mark.load_balancer -# Location of JSON test specifications. -if _IS_SYNC: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "load_balancer") -else: - _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "load_balancer") - # Generate unified tests. -globals().update(generate_test_classes(_TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("load_balancer"), module=__name__)) class TestLB(IntegrationTest): diff --git a/test/test_read_write_concern_spec.py b/test/test_read_write_concern_spec.py index 4b816b7af9..54946c3ea0 100644 --- a/test/test_read_write_concern_spec.py +++ b/test/test_read_write_concern_spec.py @@ -24,7 +24,7 @@ sys.path[0:0] = [""] from test import IntegrationTest, client_context, unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import OvertCommandListener from pymongo import DESCENDING @@ -42,11 +42,7 @@ _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "read_write_concern") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "read_write_concern") +TEST_PATH = get_test_path("read_write_concern") class TestReadWriteConcernSpec(IntegrationTest): diff --git a/test/test_retryable_reads_unified.py b/test/test_retryable_reads_unified.py index b1c6435c9a..c47d89d045 100644 --- a/test/test_retryable_reads_unified.py +++ b/test/test_retryable_reads_unified.py @@ -22,21 +22,15 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "retryable_reads/unified") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "retryable_reads/unified") - # Generate unified tests. # PyMongo does not support MapReduce, ListDatabaseObjects or ListCollectionObjects. globals().update( generate_test_classes( - TEST_PATH, + get_test_path("retryable_reads", "unified"), module=__name__, expected_failures=["ListDatabaseObjects .*", "ListCollectionObjects .*", "MapReduce .*"], ) diff --git a/test/test_retryable_writes_unified.py b/test/test_retryable_writes_unified.py index 036c410e24..d06ee206fc 100644 --- a/test/test_retryable_writes_unified.py +++ b/test/test_retryable_writes_unified.py @@ -22,18 +22,14 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "retryable_writes/unified") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "retryable_writes/unified") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update( + generate_test_classes(get_test_path("retryable_writes", "unified"), module=__name__) +) if __name__ == "__main__": unittest.main() diff --git a/test/test_run_command.py b/test/test_run_command.py index d2ef43b97e..df835fb6d7 100644 --- a/test/test_run_command.py +++ b/test/test_run_command.py @@ -18,20 +18,13 @@ import os import unittest from pathlib import Path -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "run_command") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "run_command") - - globals().update( generate_test_classes( - os.path.join(TEST_PATH, "unified"), + get_test_path("run_command", "unified"), module=__name__, ) ) diff --git a/test/test_server_selection_logging.py b/test/test_server_selection_logging.py index d53d8dc84f..c48e166a19 100644 --- a/test/test_server_selection_logging.py +++ b/test/test_server_selection_logging.py @@ -22,20 +22,14 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "server_selection_logging") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "server_selection_logging") - globals().update( generate_test_classes( - TEST_PATH, + get_test_path("server_selection_logging"), module=__name__, ) ) diff --git a/test/test_sessions_unified.py b/test/test_sessions_unified.py index 3c80c70d38..3d15fac85f 100644 --- a/test/test_sessions_unified.py +++ b/test/test_sessions_unified.py @@ -22,19 +22,12 @@ sys.path[0:0] = [""] from test import unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "sessions") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "sessions") - - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("sessions"), module=__name__)) if __name__ == "__main__": unittest.main() diff --git a/test/test_transactions_unified.py b/test/test_transactions_unified.py index 4ab4885e2a..05e4a1e5c3 100644 --- a/test/test_transactions_unified.py +++ b/test/test_transactions_unified.py @@ -22,7 +22,7 @@ sys.path[0:0] = [""] from test import client_context, unittest -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True @@ -31,25 +31,13 @@ def setUpModule(): pass -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "transactions/unified") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "transactions/unified") - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) - -# Location of JSON test specifications for transactions-convenient-api. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "transactions-convenient-api/unified") -else: - TEST_PATH = os.path.join( - Path(__file__).resolve().parent.parent, "transactions-convenient-api/unified" - ) +globals().update(generate_test_classes(get_test_path("transactions/unified"), module=__name__)) # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update( + generate_test_classes(get_test_path("transactions-convenient-api/unified"), module=__name__) +) if __name__ == "__main__": unittest.main() diff --git a/test/test_unified_format.py b/test/test_unified_format.py index f1cfd0139b..a55f810473 100644 --- a/test/test_unified_format.py +++ b/test/test_unified_format.py @@ -21,18 +21,18 @@ sys.path[0:0] = [""] from test import UnitTest, unittest -from test.unified_format import MatchEvaluatorUtil, generate_test_classes +from test.unified_format import ( + MatchEvaluatorUtil, + generate_test_classes, + get_test_path, +) from bson import ObjectId _IS_SYNC = True # Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "unified-test-format") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "unified-test-format") - +TEST_PATH = get_test_path("unified-test-format") globals().update( generate_test_classes( diff --git a/test/test_versioned_api_integration.py b/test/test_versioned_api_integration.py index 066a1935ca..c4ee7856f3 100644 --- a/test/test_versioned_api_integration.py +++ b/test/test_versioned_api_integration.py @@ -16,7 +16,7 @@ import os import sys from pathlib import Path -from test.unified_format import generate_test_classes +from test.unified_format import generate_test_classes, get_test_path sys.path[0:0] = [""] @@ -27,15 +27,8 @@ _IS_SYNC = True -# Location of JSON test specifications. -if _IS_SYNC: - TEST_PATH = os.path.join(Path(__file__).resolve().parent, "versioned-api") -else: - TEST_PATH = os.path.join(Path(__file__).resolve().parent.parent, "versioned-api") - - # Generate unified tests. -globals().update(generate_test_classes(TEST_PATH, module=__name__)) +globals().update(generate_test_classes(get_test_path("versioned-api"), module=__name__)) class TestServerApiIntegration(IntegrationTest): diff --git a/test/unified_format.py b/test/unified_format.py index 0c5f68edd3..277783a9a3 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -29,6 +29,7 @@ import traceback from collections import defaultdict from inspect import iscoroutinefunction +from pathlib import Path from test import ( IntegrationTest, client_context, @@ -1549,6 +1550,14 @@ def test_case(self): } +def get_test_path(*args): + if _IS_SYNC: + root_dir = Path(__file__).resolve().parent + else: + root_dir = Path(__file__).resolve().parent.parent + return os.path.join(root_dir, *args) + + def generate_test_classes( test_path, module=__name__, @@ -1581,10 +1590,12 @@ class SpecTestBase(with_metaclass(UnifiedSpecTestMeta)): # type: ignore return base + found_any = False for dirpath, _, filenames in os.walk(test_path): dirname = os.path.split(dirpath)[-1] for filename in filenames: + found_any = True fpath = os.path.join(dirpath, filename) with open(fpath) as scenario_stream: # Use tz_aware=False to match how CodecOptions decodes @@ -1622,4 +1633,7 @@ class SpecTestBase(with_metaclass(UnifiedSpecTestMeta)): # type: ignore continue raise + if not found_any: + raise ValueError(f"No test files found in {test_path}") + return test_klasses From 029c74cb3a2278ee14299437aa3f92222b2c8f9d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 16 Dec 2025 13:32:40 -0600 Subject: [PATCH 042/129] PYTHON-5670 Restore minimal support for Python 3.9 (#2640) --- .evergreen/generated_configs/tasks.yml | 92 +++++++++++++++++++++ .evergreen/generated_configs/variants.yml | 8 ++ .evergreen/scripts/generate_config.py | 24 ++++++ .evergreen/scripts/generate_config_utils.py | 1 + .github/workflows/dist.yml | 3 +- .github/workflows/test-python.yml | 6 +- CONTRIBUTING.md | 2 +- README.md | 2 +- doc/changelog.rst | 7 +- gridfs/asynchronous/grid_file.py | 1 + gridfs/synchronous/grid_file.py | 1 + pymongo/asynchronous/cursor.py | 1 + pymongo/asynchronous/helpers.py | 16 ++++ pymongo/synchronous/cursor.py | 1 + pymongo/synchronous/helpers.py | 16 ++++ test/asynchronous/test_change_stream.py | 1 + test/asynchronous/test_client.py | 1 + test/asynchronous/test_collation.py | 1 + test/asynchronous/test_collection.py | 1 + test/asynchronous/test_cursor.py | 1 + test/asynchronous/test_custom_types.py | 1 + test/asynchronous/test_database.py | 1 + test/asynchronous/test_encryption.py | 1 + test/asynchronous/test_examples.py | 1 + test/asynchronous/test_grid_file.py | 1 + test/asynchronous/test_load_balancer.py | 2 + test/asynchronous/test_monitoring.py | 1 + test/asynchronous/test_read_preferences.py | 1 + test/asynchronous/test_session.py | 1 + test/asynchronous/test_transactions.py | 1 + test/asynchronous/unified_format.py | 1 + test/test_change_stream.py | 1 + test/test_client.py | 1 + test/test_collation.py | 1 + test/test_collection.py | 1 + test/test_cursor.py | 1 + test/test_custom_types.py | 1 + test/test_database.py | 1 + test/test_encryption.py | 1 + test/test_examples.py | 1 + test/test_grid_file.py | 1 + test/test_load_balancer.py | 2 + test/test_monitoring.py | 1 + test/test_read_preferences.py | 1 + test/test_session.py | 1 + test/test_transactions.py | 1 + test/unified_format.py | 1 + 47 files changed, 204 insertions(+), 11 deletions(-) diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index e3f5492ac9..6e75170ec9 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -242,6 +242,98 @@ tasks: TEST_MIN_DEPS: "1" tags: [test-min-deps, sharded_cluster-auth-ssl] + # Min support tests + - name: test-min-support-python3.9-standalone-noauth-nossl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: "3.9" + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone + tags: [test-min-support] + - name: test-min-support-python3.9-replica-set-noauth-ssl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: "3.9" + AUTH: noauth + SSL: ssl + TOPOLOGY: replica_set + tags: [test-min-support] + - name: test-min-support-python3.9-sharded-cluster-auth-ssl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: "3.9" + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + tags: [test-min-support] + - name: test-min-support-pypy3.9-standalone-noauth-nossl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: pypy3.9 + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone + tags: [test-min-support] + - name: test-min-support-pypy3.9-replica-set-noauth-ssl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: pypy3.9 + AUTH: noauth + SSL: ssl + TOPOLOGY: replica_set + tags: [test-min-support] + - name: test-min-support-pypy3.9-sharded-cluster-auth-ssl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: pypy3.9 + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + tags: [test-min-support] + - name: test-min-support-pypy3.10-standalone-noauth-nossl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: pypy3.10 + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone + tags: [test-min-support] + - name: test-min-support-pypy3.10-replica-set-noauth-ssl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: pypy3.10 + AUTH: noauth + SSL: ssl + TOPOLOGY: replica_set + tags: [test-min-support] + - name: test-min-support-pypy3.10-sharded-cluster-auth-ssl + commands: + - func: run server + - func: run tests + vars: + UV_PYTHON: pypy3.10 + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + tags: [test-min-support] + # Mod wsgi tests - name: mod-wsgi-replica-set-python3.10 commands: diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 8261f08236..a0f2025bbd 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -326,6 +326,14 @@ buildvariants: expansions: TEST_NAME: load_balancer + # Min support tests + - name: min-support-rhel8 + tasks: + - name: .test-min-support + display_name: Min Support RHEL8 + run_on: + - rhel87-small + # Mockupdb tests - name: mockupdb-rhel8 tasks: diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 5937d72964..2d9c478d79 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -502,6 +502,12 @@ def create_aws_auth_variants(): return variants +def create_min_support_variants(): + host = HOSTS["rhel8"] + name = get_variant_name("Min Support", host=host) + return [create_variant([".test-min-support"], name, host=host)] + + def create_no_server_variants(): host = HOSTS["rhel8"] name = get_variant_name("No server", host=host) @@ -897,6 +903,24 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name): return tasks +def create_min_support_tasks(): + server_func = FunctionCall(func="run server") + from generate_config_utils import MIN_SUPPORT_VERSIONS + + tasks = [] + for python, topology in product(MIN_SUPPORT_VERSIONS, TOPOLOGIES): + auth, ssl = get_standard_auth_ssl(topology) + vars = dict(UV_PYTHON=python, AUTH=auth, SSL=ssl, TOPOLOGY=topology) + test_func = FunctionCall(func="run tests", vars=vars) + task_name = get_task_name( + "test-min-support", python=python, topology=topology, auth=auth, ssl=ssl + ) + tags = ["test-min-support"] + commands = [server_func, test_func] + tasks.append(EvgTask(name=task_name, tags=tags, commands=commands)) + return tasks + + def create_aws_lambda_tasks(): assume_func = FunctionCall(func="assume ec2 role") vars = dict(TEST_NAME="aws_lambda") diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index e098ff8008..b87286f39d 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -24,6 +24,7 @@ ALL_VERSIONS = ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"] CPYTHONS = ["3.10", "3.11", "3.12", "3.13", "3.14t", "3.14"] PYPYS = ["pypy3.11"] +MIN_SUPPORT_VERSIONS = ["3.9", "pypy3.9", "pypy3.10"] ALL_PYTHONS = CPYTHONS + PYPYS MIN_MAX_PYTHON = [CPYTHONS[0], CPYTHONS[-1]] BATCHTIME_WEEK = 10080 diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 2d36211dac..645cb74de2 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -83,6 +83,7 @@ jobs: - name: Assert all versions in wheelhouse if: ${{ ! startsWith(matrix.buildplat[1], 'macos') }} run: | + ls wheelhouse/*cp39*.whl ls wheelhouse/*cp310*.whl ls wheelhouse/*cp311*.whl ls wheelhouse/*cp312*.whl @@ -110,7 +111,7 @@ jobs: - uses: actions/setup-python@v6 with: # Build sdist on lowest supported Python - python-version: "3.10" + python-version: "3.9" - name: Build SDist run: | diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index c35fad7c35..3d32a852a2 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -208,7 +208,7 @@ jobs: cache: 'pip' cache-dependency-path: 'pyproject.toml' # Build sdist on lowest supported Python - python-version: "3.10" + python-version: "3.9" - name: Build SDist shell: bash run: | @@ -242,7 +242,7 @@ jobs: cache: 'pip' cache-dependency-path: 'sdist/test/pyproject.toml' # Test sdist on lowest supported Python - python-version: "3.10" + python-version: "3.9" - id: setup-mongodb uses: mongodb-labs/drivers-evergreen-tools@master - name: Run connect test from sdist @@ -266,7 +266,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 with: - python-version: "3.10" + python-version: "3.9" - id: setup-mongodb uses: mongodb-labs/drivers-evergreen-tools@master with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec2b6ab1e5..eaf05111de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ be of interest or that has already been addressed. ## Supported Interpreters -PyMongo supports CPython 3.10+ and PyPy3.10+. Language features not +PyMongo supports CPython 3.9+ and PyPy3.9+. Language features not supported by all interpreters can not be used. ## Style Guide diff --git a/README.md b/README.md index b8bfa294a6..c807733e5b 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ package that is incompatible with PyMongo. ## Dependencies -PyMongo supports CPython 3.10+ and PyPy3.10+. +PyMongo supports CPython 3.9+ and PyPy3.9+. Required dependencies: diff --git a/doc/changelog.rst b/doc/changelog.rst index e205f2cc12..a2a8f6c211 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,14 +6,11 @@ Changes in Version 4.16.0 (XXXX/XX/XX) PyMongo 4.16 brings a number of changes including: -.. warning:: PyMongo 4.16 drops support for Python 3.9 and PyPy 3.10: Python 3.10+ or PyPy 3.11+ is now required. - -- Dropped support for Python 3.9 and PyPy 3.10. - Removed invalid documents from :class:`bson.errors.InvalidDocument` error messages as doing so may leak sensitive user data. Instead, invalid documents are stored in :attr:`bson.errors.InvalidDocument.document`. -- PyMongo now requires ``dnspython>=2.6.1``, since ``dnspython`` 1.0 is no longer maintained and is incompatible with - Python 3.10+. The minimum version is ``2.6.1`` to account for `CVE-2023-29483 `_. +- PyMongo now requires ``dnspython>=2.6.1``, since ``dnspython`` 1.0 is no longer maintained. + The minimum version is ``2.6.1`` to account for `CVE-2023-29483 `_. - Removed support for Eventlet. Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. - Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions. diff --git a/gridfs/asynchronous/grid_file.py b/gridfs/asynchronous/grid_file.py index 69a2200d3b..e512f796a8 100644 --- a/gridfs/asynchronous/grid_file.py +++ b/gridfs/asynchronous/grid_file.py @@ -46,6 +46,7 @@ from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.cursor import AsyncCursor from pymongo.asynchronous.database import AsyncDatabase +from pymongo.asynchronous.helpers import anext from pymongo.common import validate_string from pymongo.errors import ( BulkWriteError, diff --git a/gridfs/synchronous/grid_file.py b/gridfs/synchronous/grid_file.py index 7364aedda3..70a4f80774 100644 --- a/gridfs/synchronous/grid_file.py +++ b/gridfs/synchronous/grid_file.py @@ -57,6 +57,7 @@ from pymongo.synchronous.collection import Collection from pymongo.synchronous.cursor import Cursor from pymongo.synchronous.database import Database +from pymongo.synchronous.helpers import next _IS_SYNC = True diff --git a/pymongo/asynchronous/cursor.py b/pymongo/asynchronous/cursor.py index f19d3f6cee..67494192fe 100644 --- a/pymongo/asynchronous/cursor.py +++ b/pymongo/asynchronous/cursor.py @@ -37,6 +37,7 @@ from bson.code import Code from bson.son import SON from pymongo import _csot, helpers_shared +from pymongo.asynchronous.helpers import anext from pymongo.collation import validate_collation_or_none from pymongo.common import ( validate_is_document_type, diff --git a/pymongo/asynchronous/helpers.py b/pymongo/asynchronous/helpers.py index 4a8c918133..ccda16e28b 100644 --- a/pymongo/asynchronous/helpers.py +++ b/pymongo/asynchronous/helpers.py @@ -16,7 +16,9 @@ from __future__ import annotations import asyncio +import builtins import socket +import sys from typing import ( Any, Callable, @@ -84,3 +86,17 @@ async def _getaddrinfo( return await loop.getaddrinfo(host, port, **kwargs) # type: ignore[return-value] else: return socket.getaddrinfo(host, port, **kwargs) + + +if sys.version_info >= (3, 10): + anext = builtins.anext + aiter = builtins.aiter +else: + + async def anext(cls: Any) -> Any: + """Compatibility function until we drop 3.9 support: https://docs.python.org/3/library/functions.html#anext.""" + return await cls.__anext__() + + def aiter(cls: Any) -> Any: + """Compatibility function until we drop 3.9 support: https://docs.python.org/3/library/functions.html#anext.""" + return cls.__aiter__() diff --git a/pymongo/synchronous/cursor.py b/pymongo/synchronous/cursor.py index fcd8ebeb1d..d1e9731d99 100644 --- a/pymongo/synchronous/cursor.py +++ b/pymongo/synchronous/cursor.py @@ -55,6 +55,7 @@ _RawBatchQuery, ) from pymongo.response import PinnedResponse +from pymongo.synchronous.helpers import next from pymongo.typings import _Address, _CollationIn, _DocumentOut, _DocumentType from pymongo.write_concern import validate_boolean diff --git a/pymongo/synchronous/helpers.py b/pymongo/synchronous/helpers.py index c1b75a3c95..1fff9a0f23 100644 --- a/pymongo/synchronous/helpers.py +++ b/pymongo/synchronous/helpers.py @@ -16,7 +16,9 @@ from __future__ import annotations import asyncio +import builtins import socket +import sys from typing import ( Any, Callable, @@ -84,3 +86,17 @@ def _getaddrinfo( return loop.getaddrinfo(host, port, **kwargs) # type: ignore[return-value] else: return socket.getaddrinfo(host, port, **kwargs) + + +if sys.version_info >= (3, 10): + next = builtins.next + iter = builtins.iter +else: + + def next(cls: Any) -> Any: + """Compatibility function until we drop 3.9 support: https://docs.python.org/3/library/functions.html#next.""" + return cls.__next__() + + def iter(cls: Any) -> Any: + """Compatibility function until we drop 3.9 support: https://docs.python.org/3/library/functions.html#next.""" + return cls.__iter__() diff --git a/test/asynchronous/test_change_stream.py b/test/asynchronous/test_change_stream.py index 3a34319eaa..2e1f83884b 100644 --- a/test/asynchronous/test_change_stream.py +++ b/test/asynchronous/test_change_stream.py @@ -48,6 +48,7 @@ from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument from pymongo import AsyncMongoClient from pymongo.asynchronous.command_cursor import AsyncCommandCursor +from pymongo.asynchronous.helpers import anext from pymongo.errors import ( InvalidOperation, OperationFailure, diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index 15b32c4e99..37fb239237 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -92,6 +92,7 @@ from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.cursor import AsyncCursor, CursorType from pymongo.asynchronous.database import AsyncDatabase +from pymongo.asynchronous.helpers import anext from pymongo.asynchronous.mongo_client import AsyncMongoClient from pymongo.asynchronous.pool import ( AsyncConnection, diff --git a/test/asynchronous/test_collation.py b/test/asynchronous/test_collation.py index da810a2a9f..05e548c79e 100644 --- a/test/asynchronous/test_collation.py +++ b/test/asynchronous/test_collation.py @@ -21,6 +21,7 @@ from test.utils_shared import EventListener, OvertCommandListener from typing import Any +from pymongo.asynchronous.helpers import anext from pymongo.collation import ( Collation, CollationAlternate, diff --git a/test/asynchronous/test_collection.py b/test/asynchronous/test_collection.py index 498563fe83..3232650487 100644 --- a/test/asynchronous/test_collection.py +++ b/test/asynchronous/test_collection.py @@ -25,6 +25,7 @@ from typing import Any, Iterable, no_type_check from pymongo.asynchronous.database import AsyncDatabase +from pymongo.asynchronous.helpers import anext sys.path[0:0] = [""] diff --git a/test/asynchronous/test_cursor.py b/test/asynchronous/test_cursor.py index 906f78cc97..08da82762c 100644 --- a/test/asynchronous/test_cursor.py +++ b/test/asynchronous/test_cursor.py @@ -46,6 +46,7 @@ from bson.raw_bson import RawBSONDocument from pymongo import ASCENDING, DESCENDING from pymongo.asynchronous.cursor import AsyncCursor, CursorType +from pymongo.asynchronous.helpers import anext from pymongo.collation import Collation from pymongo.errors import ExecutionTimeout, InvalidOperation, OperationFailure, PyMongoError from pymongo.operations import _IndexList diff --git a/test/asynchronous/test_custom_types.py b/test/asynchronous/test_custom_types.py index f8fa51ba76..82c54512cc 100644 --- a/test/asynchronous/test_custom_types.py +++ b/test/asynchronous/test_custom_types.py @@ -53,6 +53,7 @@ from bson.int64 import Int64 from bson.raw_bson import RawBSONDocument from pymongo.asynchronous.collection import ReturnDocument +from pymongo.asynchronous.helpers import anext from pymongo.errors import DuplicateKeyError from pymongo.message import _CursorAddress diff --git a/test/asynchronous/test_database.py b/test/asynchronous/test_database.py index c1edb7a296..3afdc230ef 100644 --- a/test/asynchronous/test_database.py +++ b/test/asynchronous/test_database.py @@ -42,6 +42,7 @@ from pymongo.asynchronous import auth from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.database import AsyncDatabase +from pymongo.asynchronous.helpers import anext from pymongo.asynchronous.mongo_client import AsyncMongoClient from pymongo.errors import ( CollectionInvalid, diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 3f71b35e3d..b1dbc73f39 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -84,6 +84,7 @@ from pymongo import ReadPreference from pymongo.asynchronous import encryption from pymongo.asynchronous.encryption import Algorithm, AsyncClientEncryption, QueryType +from pymongo.asynchronous.helpers import anext from pymongo.asynchronous.mongo_client import AsyncMongoClient from pymongo.cursor_shared import CursorType from pymongo.encryption_options import _HAVE_PYMONGOCRYPT, AutoEncryptionOpts, RangeOpts, TextOpts diff --git a/test/asynchronous/test_examples.py b/test/asynchronous/test_examples.py index 21770f490c..dd27623654 100644 --- a/test/asynchronous/test_examples.py +++ b/test/asynchronous/test_examples.py @@ -29,6 +29,7 @@ from test.utils_shared import async_wait_until import pymongo +from pymongo.asynchronous.helpers import anext from pymongo.errors import ConnectionFailure, OperationFailure from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference diff --git a/test/asynchronous/test_grid_file.py b/test/asynchronous/test_grid_file.py index 2a7e9e1f9d..f3ca596142 100644 --- a/test/asynchronous/test_grid_file.py +++ b/test/asynchronous/test_grid_file.py @@ -47,6 +47,7 @@ ) from gridfs.errors import NoFile from pymongo import AsyncMongoClient +from pymongo.asynchronous.helpers import aiter, anext from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError from pymongo.message import _CursorAddress diff --git a/test/asynchronous/test_load_balancer.py b/test/asynchronous/test_load_balancer.py index 8e1ee3e797..8c5d57434c 100644 --- a/test/asynchronous/test_load_balancer.py +++ b/test/asynchronous/test_load_balancer.py @@ -36,6 +36,8 @@ create_async_event, ) +from pymongo.asynchronous.helpers import anext + _IS_SYNC = False pytestmark = pytest.mark.load_balancer diff --git a/test/asynchronous/test_monitoring.py b/test/asynchronous/test_monitoring.py index 6a9a5b8da7..9b2a3691eb 100644 --- a/test/asynchronous/test_monitoring.py +++ b/test/asynchronous/test_monitoring.py @@ -40,6 +40,7 @@ from bson.son import SON from pymongo import CursorType, DeleteOne, InsertOne, UpdateOne, monitoring from pymongo.asynchronous.command_cursor import AsyncCommandCursor +from pymongo.asynchronous.helpers import anext from pymongo.errors import AutoReconnect, NotPrimaryError, OperationFailure from pymongo.read_preferences import ReadPreference from pymongo.write_concern import WriteConcern diff --git a/test/asynchronous/test_read_preferences.py b/test/asynchronous/test_read_preferences.py index d18887da40..72dd809db0 100644 --- a/test/asynchronous/test_read_preferences.py +++ b/test/asynchronous/test_read_preferences.py @@ -42,6 +42,7 @@ from test.version import Version from bson.son import SON +from pymongo.asynchronous.helpers import anext from pymongo.asynchronous.mongo_client import AsyncMongoClient from pymongo.errors import ConfigurationError, OperationFailure from pymongo.message import _maybe_add_read_preference diff --git a/test/asynchronous/test_session.py b/test/asynchronous/test_session.py index ff0feebafc..19ce868c56 100644 --- a/test/asynchronous/test_session.py +++ b/test/asynchronous/test_session.py @@ -48,6 +48,7 @@ from pymongo import ASCENDING, AsyncMongoClient, _csot, monitoring from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.cursor import AsyncCursor +from pymongo.asynchronous.helpers import anext from pymongo.common import _MAX_END_SESSIONS from pymongo.errors import ConfigurationError, InvalidOperation, OperationFailure from pymongo.operations import IndexModel, InsertOne, UpdateOne diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 23f121fafd..4fc20fba3b 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -39,6 +39,7 @@ from pymongo.asynchronous.client_session import TransactionOptions from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.cursor import AsyncCursor +from pymongo.asynchronous.helpers import anext from pymongo.errors import ( AutoReconnect, CollectionInvalid, diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index e308833367..bca259b826 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -78,6 +78,7 @@ from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.database import AsyncDatabase from pymongo.asynchronous.encryption import AsyncClientEncryption +from pymongo.asynchronous.helpers import anext from pymongo.driver_info import DriverInfo from pymongo.encryption_options import _HAVE_PYMONGOCRYPT, AutoEncryptionOpts from pymongo.errors import ( diff --git a/test/test_change_stream.py b/test/test_change_stream.py index f1d01458da..792b39cc29 100644 --- a/test/test_change_stream.py +++ b/test/test_change_stream.py @@ -55,6 +55,7 @@ from pymongo.message import _CursorAddress from pymongo.read_concern import ReadConcern from pymongo.synchronous.command_cursor import CommandCursor +from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern _IS_SYNC = True diff --git a/test/test_client.py b/test/test_client.py index d07ce32365..2d30eb1bd2 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -114,6 +114,7 @@ from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.cursor import Cursor, CursorType from pymongo.synchronous.database import Database +from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.synchronous.pool import ( Connection, diff --git a/test/test_collation.py b/test/test_collation.py index 903f24a228..5425551dc6 100644 --- a/test/test_collation.py +++ b/test/test_collation.py @@ -37,6 +37,7 @@ UpdateMany, UpdateOne, ) +from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern _IS_SYNC = True diff --git a/test/test_collection.py b/test/test_collection.py index 18be309f22..ac469782e9 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -25,6 +25,7 @@ from typing import Any, Iterable, no_type_check from pymongo.synchronous.database import Database +from pymongo.synchronous.helpers import next sys.path[0:0] = [""] diff --git a/test/test_cursor.py b/test/test_cursor.py index 219ca396c9..b63638bfab 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -51,6 +51,7 @@ from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference from pymongo.synchronous.cursor import Cursor, CursorType +from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern _IS_SYNC = True diff --git a/test/test_custom_types.py b/test/test_custom_types.py index 02f3127165..aba6b55119 100644 --- a/test/test_custom_types.py +++ b/test/test_custom_types.py @@ -55,6 +55,7 @@ from pymongo.errors import DuplicateKeyError from pymongo.message import _CursorAddress from pymongo.synchronous.collection import ReturnDocument +from pymongo.synchronous.helpers import next _IS_SYNC = True diff --git a/test/test_database.py b/test/test_database.py index 25c3275238..20ac55b6eb 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -51,6 +51,7 @@ from pymongo.synchronous import auth from pymongo.synchronous.collection import Collection from pymongo.synchronous.database import Database +from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern diff --git a/test/test_encryption.py b/test/test_encryption.py index 4da307689c..88d37cfa0d 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -100,6 +100,7 @@ from pymongo.operations import InsertOne, ReplaceOne, UpdateOne from pymongo.synchronous import encryption from pymongo.synchronous.encryption import Algorithm, ClientEncryption, QueryType +from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern diff --git a/test/test_examples.py b/test/test_examples.py index 266e32e8d4..13f0c94c56 100644 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -33,6 +33,7 @@ from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference from pymongo.server_api import ServerApi +from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern _IS_SYNC = True diff --git a/test/test_grid_file.py b/test/test_grid_file.py index c7ccda44a4..6fe209f438 100644 --- a/test/test_grid_file.py +++ b/test/test_grid_file.py @@ -49,6 +49,7 @@ from pymongo import MongoClient from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError from pymongo.message import _CursorAddress +from pymongo.synchronous.helpers import iter, next _IS_SYNC = True diff --git a/test/test_load_balancer.py b/test/test_load_balancer.py index 41663d988b..de4b14e546 100644 --- a/test/test_load_balancer.py +++ b/test/test_load_balancer.py @@ -36,6 +36,8 @@ wait_until, ) +from pymongo.synchronous.helpers import next + _IS_SYNC = True pytestmark = pytest.mark.load_balancer diff --git a/test/test_monitoring.py b/test/test_monitoring.py index f5a18af9ed..7cb93adf81 100644 --- a/test/test_monitoring.py +++ b/test/test_monitoring.py @@ -42,6 +42,7 @@ from pymongo.errors import AutoReconnect, NotPrimaryError, OperationFailure from pymongo.read_preferences import ReadPreference from pymongo.synchronous.command_cursor import CommandCursor +from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern _IS_SYNC = True diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index 084abdf3e1..afde01723d 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -56,6 +56,7 @@ from pymongo.server_description import ServerDescription from pymongo.server_selectors import Selection, readable_server_selector from pymongo.server_type import SERVER_TYPE +from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern diff --git a/test/test_session.py b/test/test_session.py index 9aa56a711e..40d0a53afb 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -52,6 +52,7 @@ from pymongo.read_concern import ReadConcern from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.cursor import Cursor +from pymongo.synchronous.helpers import next _IS_SYNC = True diff --git a/test/test_transactions.py b/test/test_transactions.py index feefcada2a..01b7ba1553 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -50,6 +50,7 @@ from pymongo.synchronous.client_session import TransactionOptions from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.cursor import Cursor +from pymongo.synchronous.helpers import next _IS_SYNC = True diff --git a/test/unified_format.py b/test/unified_format.py index 277783a9a3..06c6abd004 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -102,6 +102,7 @@ from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.database import Database from pymongo.synchronous.encryption import ClientEncryption +from pymongo.synchronous.helpers import next from pymongo.topology_description import TopologyDescription from pymongo.typings import _Address from pymongo.write_concern import WriteConcern From 1e78bd4d465cf117efaa038bf4064e2b1efb6a56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:33:40 -0600 Subject: [PATCH 043/129] Bump mypy from 1.19.0 to 1.19.1 (#2652) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester Co-authored-by: Jib --- pyproject.toml | 2 +- uv.lock | 84 +++++++++++++++++++++++++------------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c2832c09e..f6318b8ff6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ mockupdb = [ ] perf = ["simplejson>=3.17.0"] typing = [ - "mypy==1.19.0", + "mypy==1.19.1", "pyright==1.1.407", "typing_extensions", "pip" diff --git a/uv.lock b/uv.lock index a182e983b7..d2a05d7088 100644 --- a/uv.lock +++ b/uv.lock @@ -1130,54 +1130,54 @@ dependencies = [ [[package]] name = "mypy" -version = "1.19.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "librt" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" }, - { url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" }, - { url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" }, - { url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" }, - { url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" }, - { url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, - { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, - { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, - { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, - { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, - { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, - { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, - { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, - { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, - { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, - { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, - { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, - { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, - { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, - { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, - { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, - { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, - { url = "https://files.pythonhosted.org/packages/b4/59/a7748ef43446163a93159d82bb270c6c4f3d94c1fcbdd2a29a7e439e74d7/mypy-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0dde5cb375cb94deff0d4b548b993bec52859d1651e073d63a1386d392a95495", size = 13094255, upload-time = "2025-11-28T15:47:14.282Z" }, - { url = "https://files.pythonhosted.org/packages/f5/0b/92ebf5abc83f559a35dcba3bd9227726b04b04178f1e521f38e647b930eb/mypy-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1cf9c59398db1c68a134b0b5354a09a1e124523f00bacd68e553b8bd16ff3299", size = 12161414, upload-time = "2025-11-28T15:45:03.302Z" }, - { url = "https://files.pythonhosted.org/packages/aa/03/19412f0a786722055a52c01b4c5d71e5b5443a89f6bbcdd445408240e217/mypy-1.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3210d87b30e6af9c8faed61be2642fcbe60ef77cec64fa1ef810a630a4cf671c", size = 12756782, upload-time = "2025-11-28T15:46:49.522Z" }, - { url = "https://files.pythonhosted.org/packages/cb/85/395d53c9098b251414b0448cdadcd3277523ff36f5abda6d26ff945dbdb3/mypy-1.19.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2c1101ab41d01303103ab6ef82cbbfedb81c1a060c868fa7cc013d573d37ab5", size = 13503492, upload-time = "2025-11-28T15:48:57.339Z" }, - { url = "https://files.pythonhosted.org/packages/dd/33/1ab1113e3778617ae7aba66b4b537f90512bd279ff65b6c984fb91fbb2d3/mypy-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ea4fd21bb48f0da49e6d3b37ef6bd7e8228b9fe41bbf4d80d9364d11adbd43c", size = 13787703, upload-time = "2025-11-28T15:48:41.286Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2d/8b0821b3e0d538de1ad96c86502256c7326274d5cb74e0b373efaada273f/mypy-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:16f76ff3f3fd8137aadf593cb4607d82634fca675e8211ad75c43d86033ee6c6", size = 10049225, upload-time = "2025-11-28T15:45:55.089Z" }, - { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", size = 13102606, upload-time = "2025-12-15T05:02:46.833Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", size = 12164496, upload-time = "2025-12-15T05:03:41.947Z" }, + { url = "https://files.pythonhosted.org/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", size = 12772068, upload-time = "2025-12-15T05:02:53.689Z" }, + { url = "https://files.pythonhosted.org/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", size = 13520385, upload-time = "2025-12-15T05:02:38.328Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", size = 13796221, upload-time = "2025-12-15T05:03:22.147Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", size = 10055456, upload-time = "2025-12-15T05:03:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] @@ -1391,7 +1391,7 @@ mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mo perf = [{ name = "simplejson", specifier = ">=3.17.0" }] pip = [{ name = "pip" }] typing = [ - { name = "mypy", specifier = "==1.19.0" }, + { name = "mypy", specifier = "==1.19.1" }, { name = "pip" }, { name = "pyright", specifier = "==1.1.407" }, { name = "typing-extensions" }, From 60289f0398dd43ff305a3145068dd4fc373959ed Mon Sep 17 00:00:00 2001 From: Jib Date: Wed, 17 Dec 2025 21:37:58 -0500 Subject: [PATCH 044/129] PYTHON-5433 (hotfix): Fix typing check for sbom requirements file (#2655) --- tools/generate_sbom_requirements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/generate_sbom_requirements.py b/tools/generate_sbom_requirements.py index 131605ab88..59389bee5a 100644 --- a/tools/generate_sbom_requirements.py +++ b/tools/generate_sbom_requirements.py @@ -6,6 +6,7 @@ - Excludes docs.txt and test.txt in the requirements folder - Writes output to sbom-requirements.txt (overwrites) """ + from __future__ import annotations from pathlib import Path @@ -59,7 +60,7 @@ def write_combined_req_files(root: Path, files: list[Path], outname: str) -> Pat raise RuntimeError(f"Failed to write {outpath}: {e}") from e -def main(): +def main() -> None: root = Path(__file__).parent.parent.resolve() files = collect_files(root) From e5070789ccb8e42e720d10b15ccd21ead865c375 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 18 Dec 2025 12:16:02 +0000 Subject: [PATCH 045/129] PYTHON-5679 Optimize `ObjectId.__str__()` (#2657) Co-authored-by: Steven Silvester --- bson/objectid.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bson/objectid.py b/bson/objectid.py index 970c4e52e8..d3c64ef7c3 100644 --- a/bson/objectid.py +++ b/bson/objectid.py @@ -15,7 +15,6 @@ """Tools for working with MongoDB ObjectIds.""" from __future__ import annotations -import binascii import datetime import os import struct @@ -234,7 +233,7 @@ def __setstate__(self, value: Any) -> None: self.__id = oid def __str__(self) -> str: - return binascii.hexlify(self.__id).decode() + return self.__id.hex() def __repr__(self) -> str: return f"ObjectId('{self!s}')" From b1ea3918428929a98bec6f3d762eed67bb09e17a Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Thu, 18 Dec 2025 12:16:29 +0000 Subject: [PATCH 046/129] PYTHON-5679 Optimize `ObjectId` (#2656) Co-authored-by: Steven Silvester --- bson/objectid.py | 51 +++++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/bson/objectid.py b/bson/objectid.py index d3c64ef7c3..e66a77b95e 100644 --- a/bson/objectid.py +++ b/bson/objectid.py @@ -97,11 +97,27 @@ def __init__(self, oid: Optional[Union[str, ObjectId, bytes]] = None) -> None: objectid.rst>`_. """ if oid is None: - self.__generate() + # Generate a new value for this ObjectId. + with ObjectId._inc_lock: + inc = ObjectId._inc + ObjectId._inc = (inc + 1) % (_MAX_COUNTER_VALUE + 1) + + # 4 bytes current time, 5 bytes random, 3 bytes inc. + self.__id = _PACK_INT_RANDOM(int(time.time()), ObjectId._random()) + _PACK_INT(inc)[1:4] elif isinstance(oid, bytes) and len(oid) == 12: self.__id = oid + elif isinstance(oid, str): + if len(oid) == 24: + try: + self.__id = bytes.fromhex(oid) + except (TypeError, ValueError): + _raise_invalid_id(oid) + else: + _raise_invalid_id(oid) + elif isinstance(oid, ObjectId): + self.__id = oid.binary else: - self.__validate(oid) + raise TypeError(f"id must be an instance of (bytes, str, ObjectId), not {type(oid)}") @classmethod def from_datetime(cls: Type[ObjectId], generation_time: datetime.datetime) -> ObjectId: @@ -162,37 +178,6 @@ def _random(cls) -> bytes: cls.__random = _random_bytes() return cls.__random - def __generate(self) -> None: - """Generate a new value for this ObjectId.""" - with ObjectId._inc_lock: - inc = ObjectId._inc - ObjectId._inc = (inc + 1) % (_MAX_COUNTER_VALUE + 1) - - # 4 bytes current time, 5 bytes random, 3 bytes inc. - self.__id = _PACK_INT_RANDOM(int(time.time()), ObjectId._random()) + _PACK_INT(inc)[1:4] - - def __validate(self, oid: Any) -> None: - """Validate and use the given id for this ObjectId. - - Raises TypeError if id is not an instance of :class:`str`, - :class:`bytes`, or ObjectId. Raises InvalidId if it is not a - valid ObjectId. - - :param oid: a valid ObjectId - """ - if isinstance(oid, ObjectId): - self.__id = oid.binary - elif isinstance(oid, str): - if len(oid) == 24: - try: - self.__id = bytes.fromhex(oid) - except (TypeError, ValueError): - _raise_invalid_id(oid) - else: - _raise_invalid_id(oid) - else: - raise TypeError(f"id must be an instance of (bytes, str, ObjectId), not {type(oid)}") - @property def binary(self) -> bytes: """12-byte binary representation of this ObjectId.""" From c930c6977673a57f8be5c97a71718f7cc6a3d4b8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 19 Dec 2025 13:14:52 -0600 Subject: [PATCH 047/129] PYTHON-5566 & PYTHON-3132 Add minimum version checks for remaining test variants (#2650) --- .evergreen/generated_configs/tasks.yml | 554 +++-- .evergreen/generated_configs/variants.yml | 2 - .evergreen/scripts/generate_config.py | 42 +- .evergreen/scripts/generate_config_utils.py | 4 +- pymongo/ocsp_support.py | 14 - pymongo/pyopenssl_context.py | 6 +- pyproject.toml | 12 +- requirements/gssapi.txt | 2 +- requirements/ocsp.txt | 9 +- requirements/snappy.txt | 2 +- uv.lock | 2233 +++++++++++-------- 11 files changed, 1707 insertions(+), 1173 deletions(-) diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index 6e75170ec9..f69afcdb9e 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -9,7 +9,7 @@ tasks: tags: [aws_lambda] # Aws tests - - name: test-auth-aws-4.4-regular-python3.10 + - name: test-auth-aws-4.4-regular-python3.10-min-deps commands: - func: run server vars: @@ -21,6 +21,7 @@ tasks: TEST_NAME: auth_aws SUB_TEST_NAME: regular TOOLCHAIN_VERSION: "3.10" + TEST_MIN_DEPS: "1" tags: [auth-aws, auth-aws-regular] - name: test-auth-aws-5.0-assume-role-python3.11 commands: @@ -198,50 +199,6 @@ tasks: SUB_TEST_NAME: azure-fail tags: [pr] - # Min deps tests - - name: test-min-deps-python3.10-sync-noauth-nossl-standalone - commands: - - func: run server - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - - func: run tests - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - TEST_MIN_DEPS: "1" - tags: [test-min-deps, standalone-noauth-nossl] - - name: test-min-deps-python3.10-sync-noauth-ssl-replica-set - commands: - - func: run server - vars: - AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set - - func: run tests - vars: - AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set - TEST_MIN_DEPS: "1" - tags: [test-min-deps, replica_set-noauth-ssl] - - name: test-min-deps-python3.10-sync-auth-ssl-sharded-cluster - commands: - - func: run server - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - - func: run tests - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - TEST_MIN_DEPS: "1" - tags: [test-min-deps, sharded_cluster-auth-ssl] - # Min support tests - name: test-min-support-python3.9-standalone-noauth-nossl commands: @@ -397,12 +354,13 @@ tasks: tags: [mod_wsgi, pr] # No orchestration tests - - name: test-no-orchestration-python3.10 + - name: test-no-orchestration-python3.10-min-deps commands: - func: assume ec2 role - func: run tests vars: TOOLCHAIN_VERSION: "3.10" + TEST_MIN_DEPS: "1" tags: [test-no-orchestration, python-3.10] - name: test-no-orchestration-python3.14 commands: @@ -464,7 +422,7 @@ tasks: tags: [test-no-toolchain, sharded_cluster-auth-ssl] # Ocsp tests - - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -473,8 +431,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -483,8 +442,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -493,8 +453,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -503,8 +464,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -513,8 +475,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -523,6 +486,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple-latest-python3.14 commands: @@ -534,7 +498,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -543,8 +507,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -553,8 +518,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -563,8 +529,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -573,8 +540,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -583,8 +551,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -593,6 +562,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple-latest-python3.14 commands: @@ -604,7 +574,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -613,8 +583,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -623,8 +594,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -633,8 +605,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -643,8 +616,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -653,8 +627,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -663,6 +638,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple-latest-python3.14 commands: @@ -674,7 +650,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -683,8 +659,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -693,8 +670,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -703,8 +681,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -713,8 +692,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -723,8 +703,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -733,6 +714,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple-latest-python3.14 commands: @@ -744,7 +726,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-soft-fail-v4.4-python3.10 + - name: test-ocsp-ecdsa-soft-fail-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -753,8 +735,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-soft-fail-v5.0-python3.10 + - name: test-ocsp-ecdsa-soft-fail-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -763,8 +746,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-soft-fail-v6.0-python3.10 + - name: test-ocsp-ecdsa-soft-fail-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -773,8 +757,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-soft-fail-v7.0-python3.10 + - name: test-ocsp-ecdsa-soft-fail-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -783,8 +768,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-soft-fail-v8.0-python3.10 + - name: test-ocsp-ecdsa-soft-fail-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -793,8 +779,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-soft-fail-rapid-python3.10 + - name: test-ocsp-ecdsa-soft-fail-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -803,6 +790,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-soft-fail-latest-python3.14 commands: @@ -814,7 +802,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-valid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -823,12 +811,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "4.4" - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -837,12 +826,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "5.0" - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -851,12 +841,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "6.0" - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -865,12 +856,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "7.0" - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -879,12 +871,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "8.0" - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-ecdsa-valid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -893,6 +886,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa @@ -912,7 +906,7 @@ tasks: - ocsp-ecdsa - latest - ocsp-staple - - name: test-ocsp-ecdsa-invalid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -921,12 +915,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "4.4" - ocsp-staple - - name: test-ocsp-ecdsa-invalid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -935,12 +930,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "5.0" - ocsp-staple - - name: test-ocsp-ecdsa-invalid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -949,12 +945,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "6.0" - ocsp-staple - - name: test-ocsp-ecdsa-invalid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -963,12 +960,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "7.0" - ocsp-staple - - name: test-ocsp-ecdsa-invalid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -977,12 +975,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "8.0" - ocsp-staple - - name: test-ocsp-ecdsa-invalid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-ecdsa-invalid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -991,6 +990,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa @@ -1010,7 +1010,7 @@ tasks: - ocsp-ecdsa - latest - ocsp-staple - - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1019,12 +1019,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "4.4" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1033,12 +1034,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "5.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1047,12 +1049,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "6.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1061,12 +1064,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "7.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1075,12 +1079,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "8.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1089,6 +1094,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa @@ -1108,7 +1114,7 @@ tasks: - ocsp-ecdsa - latest - ocsp-staple - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1117,12 +1123,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "4.4" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1131,12 +1138,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "5.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1145,12 +1153,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "6.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1159,12 +1168,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "7.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1173,12 +1183,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa - "8.0" - ocsp-staple - - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1187,6 +1198,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-ecdsa @@ -1206,7 +1218,7 @@ tasks: - ocsp-ecdsa - latest - ocsp-staple - - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1215,8 +1227,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1225,8 +1238,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1235,8 +1249,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1245,8 +1260,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1255,8 +1271,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1265,6 +1282,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 commands: @@ -1276,7 +1294,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1285,8 +1303,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1295,8 +1314,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1305,8 +1325,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1315,8 +1336,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1325,8 +1347,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1335,6 +1358,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 commands: @@ -1346,7 +1370,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1355,8 +1379,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "4.4"] - - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1365,8 +1390,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "5.0"] - - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1375,8 +1401,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "6.0"] - - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1385,8 +1412,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "7.0"] - - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1395,8 +1423,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, "8.0"] - - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1405,6 +1434,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-ecdsa, rapid] - name: test-ocsp-ecdsa-malicious-no-responder-muststaple-server-does-not-staple-latest-python3.14 commands: @@ -1416,7 +1446,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-ecdsa, latest] - - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1425,8 +1455,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1435,8 +1466,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1445,8 +1477,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1455,8 +1488,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1465,8 +1499,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-valid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-valid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1475,6 +1510,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-valid-cert-server-does-not-staple-latest-python3.14 commands: @@ -1486,7 +1522,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1495,8 +1531,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1505,8 +1542,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1515,8 +1553,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1525,8 +1564,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1535,8 +1575,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1545,6 +1586,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-invalid-cert-server-does-not-staple-latest-python3.14 commands: @@ -1556,7 +1598,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1565,8 +1607,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1575,8 +1618,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1585,8 +1629,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1595,8 +1640,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1605,8 +1651,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1615,6 +1662,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple-latest-python3.14 commands: @@ -1626,7 +1674,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1635,8 +1683,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1645,8 +1694,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1655,8 +1705,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1665,8 +1716,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1675,8 +1727,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1685,6 +1738,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple-latest-python3.14 commands: @@ -1696,7 +1750,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-soft-fail-v4.4-python3.10 + - name: test-ocsp-rsa-soft-fail-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1705,8 +1759,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-soft-fail-v5.0-python3.10 + - name: test-ocsp-rsa-soft-fail-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1715,8 +1770,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-soft-fail-v6.0-python3.10 + - name: test-ocsp-rsa-soft-fail-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1725,8 +1781,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-soft-fail-v7.0-python3.10 + - name: test-ocsp-rsa-soft-fail-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1735,8 +1792,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-soft-fail-v8.0-python3.10 + - name: test-ocsp-rsa-soft-fail-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1745,8 +1803,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-soft-fail-rapid-python3.10 + - name: test-ocsp-rsa-soft-fail-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1755,6 +1814,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-soft-fail-latest-python3.14 commands: @@ -1766,7 +1826,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-valid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-rsa-valid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1775,12 +1835,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "4.4" - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1789,12 +1850,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "5.0" - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1803,12 +1865,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "6.0" - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1817,12 +1880,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "7.0" - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-rsa-valid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1831,12 +1895,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "8.0" - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-rsa-valid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1845,6 +1910,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa @@ -1864,7 +1930,7 @@ tasks: - ocsp-rsa - latest - ocsp-staple - - name: test-ocsp-rsa-invalid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1873,12 +1939,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "4.4" - ocsp-staple - - name: test-ocsp-rsa-invalid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1887,12 +1954,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "5.0" - ocsp-staple - - name: test-ocsp-rsa-invalid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1901,12 +1969,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "6.0" - ocsp-staple - - name: test-ocsp-rsa-invalid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -1915,12 +1984,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "7.0" - ocsp-staple - - name: test-ocsp-rsa-invalid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -1929,12 +1999,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "8.0" - ocsp-staple - - name: test-ocsp-rsa-invalid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-rsa-invalid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -1943,6 +2014,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa @@ -1962,7 +2034,7 @@ tasks: - ocsp-rsa - latest - ocsp-staple - - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -1971,12 +2043,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "4.4" - ocsp-staple - - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -1985,12 +2058,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "5.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -1999,12 +2073,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "6.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -2013,12 +2088,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "7.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -2027,12 +2103,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "8.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-valid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-rsa-delegate-valid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -2041,6 +2118,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa @@ -2060,7 +2138,7 @@ tasks: - ocsp-rsa - latest - ocsp-staple - - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v4.4-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -2069,12 +2147,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "4.4" - ocsp-staple - - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v5.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -2083,12 +2162,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "5.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v6.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -2097,12 +2177,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "6.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v7.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -2111,12 +2192,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "7.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v8.0-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -2125,12 +2207,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa - "8.0" - ocsp-staple - - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-rapid-python3.10 + - name: test-ocsp-rsa-delegate-invalid-cert-server-staples-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -2139,6 +2222,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: - ocsp - ocsp-rsa @@ -2158,7 +2242,7 @@ tasks: - ocsp-rsa - latest - ocsp-staple - - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -2167,8 +2251,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -2177,8 +2262,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -2187,8 +2273,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -2197,8 +2284,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -2207,8 +2295,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -2217,6 +2306,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 commands: @@ -2228,7 +2318,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -2237,8 +2327,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -2247,8 +2338,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -2257,8 +2349,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -2267,8 +2360,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -2277,8 +2371,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -2287,6 +2382,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-delegate-malicious-invalid-cert-muststaple-server-does-not-staple-latest-python3.14 commands: @@ -2298,7 +2394,7 @@ tasks: TOOLCHAIN_VERSION: "3.14" VERSION: latest tags: [ocsp, ocsp-rsa, latest] - - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v4.4-python3.10 + - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v4.4-python3.10-min-deps commands: - func: run tests vars: @@ -2307,8 +2403,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "4.4" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "4.4"] - - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v5.0-python3.10 + - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v5.0-python3.10-min-deps commands: - func: run tests vars: @@ -2317,8 +2414,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "5.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "5.0"] - - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v6.0-python3.10 + - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v6.0-python3.10-min-deps commands: - func: run tests vars: @@ -2327,8 +2425,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "6.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "6.0"] - - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v7.0-python3.10 + - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v7.0-python3.10-min-deps commands: - func: run tests vars: @@ -2337,8 +2436,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "7.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "7.0"] - - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v8.0-python3.10 + - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-v8.0-python3.10-min-deps commands: - func: run tests vars: @@ -2347,8 +2447,9 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: "8.0" + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, "8.0"] - - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-rapid-python3.10 + - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-rapid-python3.10-min-deps commands: - func: run tests vars: @@ -2357,6 +2458,7 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.10" VERSION: rapid + TEST_MIN_DEPS: "1" tags: [ocsp, ocsp-rsa, rapid] - name: test-ocsp-rsa-malicious-no-responder-muststaple-server-does-not-staple-latest-python3.14 commands: @@ -2586,19 +2688,21 @@ tasks: - replica_set-noauth-nossl - async - pr - - name: test-server-version-python3.10-sync-noauth-nossl-replica-set-cov + - name: test-server-version-python3.10-sync-noauth-nossl-replica-set-min-deps-cov commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: replica_set + TEST_MIN_DEPS: "1" COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: replica_set + TEST_MIN_DEPS: "1" COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync @@ -2689,19 +2793,21 @@ tasks: - sharded_cluster-auth-nossl - sync - free-threaded - - name: test-server-version-python3.10-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.10-async-auth-ssl-sharded-cluster-min-deps-cov commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster + TEST_MIN_DEPS: "1" COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster + TEST_MIN_DEPS: "1" COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async @@ -2834,19 +2940,21 @@ tasks: - python-pypy3.11 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.10-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.10-sync-auth-ssl-sharded-cluster-min-deps-cov commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster + TEST_MIN_DEPS: "1" COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster + TEST_MIN_DEPS: "1" COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync @@ -3021,19 +3129,21 @@ tasks: - python-3.11 - sharded_cluster-noauth-nossl - sync - - name: test-server-version-python3.10-async-noauth-ssl-sharded-cluster-cov + - name: test-server-version-python3.10-async-noauth-ssl-sharded-cluster-min-deps-cov commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: sharded_cluster + TEST_MIN_DEPS: "1" COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: sharded_cluster + TEST_MIN_DEPS: "1" COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async @@ -3124,19 +3234,21 @@ tasks: - python-3.11 - standalone-auth-ssl - async - - name: test-server-version-python3.10-sync-auth-ssl-standalone-cov + - name: test-server-version-python3.10-sync-auth-ssl-standalone-min-deps-cov commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: standalone + TEST_MIN_DEPS: "1" COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: standalone + TEST_MIN_DEPS: "1" COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync @@ -3145,19 +3257,21 @@ tasks: - python-3.10 - standalone-auth-ssl - sync - - name: test-server-version-python3.10-async-noauth-nossl-standalone-cov + - name: test-server-version-python3.10-async-noauth-nossl-standalone-min-deps-cov commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone + TEST_MIN_DEPS: "1" COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone + TEST_MIN_DEPS: "1" COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async @@ -3319,7 +3433,7 @@ tasks: - sharded_cluster-auth-ssl - sync - pypy - - name: test-standard-v4.2-python3.10-sync-noauth-nossl-standalone + - name: test-standard-v4.2-python3.10-sync-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -3327,12 +3441,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: "4.2" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: @@ -3453,7 +3569,7 @@ tasks: - sharded_cluster-auth-ssl - async - pypy - - name: test-standard-v4.4-python3.10-async-noauth-nossl-standalone + - name: test-standard-v4.4-python3.10-async-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -3461,12 +3577,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.4" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: "4.4" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: @@ -3498,7 +3616,7 @@ tasks: - standalone-noauth-nossl - async - free-threaded - - name: test-standard-v5.0-python3.10-sync-noauth-ssl-replica-set + - name: test-standard-v5.0-python3.10-sync-noauth-ssl-replica-set-min-deps commands: - func: run server vars: @@ -3506,12 +3624,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "5.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: "5.0" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: @@ -3609,7 +3729,7 @@ tasks: - python-3.13 - standalone-noauth-nossl - sync - - name: test-standard-v6.0-python3.10-async-noauth-ssl-replica-set + - name: test-standard-v6.0-python3.10-async-noauth-ssl-replica-set-min-deps commands: - func: run server vars: @@ -3617,12 +3737,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "6.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: "6.0" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: @@ -3742,7 +3864,7 @@ tasks: - python-3.13 - replica_set-noauth-ssl - sync - - name: test-standard-v7.0-python3.10-sync-auth-ssl-sharded-cluster + - name: test-standard-v7.0-python3.10-sync-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -3750,12 +3872,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: @@ -3854,7 +3978,7 @@ tasks: - python-3.13 - replica_set-noauth-ssl - async - - name: test-standard-v8.0-python3.10-async-auth-ssl-sharded-cluster + - name: test-standard-v8.0-python3.10-async-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -3862,12 +3986,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: @@ -4214,7 +4340,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v4.2-python3.10-noauth-nossl-standalone + - name: test-non-standard-v4.2-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -4222,12 +4348,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "4.2" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: "4.2" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard @@ -4407,7 +4535,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-non-standard-v5.0-python3.10-noauth-nossl-standalone + - name: test-non-standard-v5.0-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -4415,12 +4543,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "5.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: "5.0" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard @@ -4578,7 +4708,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v7.0-python3.10-noauth-nossl-standalone + - name: test-non-standard-v7.0-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -4586,12 +4716,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: "7.0" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard @@ -4816,7 +4948,7 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-rapid-python3.10-noauth-nossl-standalone + - name: test-non-standard-rapid-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -4824,12 +4956,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: rapid + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard @@ -4883,7 +5017,7 @@ tasks: - test-numpy # Test standard auth tests - - name: test-standard-auth-v4.2-python3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-v4.2-python3.10-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -4891,12 +5025,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: "4.2" + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-standard-auth @@ -5186,7 +5322,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-rapid-python3.10-auth-ssl-sharded-cluster + - name: test-standard-auth-rapid-python3.10-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -5194,12 +5330,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid + TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-standard-auth diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index a0f2025bbd..1b0c3fc734 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -177,7 +177,6 @@ buildvariants: - name: encryption-rhel8 tasks: - name: .test-non-standard - - name: .test-min-deps display_name: Encryption RHEL8 run_on: - rhel87-small @@ -208,7 +207,6 @@ buildvariants: - name: encryption-crypt_shared-rhel8 tasks: - name: .test-non-standard - - name: .test-min-deps display_name: Encryption crypt_shared RHEL8 run_on: - rhel87-small diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 2d9c478d79..05afc16db1 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -128,7 +128,7 @@ def get_encryption_expansions(encryption): ): expansions = get_encryption_expansions(encryption) display_name = get_variant_name(encryption, host, **expansions) - tasks = [".test-non-standard", ".test-min-deps"] + tasks = [".test-non-standard"] if host != "rhel8": tasks = [".test-non-standard !.pypy"] variant = create_variant( @@ -581,6 +581,8 @@ def create_server_version_tasks(): seen.add(combo) tags.append("pr") expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology) + if python == ALL_PYTHONS[0]: + expansions["TEST_MIN_DEPS"] = "1" if "t" in python: tags.append("free-threaded") if python not in PYPYS and "t" not in python: @@ -646,6 +648,8 @@ def create_test_non_standard_tasks(): if pr: tags.append("pr") expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) + if python == ALL_PYTHONS[0]: + expansions["TEST_MIN_DEPS"] = "1" name = get_task_name("test-non-standard", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -686,6 +690,8 @@ def create_test_standard_auth_tasks(): if pr: tags.append("pr") expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) + if python == ALL_PYTHONS[0]: + expansions["TEST_MIN_DEPS"] = "1" name = get_task_name("test-standard-auth", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -695,22 +701,6 @@ def create_test_standard_auth_tasks(): return tasks -def create_min_deps_tasks(): - """For variants that support testing with minimum dependencies.""" - tasks = [] - for topology in TOPOLOGIES: - auth, ssl = get_standard_auth_ssl(topology) - tags = ["test-min-deps", f"{topology}-{auth}-{ssl}"] - expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology) - server_func = FunctionCall(func="run server", vars=expansions) - test_vars = expansions.copy() - test_vars["TEST_MIN_DEPS"] = "1" - name = get_task_name("test-min-deps", python=CPYTHONS[0], sync="sync", **test_vars) - test_func = FunctionCall(func="run tests", vars=test_vars) - tasks.append(EvgTask(name=name, tags=tags, commands=[server_func, test_func])) - return tasks - - def create_standard_tasks(): """For variants that do not set a TEST_NAME.""" tasks = [] @@ -738,6 +728,8 @@ def create_standard_tasks(): if pr: tags.append("pr") expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) + if python == ALL_PYTHONS[0]: + expansions["TEST_MIN_DEPS"] = "1" name = get_task_name("test-standard", python=python, sync=sync, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -755,9 +747,11 @@ def create_no_orchestration_tasks(): "test-no-orchestration", f"python-{python}", ] - name = get_task_name("test-no-orchestration", python=python) assume_func = FunctionCall(func="assume ec2 role") test_vars = dict(TOOLCHAIN_VERSION=python) + if python == ALL_PYTHONS[0]: + test_vars["TEST_MIN_DEPS"] = "1" + name = get_task_name("test-no-orchestration", **test_vars) test_func = FunctionCall(func="run tests", vars=test_vars) commands = [assume_func, test_func] tasks.append(EvgTask(name=name, tags=tags, commands=commands)) @@ -805,8 +799,10 @@ def create_aws_tasks(): tags = [*base_tags, f"auth-aws-{test_type}"] if "t" in python: tags.append("free-threaded") - name = get_task_name(f"{base_name}-{test_type}", python=python) test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME=test_type, TOOLCHAIN_VERSION=python) + if python == ALL_PYTHONS[0] and test_type != "ecs": + test_vars["TEST_MIN_DEPS"] = "1" + name = get_task_name(f"{base_name}-{test_type}", **test_vars) test_func = FunctionCall(func="run tests", vars=test_vars) funcs = [server_func, assume_func, test_func] tasks.append(EvgTask(name=name, tags=tags, commands=funcs)) @@ -885,6 +881,8 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name): TOOLCHAIN_VERSION=python, VERSION=version, ) + if python == ALL_PYTHONS[0]: + vars["TEST_MIN_DEPS"] = "1" test_func = FunctionCall(func="run tests", vars=vars) tags = ["ocsp", f"ocsp-{algo}", version] @@ -893,11 +891,7 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name): if algo == "valid-cert-server-staples" and version == "latest": tags.append("pr") - task_name = get_task_name( - f"test-ocsp-{algo}-{base_task_name}", - python=python, - version=version, - ) + task_name = get_task_name(f"test-ocsp-{algo}-{base_task_name}", **vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) return tasks diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index b87286f39d..573afa36ea 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -43,7 +43,7 @@ sync={"sync": "Sync", "async": "Async"}, coverage={"1": "cov"}, no_ext={"1": "No C"}, - test_min_deps={True: "Min Deps"}, + test_min_deps={"1": "Min Deps"}, ) HOSTS = dict() @@ -172,7 +172,7 @@ def get_common_name(base: str, sep: str, **kwargs) -> str: display_name = f"{display_name}{sep}{version}" for key, value in kwargs.items(): name = value - if key.lower() == "python": + if key.lower() in ["python", "toolchain_version"]: if not value.startswith("pypy"): name = f"Python{value}" else: diff --git a/pymongo/ocsp_support.py b/pymongo/ocsp_support.py index 8322f821fb..41fdd7fda6 100644 --- a/pymongo/ocsp_support.py +++ b/pymongo/ocsp_support.py @@ -45,7 +45,6 @@ from cryptography.x509 import ExtensionNotFound as _ExtensionNotFound from cryptography.x509 import TLSFeature as _TLSFeature from cryptography.x509 import TLSFeatureType as _TLSFeatureType -from cryptography.x509 import load_pem_x509_certificate as _load_pem_x509_certificate from cryptography.x509.ocsp import OCSPCertStatus as _OCSPCertStatus from cryptography.x509.ocsp import OCSPRequestBuilder as _OCSPRequestBuilder from cryptography.x509.ocsp import OCSPResponseStatus as _OCSPResponseStatus @@ -102,19 +101,6 @@ ) -def _load_trusted_ca_certs(cafile: str) -> list[Certificate]: - """Parse the tlsCAFile into a list of certificates.""" - with open(cafile, "rb") as f: - data = f.read() - - # Load all the certs in the file. - trusted_ca_certs = [] - backend = _default_backend() - for cert_data in _re.findall(_CERT_REGEX, data): - trusted_ca_certs.append(_load_pem_x509_certificate(cert_data, backend)) - return trusted_ca_certs - - def _get_issuer_cert( cert: Certificate, chain: Iterable[Certificate], trusted_ca_certs: Optional[list[Certificate]] ) -> Optional[Certificate]: diff --git a/pymongo/pyopenssl_context.py b/pymongo/pyopenssl_context.py index 08fe99c889..67456d5537 100644 --- a/pymongo/pyopenssl_context.py +++ b/pymongo/pyopenssl_context.py @@ -35,7 +35,7 @@ from pymongo.errors import ConfigurationError as _ConfigurationError from pymongo.errors import _CertificateError # type:ignore[attr-defined] from pymongo.ocsp_cache import _OCSPCache -from pymongo.ocsp_support import _load_trusted_ca_certs, _ocsp_callback +from pymongo.ocsp_support import _ocsp_callback from pymongo.socket_checker import SocketChecker as _SocketChecker from pymongo.socket_checker import _errno_from_exception from pymongo.write_concern import validate_boolean @@ -322,10 +322,6 @@ def load_verify_locations( ssl.CERT_NONE. """ self._ctx.load_verify_locations(cafile, capath) - # Manually load the CA certs when get_verified_chain is not available (pyopenssl<20). - if not hasattr(_SSL.Connection, "get_verified_chain"): - assert cafile is not None - self._callback_data.trusted_ca_certs = _load_trusted_ca_certs(cafile) def _load_certifi(self) -> None: """Attempt to load CA certs from certifi.""" diff --git a/pyproject.toml b/pyproject.toml index f6318b8ff6..37b6d6db23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,11 +48,11 @@ Tracker = "https://jira.mongodb.org/projects/PYTHON/issues" [dependency-groups] dev = [] -pip = ["pip"] -gevent = ["gevent>=20.6.0"] +pip = ["pip>=20.2"] +gevent = ["gevent>=21.12"] coverage = [ - "pytest-cov", - "coverage>=5,<=7.10.6" + "pytest-cov>=4.0.0", + "coverage[toml]>=5,<=7.10.6" ] mockupdb = [ "mockupdb@git+https://github.com/mongodb-labs/mongo-mockup-db@master" @@ -61,8 +61,8 @@ perf = ["simplejson>=3.17.0"] typing = [ "mypy==1.19.1", "pyright==1.1.407", - "typing_extensions", - "pip" + "typing_extensions>=3.7.4.2", + "pip>=20.2" ] # Used to call hatch_build.py diff --git a/requirements/gssapi.txt b/requirements/gssapi.txt index 7f156b9cea..28ad8503de 100644 --- a/requirements/gssapi.txt +++ b/requirements/gssapi.txt @@ -1,2 +1,2 @@ -pykerberos;os.name!='nt' +pykerberos>=1.2.4;os.name!='nt' winkerberos>=0.5.0;os.name=='nt' diff --git a/requirements/ocsp.txt b/requirements/ocsp.txt index 39dbddef14..a507b03d22 100644 --- a/requirements/ocsp.txt +++ b/requirements/ocsp.txt @@ -4,9 +4,10 @@ # service_identity 18.1.0 introduced support for IP addr matching. # Fallback to certifi on Windows if we can't load CA certs from the system # store and just use certifi on macOS. +# pyopenssl, cryptography, and service_identity must be set in tandem. # https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_default_verify_paths certifi>=2023.7.22;os.name=='nt' or sys_platform=='darwin' -pyopenssl>=17.2.0 -requests<3.0.0 -cryptography>=2.5 -service_identity>=18.1.0 +pyopenssl>=23.2.0 +requests>=2.23.0,<3.0 +cryptography>=42.0.0 +service_identity>=23.1.0 diff --git a/requirements/snappy.txt b/requirements/snappy.txt index 9bb71204b8..1b33fd9d08 100644 --- a/requirements/snappy.txt +++ b/requirements/snappy.txt @@ -1 +1 @@ -python-snappy +python-snappy>=0.6.0 diff --git a/uv.lock b/uv.lock index d2a05d7088..bba8df9386 100644 --- a/uv.lock +++ b/uv.lock @@ -2,8 +2,7 @@ version = 1 revision = 3 requires-python = ">=3.9" resolution-markers = [ - "python_full_version == '3.14.*'", - "python_full_version >= '3.15' or (python_full_version >= '3.11' and python_full_version < '3.14')", + "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version < '3.10'", ] @@ -37,8 +36,7 @@ name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.14.*'", - "python_full_version >= '3.15' or (python_full_version >= '3.11' and python_full_version < '3.14')", + "python_full_version >= '3.11'", "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } @@ -48,26 +46,25 @@ wheels = [ [[package]] name = "anyio" -version = "4.9.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -90,163 +87,175 @@ wheels = [ [[package]] name = "backports-zstd" -version = "1.0.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/12/8080a1b7bce609eb250813519f550b36ad5950b64f0af2738c0fb53e7fb3/backports_zstd-1.0.0.tar.gz", hash = "sha256:8e99702fd4092c26624b914bcd140d03911a16445ba6a74435b29a190469cce3", size = 995991, upload-time = "2025-10-10T07:06:18.481Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/79/10389134d3a7e3099798ca55fc82abe9d7f49239c69c8d9c4979b091338c/backports_zstd-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34cd28f44f6b8f70ea5d86c2b3ba26d0d51f94606bd1f0c80bde1f820e8c82b2", size = 435682, upload-time = "2025-10-10T07:03:59.169Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6e/40d033accd0d54ee0b696f8f2ae0840bd4fae7255b3463ff46b210520e4f/backports_zstd-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7ccfd860da6b4c0c1f0f68dc917483977383e1207bdee262d40369fe616fc8", size = 362079, upload-time = "2025-10-10T07:04:00.784Z" }, - { url = "https://files.pythonhosted.org/packages/73/47/a1ed28ffd9b956aadbde6ad9a8d4adeab38b5cbfdd1d3f3d485a1bb18eba/backports_zstd-1.0.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:65de196c84b0d2c44fb76a729041de3a292f0128ded1b5ff7c1c4eab948aae0b", size = 505978, upload-time = "2025-10-10T07:04:02.903Z" }, - { url = "https://files.pythonhosted.org/packages/0e/14/6ea8a2567881ce0a46b7c8376c336f366e6e5dfe84766c45fed7473f0649/backports_zstd-1.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1096d4504557d2deb9e71e1aef9914c651b7aa05c35276d77854fee7a4bdd09a", size = 475592, upload-time = "2025-10-10T07:04:04.275Z" }, - { url = "https://files.pythonhosted.org/packages/62/27/5782d0bb36adbeec58687a2abf7e1a1659af30782129355456551486794f/backports_zstd-1.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a0db63d2a112c217a347789aecd38bdd17bc1030fa9d3d256d5d011231852f2", size = 581221, upload-time = "2025-10-10T07:04:05.759Z" }, - { url = "https://files.pythonhosted.org/packages/69/0b/accbbdbd24940b7a93d8064224b4eb304bd51d68a5e03fd2af3e2b5af268/backports_zstd-1.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0dfab6714325c29d621593b1e9198e47064bb755e601c3e79fde5211a16527f7", size = 640865, upload-time = "2025-10-10T07:04:07.236Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c4/7c5c5e47565e959a5dd6d2eae4e34e2ca46b6ba123ee1dd548c0c0d316f2/backports_zstd-1.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63076e558ba75b2c016257bdbe6b83b5b9a41fe5b6601b567f1e07890370f2f1", size = 491083, upload-time = "2025-10-10T07:04:09.04Z" }, - { url = "https://files.pythonhosted.org/packages/47/46/4ef914cfaf8a91fecd01e3d342fd506f450a06109c749a034e3a48ce97b2/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cd445dcb1ec8a8aa38d3c4e9cda7991cacead193bb07cf002f60cfc002d8628", size = 481539, upload-time = "2025-10-10T07:04:10.532Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f0/ae1dd6cf45d48b535fb6e5a77a107d6cc39db2ae8a9c060762d8fb6bcad2/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d01aa1b91007edd00abc78b3cb7e10ad9394b54a75cbd76b1584524a3b238cfc", size = 509486, upload-time = "2025-10-10T07:04:11.763Z" }, - { url = "https://files.pythonhosted.org/packages/91/35/a4829b1715e965baa00ef52529f513c8c30de83d3d2f662cbd016ad8861a/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d6bbb553640f7ed630223dae9ec93d6f56785d03db0c8385e0c4cfc88d54bdf4", size = 585584, upload-time = "2025-10-10T07:04:13.264Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b7/3d139188c2803b7e2944cc22ad7e2a974cc9773534c4dd736a136b671160/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d25adbe96f4a3fb6492d8d504e78c1641b06002ca2b578a0b24862ee9fd5a58", size = 631442, upload-time = "2025-10-10T07:04:14.655Z" }, - { url = "https://files.pythonhosted.org/packages/a5/72/442ada2be3fa510b0a93ca3f353c546c7626690029c3533dd348ea3c7730/backports_zstd-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dde864a9c6aaa94eafe4962f59916de3165e99b3fd4d2f6584cd116707aed8ff", size = 495142, upload-time = "2025-10-10T07:04:16.144Z" }, - { url = "https://files.pythonhosted.org/packages/4c/80/e26f98015801a790262f9637feaebf2028edebd915e196cc1507f4ee7b6f/backports_zstd-1.0.0-cp310-cp310-win32.whl", hash = "sha256:474847d1ac3ed2e4bfca2207bbfd5632110143ddd4fc6ea41ff5c5b05d2fda1d", size = 288590, upload-time = "2025-10-10T07:04:17.755Z" }, - { url = "https://files.pythonhosted.org/packages/db/39/f322cf4d8b3194353a5bc01db6f2829835d1df273e93ebd1607f130213b5/backports_zstd-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5d2fb3f4d53b2f92a26e7fc34b313ac5eebd7ff437f37f8f5c308d72c844fbd7", size = 313505, upload-time = "2025-10-10T07:04:18.895Z" }, - { url = "https://files.pythonhosted.org/packages/b6/c0/e6ce5b66c48dfe29ec149eee01901be136071dd1692d6f99e14dbd7ba7d1/backports_zstd-1.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:525a1ecb62edd97c6695812fef0e4dc7c2faa5edf3057aa3d8ec9d2dbd0f7799", size = 288707, upload-time = "2025-10-10T07:04:19.968Z" }, - { url = "https://files.pythonhosted.org/packages/01/0a/cbf3f9cb7ca865eca93744d1b859ed50d28be3f64d83cfd96ad114ed88d6/backports_zstd-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:782923d65aa6d0c8c465c32563df70dbdd3e255532de2a2d26e13598fc5f85ae", size = 435683, upload-time = "2025-10-10T07:04:21.097Z" }, - { url = "https://files.pythonhosted.org/packages/c7/70/65f975ac0e1780963c5bcfae40e822724d7e4bfe902eeef3637a14fb56b1/backports_zstd-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6825598589ef9b8c0c4e574170d29d84400be24c2f172b81403435b33c8d103a", size = 362075, upload-time = "2025-10-10T07:04:22.382Z" }, - { url = "https://files.pythonhosted.org/packages/0a/22/007acd1b0af3a78188c2b71fd4a3284f005826bd93e234e73412944d7b99/backports_zstd-1.0.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:15863463b7f28049d4511f9f123c3b96d66c3de543315c21ef3bc6b001b20d01", size = 505978, upload-time = "2025-10-10T07:04:23.504Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d6/f0a148d3f0d0558ace2fc0e7d4f0cc648e88c212665cbf8df718037adde9/backports_zstd-1.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b39da619431f4782f3d8bb0d99a6067db571eab50579527ba168bcc12887d328", size = 475589, upload-time = "2025-10-10T07:04:24.791Z" }, - { url = "https://files.pythonhosted.org/packages/49/b5/32fcb6342cfa9ca5692b0344961aafd082887e4fad89248f890927522bad/backports_zstd-1.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:411da73bb3eadef58da781c55c6399fc6dba9b898ca05009410138fb1d7fef8d", size = 581218, upload-time = "2025-10-10T07:04:26.493Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/757aa4952b8f3d955bb62b72360940639c781fc4f39249f5ea40e0b8125b/backports_zstd-1.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f8b0bc92f5be153a4878188ab0aeab5b9bbff3dc3e9d3ad3b19e29fe4932741", size = 640908, upload-time = "2025-10-10T07:04:27.837Z" }, - { url = "https://files.pythonhosted.org/packages/37/5f/075c31cbe58fffd8144bc482fea73d2833562159684430b3f1d402fa9f8d/backports_zstd-1.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cd5bdb76448f2259ea371d6cd62a7e339021e1429fe3c386acb3e58c1f6c61", size = 491121, upload-time = "2025-10-10T07:04:29.045Z" }, - { url = "https://files.pythonhosted.org/packages/2d/a0/4c4b9a85ff52fe90a3265aa9b5cb7b35bf1a2d48bd1ed4604d7fe1aabfc7/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d251a49e80e1868e132e6edadfbef8dba7ded7751e59a41684cd6da38bbd3507", size = 481544, upload-time = "2025-10-10T07:04:30.174Z" }, - { url = "https://files.pythonhosted.org/packages/9c/0e/1bd54a04e9f236f5a8d426c00ce0a6d5af6d68735138e9887d5545311761/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a54ea58ddeaab9a1385c368f84fca474b87b052087b62e56ac1ebd10cabac157", size = 509487, upload-time = "2025-10-10T07:04:31.386Z" }, - { url = "https://files.pythonhosted.org/packages/ef/eb/03a53be8a982e953acd8864d63ca1622ca309d9fbcf1f7ec5e2550b45057/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d6272730803dc5b212615f50af7395f2b05155d9415e367492d6dac807edc949", size = 585574, upload-time = "2025-10-10T07:04:32.585Z" }, - { url = "https://files.pythonhosted.org/packages/5c/90/17810915587c2686e767a5cd2de014e902c76e0a242daf1c4a97544ba1f5/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f6a27510ebb9e1cb877aaa26fc5e0303437bd2023e0a24976da854a3421e60e5", size = 631483, upload-time = "2025-10-10T07:04:34.107Z" }, - { url = "https://files.pythonhosted.org/packages/a4/22/d65a54a803061e475b66164c7d03d2ed889c32eaf32544c2e0d599c20628/backports_zstd-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c55f842917ac4405a9779476b1ec8219247f35d86673769cf2d3c140799d3e4a", size = 495147, upload-time = "2025-10-10T07:04:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/20/94/bdf4e76e148cfac7c324b74f76fbda83c5a587b8a85871bad09722729283/backports_zstd-1.0.0-cp311-cp311-win32.whl", hash = "sha256:c28cfbd6217ba4837d35cdd8cfd5dcf84ad54bffcb531734002e27dcc84c87ca", size = 288686, upload-time = "2025-10-10T07:04:37.132Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/726a07d04b85a687776b04b53a02b7d2c4b666d51b18c44fa2ddaadfe383/backports_zstd-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f009996762b887d1bf9330ac0ce1e83608db0b881f63644ae30f2b6a290cd36b", size = 313630, upload-time = "2025-10-10T07:04:38.358Z" }, - { url = "https://files.pythonhosted.org/packages/52/e6/727584a8794fa28164e0795441d8b86f89c75a2368dec0aaaa086f7ac58c/backports_zstd-1.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:62ab49843fc7761383aa7bea8105ca70941797c7145647f71fa4340bfd3b747a", size = 288829, upload-time = "2025-10-10T07:04:39.602Z" }, - { url = "https://files.pythonhosted.org/packages/ba/22/2a68534673efe608d7b2d0de03595d5d1de629616a2f4e394813376eed21/backports_zstd-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f9afba11403cf03849464e0f1214b035970d43546a7cdd9d8ee31dc154889e78", size = 435990, upload-time = "2025-10-10T07:04:41.075Z" }, - { url = "https://files.pythonhosted.org/packages/3a/44/c3f06c172f128bf1160f6122df2a942440e36b8450cf4ba44c69465c5f55/backports_zstd-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86498856adc6e8c6f50cbfb1d4afd4e0997d5837fb225245d3fb26008f3c9412", size = 362142, upload-time = "2025-10-10T07:04:42.344Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0d/f46bba0f0df4dcd3d47160003b956b19329c25f63fe9e910aa17ca9fa0e5/backports_zstd-1.0.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:de915a8ecd290c601be7b7436291397b0ac1f7841c97c3a13777bb1065881773", size = 506399, upload-time = "2025-10-10T07:04:43.846Z" }, - { url = "https://files.pythonhosted.org/packages/92/a1/681e03e50379d72e06c3de796fb8cc5880fca8b70b82562b2eb712abf6d1/backports_zstd-1.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fffb08c5c1b629c96813108183c8b02d6b07ed6ec81cca8d094089e749db4b5", size = 476222, upload-time = "2025-10-10T07:04:44.975Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ca/8b0a8b959668668c50af6bfad6fea564d2b6becdcffd998e03dfc04c3954/backports_zstd-1.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:064d4dc840bcfd8c5c9b37dcacd4fb27eac473c75006120015a9f88b73368c9b", size = 581678, upload-time = "2025-10-10T07:04:46.459Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9a/921ec253ad5a592da20bf8ab1a5be16b242722f193e02d7a3678702aeffc/backports_zstd-1.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0051911391c3f934bb48e8ca08f4319d94b08362a40d96a4b5534c60f00deca2", size = 640408, upload-time = "2025-10-10T07:04:48.178Z" }, - { url = "https://files.pythonhosted.org/packages/ca/8c/0826259b7076cdaaceda1d52f2859c771dc45efed155084a49f538f0ea2e/backports_zstd-1.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e5f3453f0ea32ccf262e11e711ef1a0a986903b8a3a3078bf93fafdd5cf311c", size = 494195, upload-time = "2025-10-10T07:04:49.326Z" }, - { url = "https://files.pythonhosted.org/packages/a9/a5/75b1c1e26e305f06a7cde591213d5b3c8591b06882ae635b8ffeb8df6f44/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:33bce0351cd0ad7bd9f363740b894e65255eb93d16d05097ef1d60643ce1cc27", size = 482255, upload-time = "2025-10-10T07:04:50.722Z" }, - { url = "https://files.pythonhosted.org/packages/dc/24/7061610369a5dbadcddc6f340d5aa8304ae58aee07a6a851b8fa24638036/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:820f3cd08c5c8672015b7e52bf04e3e707727e6576d988eadc1799c0c47b33d9", size = 509829, upload-time = "2025-10-10T07:04:52.014Z" }, - { url = "https://files.pythonhosted.org/packages/e6/28/afc0158ba3d5d5a03560348f9a79fb8a1e0d0ef98f1d176ab37aa887ed5e/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4e327fe73bfc634e8b04b5e0f715c97680987d633f161fd4702027b34685be43", size = 586059, upload-time = "2025-10-10T07:04:53.255Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0d/68f1fa86a79faee7f6533bced500ee622dde98c9b3b0ddab58a4fe6410d5/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4055318ebb7f6ffad99dabd312706599c9e119c834d6c741a946c0d4b3e5be4e", size = 630869, upload-time = "2025-10-10T07:04:54.397Z" }, - { url = "https://files.pythonhosted.org/packages/83/e1/a529be674d179caf201e5e406dc70a2c4156e182fa777e43f43f6afa69c6/backports_zstd-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79d3c879720ee4987782da55d728919f9294a8ea6fac76c9af84bc06f3b0f942", size = 498686, upload-time = "2025-10-10T07:04:55.593Z" }, - { url = "https://files.pythonhosted.org/packages/17/9a/075582e942841520c47535f9ff62b728a88565b737ae21dc99ebcc15ef61/backports_zstd-1.0.0-cp312-cp312-win32.whl", hash = "sha256:930ccc283fdf76d1acca9529acd6ccb6cd26cdaf684d69cc6f359683f90357be", size = 288822, upload-time = "2025-10-10T07:04:56.835Z" }, - { url = "https://files.pythonhosted.org/packages/7f/14/615cd31c0de23e330e13ba77a6aed9a1d27360ebdf5e68b078c54b8cdbdb/backports_zstd-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f1bb3e6d21ebfb22070288b7fb47bbb0baaae604890c4087edf5637debb6bd91", size = 313841, upload-time = "2025-10-10T07:04:58.003Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b8/87b2467bf82eabb4acd4651f193363ec04973baa35141be441bf9e9e98c0/backports_zstd-1.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:f5fe75e5996b5f4712235f9c63cdb7e5932a9cdf3a41232989f8a3ef1667f784", size = 288950, upload-time = "2025-10-10T07:04:59.279Z" }, - { url = "https://files.pythonhosted.org/packages/19/36/0182161a23009d5439e125d4af7b13d2df0292663e7f87141d5cf76d3060/backports_zstd-1.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c522469a67fef66998fd9eff2195512ca54d78c6fecdf1c466d2b7752dd810b", size = 435481, upload-time = "2025-10-10T07:05:00.833Z" }, - { url = "https://files.pythonhosted.org/packages/79/ce/6c235828d54d0027838316d9ce284b52e7bc266154f5e57086a7c7796691/backports_zstd-1.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c28546bcffb88ee38a742e3364338c49672d776ea2c73decc05fbf79f045797e", size = 361757, upload-time = "2025-10-10T07:05:02.422Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/7cbc80512df9b89ae39ab3920afbaad733d4b64390b4439e52ef3673da7b/backports_zstd-1.0.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:9719c14984ca99f5567a5974210d04c75aa02b0124653ee1b1d9a39bf0764fc6", size = 505673, upload-time = "2025-10-10T07:05:03.596Z" }, - { url = "https://files.pythonhosted.org/packages/f8/bc/ea32d4698fac21fe6cc08a124ae21daa41be03f788f244791c47e31a4360/backports_zstd-1.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a31220e8544c2194c4a7c3bd9f7fb0eee3c0ce5f8306e55df762428159ff0512", size = 475879, upload-time = "2025-10-10T07:05:04.796Z" }, - { url = "https://files.pythonhosted.org/packages/bf/42/68344db3586455983bdcdffe51253fa4415908e700d50287249ad6589bc9/backports_zstd-1.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3571e35d6682119daf109678a68fa8a9e29f79487ee7ec2da63a7e97562acb8c", size = 581359, upload-time = "2025-10-10T07:05:05.977Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d0/3d153d78a52a46ce4c363680da7fbc593eeb314150f005c4bf7c2bd5b51f/backports_zstd-1.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:26ccb82bbeb36fffeb3865abe7df9b9b82d6462a488cd2f3c10e91c41c3103cc", size = 642203, upload-time = "2025-10-10T07:05:07.236Z" }, - { url = "https://files.pythonhosted.org/packages/11/c3/e31b4e591daec3eab2446db971f275d349aad36041236d5f067ab20fa1a9/backports_zstd-1.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d90cfb475d6d08c596ae77a7009cdda7374ecd79354fd75185cf029bf2204620", size = 490828, upload-time = "2025-10-10T07:05:08.446Z" }, - { url = "https://files.pythonhosted.org/packages/2d/80/ef7d02d846f710fc95c6d7eb3298ef6504e51f8707f24e1624d139f791d5/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0b00c9c22cae8c1f87e2f23f9aeda7fee82ff671672b9f5a161a7ba094d9904b", size = 481638, upload-time = "2025-10-10T07:05:10.18Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9b/f32500bf26ef588ce4f6284f453532d08789e412a5ecd60c501c77c88f8f/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a87c4491036954ae6d15edaf1e6d5b1941e13a9df14d6a9952899713fcfb0796", size = 509228, upload-time = "2025-10-10T07:05:11.313Z" }, - { url = "https://files.pythonhosted.org/packages/6d/67/f689055f90a2874578b2b3e7c84311c3007b2fa60c51454e8c432203f1c7/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8ea8c5d283211bc21c9782db7a8504a275a5b97e883b0bf67f6903a3af48f3d3", size = 585789, upload-time = "2025-10-10T07:05:12.477Z" }, - { url = "https://files.pythonhosted.org/packages/86/53/dea52bd76a3ba519a4937e6cab6cbdcdc36b618090eabeac998f69d1bb97/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e1d12f64d1bd535c782f30b33d1f60c060105d124f9ade22556fefbf36087776", size = 632571, upload-time = "2025-10-10T07:05:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/43/c8/ce10a94132957f57860b9440fe726615a6a6e8c5fdfee565d8a1b3a573de/backports_zstd-1.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df08eb2735363a11a9222203c3e9a478d7569511bdd9aa2cc64a39e0403cf09a", size = 495124, upload-time = "2025-10-10T07:05:15.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/c0/830ea473e3c6133758a9a421157c8d4d5c65408d565336a59403e6bb0b29/backports_zstd-1.0.0-cp313-cp313-win32.whl", hash = "sha256:0309f924ec026d2174297754aeb97fe5fa665cfe0f8bc70e7bb82808a7adcd08", size = 288467, upload-time = "2025-10-10T07:05:16.563Z" }, - { url = "https://files.pythonhosted.org/packages/75/5a/318d40e1589908a44532e2c850fedfaedbf4e7c75b6fa3cf4b532fcadc84/backports_zstd-1.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:d68f7f72579070bfef7890ba5316701c001e90b4455bb5c2591558b9d53a7f6e", size = 313680, upload-time = "2025-10-10T07:05:17.71Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c8/bb0067165e9b1066104a88536eac04cfac388abb5d500b3405cf783c96e8/backports_zstd-1.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:911a099122ce7cebed9e1ec64c1fa54a6ab461d6c7cec8d460d8b3a09bbd439f", size = 288699, upload-time = "2025-10-10T07:05:18.904Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0e/83badde9b389c198a9a45bccd38a9dc5baa7db92e531d4951b1c0686e29a/backports_zstd-1.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fad9af0c89048a50e67bfd9e3509d710b268d4ae0e47a2bc945dca273a17286d", size = 436173, upload-time = "2025-10-10T07:05:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/a1/92/d1f5e9f7e1afbb730020e8c7060d6101cad4aa20eb13b7cb98dda9414726/backports_zstd-1.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1d0459de16491399e6b6d151213964395ba092ba21b7739756f0507533c8e44f", size = 362456, upload-time = "2025-10-10T07:05:21.367Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0c/165b04a4bd9b39455e5d051f504acab6c5af3583939336bd2c77a2dc6398/backports_zstd-1.0.0-cp313-cp313t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:2dcf4c080c0fe8f4ca8f1ff560756ae78e6fada721813c1506f8fd3399996646", size = 507618, upload-time = "2025-10-10T07:05:23.083Z" }, - { url = "https://files.pythonhosted.org/packages/72/45/868e6b66852b64766feb3a3ce28cc74dd86141120ac6740855f90239fb85/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e26b558e0f2413e9499949dd75985a03be008287598916eaa75a67efc52e4f1b", size = 475518, upload-time = "2025-10-10T07:05:24.297Z" }, - { url = "https://files.pythonhosted.org/packages/44/ff/71021dae5e024d7e12b5078719582b26eeae984f5718846c135134288330/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f0a0c11aee04e0a10e9688ef8d9014af888763507bea85a0d7a7ba5220272996", size = 580942, upload-time = "2025-10-10T07:05:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/7c/64/553009a1d449033fafba311d2e204b19ebb0dfdba069a639965fb6f0bc57/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8aa92bf9407ed1ba62234e085876b628ecd9d2636c0e1e23f2dacf3be21af2a", size = 639934, upload-time = "2025-10-10T07:05:27.147Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/490a0b80144fb888ae9328f73d7bfa58fd5ccf8bdb81a6d20561ec5a0ff7/backports_zstd-1.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c78c1eaf3fdea00514afe9636e01f94890f1e4c6e8e1dfede48015364b950705", size = 494822, upload-time = "2025-10-10T07:05:28.325Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d2/0f7702000bd08ff6aa71114b377141f2d30154597dcd9459a08554122fa5/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4ff181018de5afb1b87edf9a88ec7e62b4b053e75b91ec8ac7819042126ca7cf", size = 482001, upload-time = "2025-10-10T07:05:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/20/78/2cc5dc095b93841eb251d91cf4b3b4c1e5efc15db40f97f003603acaba3f/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eed0753c698a21f0d38464c2a6d4d5e770d2ea2e9c3a308f1712d674598a049f", size = 511380, upload-time = "2025-10-10T07:05:30.874Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b3/328c4835b661b3a9f2c6f2eb6350a9d4bc673e7e5c7d1149ecb235abe774/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:18f5d3ed08afcd08b86b305bf167c0f2b582b906742e4bd3c7389050d5b59817", size = 585514, upload-time = "2025-10-10T07:05:32.523Z" }, - { url = "https://files.pythonhosted.org/packages/4f/31/3d347703f5d913d35edb58e9fbfbf8155dc63d1e6c0ed93eb5205e09d5f1/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:7a8c950abe629e5d8ea606e6600dd1d6cd6bddd7a4566cf34201d31244d10ab3", size = 630541, upload-time = "2025-10-10T07:05:33.799Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ac/323abb5ba0e5da924dec83073464eb87223677c577e0969c90b279700c1f/backports_zstd-1.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:973e74f4e1f19f7879a6a7900e9a268522eb4297100a573ed69969df63f94674", size = 499450, upload-time = "2025-10-10T07:05:35.4Z" }, - { url = "https://files.pythonhosted.org/packages/81/cb/1d77d6cf3850e804f4994a8106db2830e58638ed0f2d0f92636adb38a38d/backports_zstd-1.0.0-cp313-cp313t-win32.whl", hash = "sha256:870effb06ffb7623af1c8dac35647a1c4b597d3bb0b3f9895c738bd5ad23666c", size = 289410, upload-time = "2025-10-10T07:05:36.776Z" }, - { url = "https://files.pythonhosted.org/packages/16/59/5ec914419b6db0516794f6f5214b1990e550971fe0867c60ea55262b5d68/backports_zstd-1.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8bb6470186301e84aaa704c8eb339c97dcdec67445e7e197d44665e933807e4e", size = 314778, upload-time = "2025-10-10T07:05:38.637Z" }, - { url = "https://files.pythonhosted.org/packages/75/88/198e1726f65229f219bb2a72849c9424ba41f6de989c3a8c9bf58118a4a7/backports_zstd-1.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b2d85810393b3be6e8e77d89a165fc67c2a08290a210dbd77e2fc148dbc4106f", size = 289333, upload-time = "2025-10-10T07:05:39.758Z" }, - { url = "https://files.pythonhosted.org/packages/c5/80/cad971088dd705adedce95e4ce77801cbad61ac9250b4e77fbbb2881c34f/backports_zstd-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1315107754808856ddcf187a19cc139cb4a2a65970bd1bafd71718cfd051d32e", size = 435835, upload-time = "2025-10-10T07:05:41.027Z" }, - { url = "https://files.pythonhosted.org/packages/8c/9f/8c13830b7d698bd270d9aaeebd685670e8955282a3e5f6967521bcb5b2d3/backports_zstd-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96bf0a564af74951adfa6addd1c148ab467ba92172cd23b267dd150b0f47fd9e", size = 362191, upload-time = "2025-10-10T07:05:42.594Z" }, - { url = "https://files.pythonhosted.org/packages/db/b4/dd0d86d04b1dd4d08468e8d980d3ece48d86909b9635f1efebce309b98d4/backports_zstd-1.0.0-cp39-cp39-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:d7c1c6ebedf7bc70c1adca3f4624e1e04b2a0d7a389b065f0c5d6244f6be3dae", size = 506076, upload-time = "2025-10-10T07:05:43.842Z" }, - { url = "https://files.pythonhosted.org/packages/86/6e/b484e33d8eb13b9379741e9e88daa48c15c9038e9ee9926ebf1096bfed6f/backports_zstd-1.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ea4ff5e162fb61f8421724021eac0a612af0aff2da9e585c96d27c2da924589", size = 475720, upload-time = "2025-10-10T07:05:45.094Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e6/c49157bb8240ffd4c0abf93306276be4e80d2ef8c1b8465e06bcecece250/backports_zstd-1.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5a6047fb0bef5bbe519b1e46108847e01a48d002b3dfc69af1423a53d8144dda", size = 581396, upload-time = "2025-10-10T07:05:46.389Z" }, - { url = "https://files.pythonhosted.org/packages/67/24/a900cfdc4dd74306c6b53604ad51af5f38e2353b0d615a3c869051134b3b/backports_zstd-1.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2d510b422e7b2b6ca142082fa85ac360edf77b73108454335ecfd19071c819ff", size = 641053, upload-time = "2025-10-10T07:05:48.012Z" }, - { url = "https://files.pythonhosted.org/packages/3d/75/5ce7953c6306fc976abf7cf33f0071a10d58c71c94348844ae625dfdee22/backports_zstd-1.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e6349defa266342802d86343b7fc59ee12048bca5f77a9fcb1c1ab9bb894d09", size = 491186, upload-time = "2025-10-10T07:05:49.424Z" }, - { url = "https://files.pythonhosted.org/packages/f9/db/375410a26abf2ac972fec554122065d774fa037f9ffeedf4f7b05553b01d/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20b0a1be02b2ee18c74b68a89eec14be98d11f0415a79eb209dce4bc2d6f4e52", size = 481750, upload-time = "2025-10-10T07:05:50.678Z" }, - { url = "https://files.pythonhosted.org/packages/21/d1/fa7c2d7b7a1c433e4e79c027c54d17f2ffc489ab7e76496b149d9ae6f667/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3595cbc2f4d8a5dc6bd791ba8d9fee2fdfcdfc07206e944c1b3ec3090fcbc99e", size = 509601, upload-time = "2025-10-10T07:05:51.952Z" }, - { url = "https://files.pythonhosted.org/packages/c4/35/befe5ee9bec078f7f4c9290cefc56d3336b4ee52d17a60293d9dda4589c0/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d3eddb298db7a9a1b122c40bcb418a154b6c8f1b54ef7308644e0e67d42c159e", size = 585743, upload-time = "2025-10-10T07:05:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0a/cfbf0ae24348be3c3f597717c639e9cbe29692a99ad650c232b8a97c74c1/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef31a9482727e6b335f673a8b8116be186b83ca72be4a07f60684b8220a213e9", size = 631591, upload-time = "2025-10-10T07:05:54.846Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2d/7c996648c7a7b84a3e8b045fb494466475c1f599374da3c780198bde96c4/backports_zstd-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0a6a6d114058735d042116aa9199b0b436236fddcb5f805fb17310fcadddd441", size = 495294, upload-time = "2025-10-10T07:05:56.417Z" }, - { url = "https://files.pythonhosted.org/packages/be/c8/5a15a4a52506e2e2598d2667ae67404516ea4336535fdd7b7b1b2fffd623/backports_zstd-1.0.0-cp39-cp39-win32.whl", hash = "sha256:8aea1bdc89becb21d1df1cdcc6182b2aa9540addaa20569169e01b25b8996f41", size = 288646, upload-time = "2025-10-10T07:05:57.993Z" }, - { url = "https://files.pythonhosted.org/packages/67/4e/42409d11a9d324f68a079493c5806d593f54184962e5fff1dc88a1d5e3ba/backports_zstd-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:23a40a40fb56f4b47ece5e9cb7048c2e93d9eeb81ad5fb4e68adcaeb699d6b98", size = 313532, upload-time = "2025-10-10T07:05:59.212Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f8/932b05fd2f98f85c95674f09ae28ccc1638b8cc17d6f566d21ed499ee456/backports_zstd-1.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:2f07bd1c1b478bd8a0bbe413439c24ee08ceb6ebc957a97de3666e8f2e612463", size = 288756, upload-time = "2025-10-10T07:06:01.216Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/680ac0ad73676eb1f3bb71f6dd3bbaa2d28a9e4293d3ede4adcd78905b93/backports_zstd-1.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:efa53658c1e617986ed202e7aa8eb23c69fc8f33d01192cd1565e455ed9aa057", size = 409790, upload-time = "2025-10-10T07:06:02.405Z" }, - { url = "https://files.pythonhosted.org/packages/62/6c/6410c334890b4a43c893b9dcd3cbc8b10f17ea8dced483d9ba200b17ccab/backports_zstd-1.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4386a17c99ce647877298c916f2afeacb238e56cb7cca2d665822a0ee743b5d5", size = 339308, upload-time = "2025-10-10T07:06:03.667Z" }, - { url = "https://files.pythonhosted.org/packages/0f/b2/ad3e651985b8a2a4876e5adc61100cef07a8caefb87180391f1f5b8c801c/backports_zstd-1.0.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:cbbb0bda54bda18af99961d7d22d7bc7fedcc7d8ca3a04dcde9189494dbfc87a", size = 420356, upload-time = "2025-10-10T07:06:04.984Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c4/361bde3f570804674b9033ac41cc26735ceb4e33ccce2645079eff62a26f/backports_zstd-1.0.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c49048ec50f81b196ab0f3c49c025912eba1c6e55259b99f11ee6e8c04226ab", size = 393900, upload-time = "2025-10-10T07:06:06.252Z" }, - { url = "https://files.pythonhosted.org/packages/7b/90/f7bc5c0d204c2312fbe4e62592c92200f19da8840ce8b4a1df56080b7537/backports_zstd-1.0.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6d1fe816c3c31241b0bdcc96364fae689f3e49a923469ad1ad7a9aeb0bbcd67", size = 413862, upload-time = "2025-10-10T07:06:07.506Z" }, - { url = "https://files.pythonhosted.org/packages/77/2b/9c1949456566228578d30013e81a593577e63e1cae9e72b058e37ae4c5e2/backports_zstd-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:96a57f48d2d64c985393bb4ae15b61097d8fc0d56416e790f7cc09bf9212fb87", size = 299722, upload-time = "2025-10-10T07:06:08.661Z" }, - { url = "https://files.pythonhosted.org/packages/a0/51/f22627d208ab63e97f5441374110363f4b5e0c2ce0b4f2412e753eb12bf1/backports_zstd-1.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8191c019cefaf074c3f05ebec5ad19ec606b7ac1dc915b66a0820268e6f0e327", size = 409687, upload-time = "2025-10-10T07:06:09.844Z" }, - { url = "https://files.pythonhosted.org/packages/4f/93/50b2ebb2e8f388bb124c4a39974e29f841ef1452d603045e292e107227b9/backports_zstd-1.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:479270cd6385775dca98accaf304e5f011d94280ad4681d3e925a1b4dfd19aaf", size = 339221, upload-time = "2025-10-10T07:06:11.13Z" }, - { url = "https://files.pythonhosted.org/packages/25/f5/103645f44a92c4de2860b8d6cf6c5414b63956278764f8b7db359bdeae94/backports_zstd-1.0.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:3beab43bfda8e453648b9cce5edcceb5add6c42c331873b41ab1d24232d9c2b0", size = 420355, upload-time = "2025-10-10T07:06:12.283Z" }, - { url = "https://files.pythonhosted.org/packages/d9/10/e185f05ec85bc05c82d7efdd75528e695c85181eb291cc4c19b2f26153f1/backports_zstd-1.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67469b247c99537b77f7d402580cbb7298fa15ebe3ce6984d89a5b65d4d5a6c2", size = 393900, upload-time = "2025-10-10T07:06:13.508Z" }, - { url = "https://files.pythonhosted.org/packages/fd/40/3f717216e21617e919d12d6520d0da5b22002e07f12638629acc9e5dcc2e/backports_zstd-1.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6910a9311e7a2987d353f396568f5e401cf4917e2112bf610e62385ad02d8cf4", size = 413863, upload-time = "2025-10-10T07:06:15.531Z" }, - { url = "https://files.pythonhosted.org/packages/23/f5/cb12f5dd6ac648e92d8cec8b69fd4064bd549c126fb0d3fe6d3dd237afbe/backports_zstd-1.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:85f08363b7ca504a8bceaa2d4333a1a307d2b2056f77a13036a81d7aa3c87b2a", size = 299719, upload-time = "2025-10-10T07:06:17.032Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f4/d8/e8426a2abd7bfdafffcc1be07a23890314f199928515937c8ee7e3537157/backports_zstd-1.2.0.tar.gz", hash = "sha256:6c3fc19342db750b52fde793e4440a93575761b1493bb4a1d3b26033d2bd3452", size = 997263, upload-time = "2025-12-06T20:26:39.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/97/01630376854dab8cc6e58819236c46ffc40bc2ad1c5a82b430e0c5b79009/backports_zstd-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68d70396997cbaaf8e571dee93f05c4cec5053ef14a6e165c26ad4aadca6b7ee", size = 435932, upload-time = "2025-12-06T20:24:08.421Z" }, + { url = "https://files.pythonhosted.org/packages/6c/62/a06ddca84e3c0ec45e667a02be5c4a157ab5e1e940d65096a80d409f0557/backports_zstd-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8dc2d15224ea683bbf7ad6aa8eacde40972c2c700e8ff72862cb0663e18ae953", size = 362327, upload-time = "2025-12-06T20:24:09.956Z" }, + { url = "https://files.pythonhosted.org/packages/10/16/1045c674bb09fad1b838098c0b16a88bae9a7bab5e305aac11e55a8c813e/backports_zstd-1.2.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:7137114011f380f7e8bd97d92664b5bd71ab5f6e08144f22836263ad45273af2", size = 506230, upload-time = "2025-12-06T20:24:11.273Z" }, + { url = "https://files.pythonhosted.org/packages/32/8d/ef8a8051374a1948d19c9888d2898d7091314448360a803bffc8474bbf58/backports_zstd-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb922d2f278ec2e62a29121f98e573f622cefa9408dc0462a0e51ac08bca30b8", size = 475845, upload-time = "2025-12-06T20:24:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b9/2c01e970e69f7ea2fa9710373fa4b48d31e677098d04d9b05d01e109feef/backports_zstd-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b72f4fe1764d53c6f8526d53ef566dcbe71daa81b3219fd802b51a153692159a", size = 581469, upload-time = "2025-12-06T20:24:14.343Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/a2780c37d876badb483815239f2cc9bd863123248e20b2290e3f08355b7e/backports_zstd-1.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:743feb4e13e0f41a22d257bb5d2d4323f0308ecfedfd53db69172e2d9c3e4ba8", size = 641118, upload-time = "2025-12-06T20:24:16.024Z" }, + { url = "https://files.pythonhosted.org/packages/92/88/5f40f9ead71dfb234fb7ab3c7949f2c0aa52eaed3f75115e4e7cfb5528f4/backports_zstd-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:199c41106c05bf12cb665a1d105ce2185da5e190e13b95933420d6fd9cd8bb10", size = 491335, upload-time = "2025-12-06T20:24:17.565Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e2/4fd0071eb6035d6a412f6c5c2802ebacdbb845569dbb7a1e723c13cc1742/backports_zstd-1.2.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:270f10d14852fd11f85e1199f59fcb9dbcb425d489c678e5b0ea669c091b1cf3", size = 565341, upload-time = "2025-12-06T20:24:18.705Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ef/b40a17346294c892d93fa738f48fc145432fa4ff265cf2d1ab3c177f10b1/backports_zstd-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c276433f6bbe67a8c71fa78bbfe1d7859ee17f799e6fcff9ac250840e38608d4", size = 481792, upload-time = "2025-12-06T20:24:19.948Z" }, + { url = "https://files.pythonhosted.org/packages/ee/24/7e0d77b17a3ae899a0c3f5ed9be842a8e6134577ea3411d8ff0e3962764d/backports_zstd-1.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:772ad9b56a546fde9c1636dcf525d727330b11c6e34c9af8f879f23b41a8054e", size = 509736, upload-time = "2025-12-06T20:24:21.263Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2a/99f7d247b974de0e5238796e85ba29e49c285a2d8a51c3b6f5b8abd4cd93/backports_zstd-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f7fd5d7c8c1c7b26b52b49bf3e392c3c6295658a34a887c587044b37a0b68a3c", size = 585835, upload-time = "2025-12-06T20:24:22.835Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/0eabba1630f4b0674ce6ae79793e8901b0cdb28c83a484b424df29ed66dc/backports_zstd-1.2.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:18b510dd03a2b7cdda62744802d8c43df7f027f578c4e67f6fa7208d8691db84", size = 563163, upload-time = "2025-12-06T20:24:24.35Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/cc1cd5cbb8982ca156393b8b50698b86efdd0245ba56f3b3b539950061c6/backports_zstd-1.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:26301adc3008c2de40fedf780deb2bceb79471ea89efba37d30768871313f673", size = 631693, upload-time = "2025-12-06T20:24:26.166Z" }, + { url = "https://files.pythonhosted.org/packages/fb/19/01d30f83e87ae4890297f0d2422eb2fad0679c48eb65de8f2c8e131a9345/backports_zstd-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9dc4c9cf3804e6b6cc5aa9aedc63cf81551cc4f6150ea4b248b95de84051317d", size = 495392, upload-time = "2025-12-06T20:24:27.424Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/4fb7553901245637ae70d934f5ae719be7207aea3243b254a19f5947b554/backports_zstd-1.2.0-cp310-cp310-win32.whl", hash = "sha256:e456426bf45dd8d818df5ce6b81faaf3961ef8b16834e91cbe2b739346abe9fb", size = 288844, upload-time = "2025-12-06T20:24:28.563Z" }, + { url = "https://files.pythonhosted.org/packages/dd/64/9f8a05ff703f5bdc2b2c9c8e5797299eb2cf4791226a46b2e14489784b4a/backports_zstd-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cf6e2cbe1e637a834e1920ded11e423897a9822d17a0be9486d3f63554f51618", size = 313759, upload-time = "2025-12-06T20:24:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e7/cc45af33a4e4aee365ab76c3f4fcadf984fea221563c2c29c5613cffeaa9/backports_zstd-1.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:4d50bd23c4520e1ccd60af59f8aadc43ce3a481f2793afe01c18a7aa6a518892", size = 288960, upload-time = "2025-12-06T20:24:30.819Z" }, + { url = "https://files.pythonhosted.org/packages/b8/40/f914ee5a00c1f5df9a162efd7130db7ab339b838e6b1613eb2ed7f0594a2/backports_zstd-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47ed63b1c04e06981ac552d107945752d1ffecae98a4bce9c8a627490ce460d", size = 435933, upload-time = "2025-12-06T20:24:31.903Z" }, + { url = "https://files.pythonhosted.org/packages/36/5b/f03eeaee5b17cf88d9f252381f5b8573b1a1c958787af68e9d287c65086a/backports_zstd-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e6433f889edb998abe33f3498c37ddd97b3ce3607eebbc0fed148f8c7c7f2ef", size = 362324, upload-time = "2025-12-06T20:24:33.186Z" }, + { url = "https://files.pythonhosted.org/packages/f8/78/369773911bd9968ca5f4e10ee4232ab6b71cbe45d6e17c78d3399e4a3944/backports_zstd-1.2.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:4cd00e5bfd6d17233809f08d979742a5b9c322162d8daea16f7c3538387b9c64", size = 506229, upload-time = "2025-12-06T20:24:34.364Z" }, + { url = "https://files.pythonhosted.org/packages/19/da/f23872cd114b5352c97bf83a2082427aa08bd22f42461309c23783e82da5/backports_zstd-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8c7239b457f4d51c03634edb0c9b2ebdffc6806f58c0396209f5eb7f8d7642e", size = 475842, upload-time = "2025-12-06T20:24:36.079Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ea/07b6ee0956b522e6a8e0aca97d7b28ed0dc72a7c35a5b77485d2b8d7c4dc/backports_zstd-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75c35e5292d5c5fa879ce3f40428fdb510b11a98801ccf1140690ed7a9c13b3d", size = 581467, upload-time = "2025-12-06T20:24:37.735Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ea/ce04fed217a484ad9f3e8e087dd29c198dbfcb2d4d2c216d044a2a18aea8/backports_zstd-1.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:153e1af06e348f5ed1b104c345880c001824a192536940a8d012d33014b27ecf", size = 641159, upload-time = "2025-12-06T20:24:38.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/8f/b28147869bb8aba7a0b30f05cfec567d90002c4161dabb8315f002709ee3/backports_zstd-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf700fd79758417b1c0b725a56fa485ba15b10ee07ada736ff7e669fddd28b38", size = 491371, upload-time = "2025-12-06T20:24:40.209Z" }, + { url = "https://files.pythonhosted.org/packages/33/92/26c7f8bee4cb3e6aae08b04351aa5578d30bac2701197ca2e3cb2b785978/backports_zstd-1.2.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b04638b6adf68f730b04b21ac81fb58eef2ea10f6c221aa653f1009c0afcf67b", size = 565341, upload-time = "2025-12-06T20:24:41.551Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4b/d1595a7d877e67da6ec6d759d08f5dedaca59d4317c6116b19fd9e3c60bd/backports_zstd-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:35a445eb01e525ae8dec59fcbabcc373c9ace57f8c10455185038f54a930a039", size = 481793, upload-time = "2025-12-06T20:24:43.036Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0f/1e1c6a154026bcdd2daecb1abd1d924cb6d274b0f7bae4042f83fb0e97ab/backports_zstd-1.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1923ac203545a18a1b9726f6ae7bed1ab4f8825f0b8f4a32d2795932af3f5322", size = 509738, upload-time = "2025-12-06T20:24:44.427Z" }, + { url = "https://files.pythonhosted.org/packages/40/7e/09a807f3920fa1fe4ae019275d5978168d94fe8615c5bde3f7969760edb7/backports_zstd-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:97d06ec9b5b21fb59cfa5e716ca1c91f3bac2cd2c3b14e21c3d29fa1b2b0baf9", size = 585823, upload-time = "2025-12-06T20:24:46.001Z" }, + { url = "https://files.pythonhosted.org/packages/aa/14/ef90815a3ad6eabbca59b9cd62013c39acfd38c7cf1f5da31c733520a6d8/backports_zstd-1.2.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:57e773e295e7d56bd67a2f57071b1c978832566d0f908d7d7aabb16f35401810", size = 563165, upload-time = "2025-12-06T20:24:47.132Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/8918bb085bb2f333d5785cc67918c65e497674de6d53834c1c42233ddde0/backports_zstd-1.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:157950cfd4ed85e03c2557067867e37090796b556c613badfaefcdf2750e95e1", size = 631734, upload-time = "2025-12-06T20:24:48.309Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c6/a2e494d412728fc04c7e1f40479bac80c505f9eaeafa8048f764104dbfc8/backports_zstd-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a3450d9cf69d76843ea195c1defeff087b68a8a4a3687f0927f870ab594e062", size = 495397, upload-time = "2025-12-06T20:24:49.516Z" }, + { url = "https://files.pythonhosted.org/packages/fb/81/f9a762ad3e965324a19574c1aa7b39ac35196bc072534efd34b24bec9786/backports_zstd-1.2.0-cp311-cp311-win32.whl", hash = "sha256:77f0e7e71506e12f99927ddea7ab1de5933d47c9af048d05a229246977d89127", size = 288936, upload-time = "2025-12-06T20:24:50.68Z" }, + { url = "https://files.pythonhosted.org/packages/21/95/1d699d9bc9a94ad5b8bc06d1a59246a5adce02668e3773a8c29b1f5a7554/backports_zstd-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:84a0b581408efce8624b887326e0b285fc2e5ba32348b9b6e6775f171fd4926b", size = 313884, upload-time = "2025-12-06T20:24:51.772Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/74b78b9313af6e330b04ae010a98e1d8cc133254c3c53ae2b5e5f4d5ec83/backports_zstd-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:65e7591b20aa803c87a104c0dc9129a984f04adec9b042d88c7a14d1254c9524", size = 289080, upload-time = "2025-12-06T20:24:53.321Z" }, + { url = "https://files.pythonhosted.org/packages/fb/53/235dcac25478d60c4e58b6f982b91550b60908dbc07ab42405f818f41794/backports_zstd-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2348b69da19243b7148f69fc60753c27b3efe313dfb29dcb642b4b3a064261", size = 436243, upload-time = "2025-12-06T20:24:54.458Z" }, + { url = "https://files.pythonhosted.org/packages/db/b2/549d1933995ccf4464b29f068f6fdd1e2d9f6abc8ecbcab99dd90d4d28fd/backports_zstd-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f194a3cb53173f8bf8160597d39be16731e576ccf0244e7694e3aeac47e6c85d", size = 362396, upload-time = "2025-12-06T20:24:55.712Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/dc21a59734f2ce145a9a7f2d0016987cedf95598a850a3f4ab6ce73ddea0/backports_zstd-1.2.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:79a18d5d707cd92fc7ce28c4a1a63cfdaf8d19223b3167d2d879042bf1c018ac", size = 506651, upload-time = "2025-12-06T20:24:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/d5/16/12f84de430428f620a6ced01fd2768d2296951d7543b81d971455f39ef75/backports_zstd-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f04bcbb75de26d39de81e7c02a784cb55a706c6ce9665b8df218fa9275193a1a", size = 476474, upload-time = "2025-12-06T20:24:58.136Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8a/980e349fac1e1ba596f440b24901af498399c6e33b83032abbf22fed7d21/backports_zstd-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c36bbd7cc85629d77f9537ad02bc438b3e3f9f1741a43f1cede1027fd9ebfb5", size = 581930, upload-time = "2025-12-06T20:24:59.605Z" }, + { url = "https://files.pythonhosted.org/packages/36/f8/cba3372ca8d777cf5c3e312b35112ff608cf6b0b2f6a813b600b69763495/backports_zstd-1.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:28828f15a7429a7f7570a1465f9b71ccf7f99ea0a6bf786be7c276777f3cdc14", size = 640659, upload-time = "2025-12-06T20:25:01.164Z" }, + { url = "https://files.pythonhosted.org/packages/61/59/c8bd0a5a39770cf7c0d864cbb65ac5df57405ee28a51fd5c11a5fbf1a169/backports_zstd-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d634cba354a3ca61837e5a8d9a6ee19d9d37927ec288f0828437b2620ae83fb", size = 494445, upload-time = "2025-12-06T20:25:02.867Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bf/52665f48d449fa1586fb775468e2fa83ebc8e222eb2d18332b3b5f12f933/backports_zstd-1.2.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3624a67d12695d5c32a332bd8cc4c1d45273eba1a4a451a0ecf70f4c3e67dd4f", size = 568897, upload-time = "2025-12-06T20:25:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/e559551d4d206a71ae545fcb690e704dfee141d88984729b0100042e91d3/backports_zstd-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:93c9afeee3c60b203644e0a1cc54028283636b4e76ba670c84522584116c1b2e", size = 482506, upload-time = "2025-12-06T20:25:05.388Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/7440f4c72324c1c455498581faeadc1711cc6728f9d60aa781e6ef939446/backports_zstd-1.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4687dac0a3c5a4c30fbd871ee3be666822f1eb902a7a68ce0d1379f190917850", size = 510079, upload-time = "2025-12-06T20:25:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bf/248692d5e0960a50eebc982e4e2cbbb3ac0f6200ad81d222d4c01ddd500d/backports_zstd-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:dd63ed6c7139cef92b1073be892e92631aa468332570f7230089e93a9449f551", size = 586309, upload-time = "2025-12-06T20:25:08.351Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7c/ecfb1d8ba18e2c9090898f12b6ea83a9dd59e735021a2c564996c4599024/backports_zstd-1.2.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:464d5fea68f5b03feabea22a4db4f39622db4ff89dab2df259b3c8665f1f676b", size = 566493, upload-time = "2025-12-06T20:25:09.8Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8d/c81ed0da565f735e8a8f0c3b8c633f9e16bcfdb82ca5cb4d029dac0f1361/backports_zstd-1.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d5b3518f82c518a09067dab4ed1bd79ca542a2c248f7f9418262dc2c4a06ccdb", size = 631120, upload-time = "2025-12-06T20:25:10.974Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/9dec5a74249b3af4ea4fd4a5dfb5d70167ea82f96592b3b4e7d340ccfff0/backports_zstd-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d57ce6d62dfb28bd37ad5523678752a5516ec68595fd35559f6c2878edd4de0", size = 498938, upload-time = "2025-12-06T20:25:12.145Z" }, + { url = "https://files.pythonhosted.org/packages/fd/99/ca71a403c79ec4bc419b71cee532de6545af1a0d8e61d0a2b8d70a034e0a/backports_zstd-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e8bedc2372dae260397a99844420e16bb886912d685058d52e1f3533164f67a5", size = 289072, upload-time = "2025-12-06T20:25:13.48Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e4/77fc5813ea35906ac1b71cd284e20c5a6f808f138e2e6a13e9586cd61d1a/backports_zstd-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:4667b30ea5e9f8b505b2042a40c5115660151987ca748b4be07facc757212ff9", size = 314094, upload-time = "2025-12-06T20:25:14.966Z" }, + { url = "https://files.pythonhosted.org/packages/44/e3/133652d59a6731f9180a107812c9d52a2c72be2d80c2fd4f874669592a0c/backports_zstd-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:cd6326f1562435677ab2bf607a44c96bb2a48beb0e14accff45e8c9f0931e9c1", size = 289197, upload-time = "2025-12-06T20:25:16.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/dd602f484f22d8df9ed71859735dc86e094e90b7d8f51e51d48808f3571e/backports_zstd-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1db671ac69df7cd88057c85a7bc614b94afd74a48faacd1576ad91dd18008f6a", size = 435731, upload-time = "2025-12-06T20:25:17.225Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/b126e05650103f269282e5271a0960e30ac4ce9f192e3ae98e303325011b/backports_zstd-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f6520e555c2b597863b97ecb90ad21857bf044119f643130c29110b55f67c1a", size = 362007, upload-time = "2025-12-06T20:25:18.841Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/464e87930aa771da08634846300c13355f7ae07a476c8a30f75631fd1689/backports_zstd-1.2.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:9ef28be8bfebf5169cde28f36ebd146a0305569c91e836aed3a3aa79b7bbc58b", size = 505925, upload-time = "2025-12-06T20:25:20.319Z" }, + { url = "https://files.pythonhosted.org/packages/44/08/bda420a2d13be0d6aa8323b735207de46bb01c08575e3a6810e01a20501f/backports_zstd-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9478c04e285da26ce1198d5ec1e43905531049c9e0f74169a39df5876f44643e", size = 476130, upload-time = "2025-12-06T20:25:21.839Z" }, + { url = "https://files.pythonhosted.org/packages/17/d5/fca7eb6e5a12e390ea4437bc6705e18efd70c9966127c3c2fec8188654f1/backports_zstd-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f032452d783876c5f4d81907e42fc6fbe812a9a712c360b497968ea7109bb17", size = 581610, upload-time = "2025-12-06T20:25:23.077Z" }, + { url = "https://files.pythonhosted.org/packages/81/e2/072fdf5bff7274788b49491a4d039bf0fe2f2f07a9975751d8b70fc14ac6/backports_zstd-1.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5642ae3832fb74817bdabc0d8b8877b109537c3f9ceeb54a6cd855aa0afc3bd9", size = 642454, upload-time = "2025-12-06T20:25:24.28Z" }, + { url = "https://files.pythonhosted.org/packages/2c/74/a7ae8e421ccb779130d64745d3191daf6da02f37bf7cf099dc10d688d14e/backports_zstd-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e7340a7fb7bac7607382554b138ae8dbffda0b8af72ce5c639fb86b49a3b2e4", size = 491079, upload-time = "2025-12-06T20:25:25.481Z" }, + { url = "https://files.pythonhosted.org/packages/25/26/450b23bad6035f0f3dc8e1cf3729e31a10ce1821a7b6d3bf8555ba818a46/backports_zstd-1.2.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b72e343b1b2927fc047054450de8738bc64c268e93fabc8228d963690eaf348e", size = 566373, upload-time = "2025-12-06T20:25:27.034Z" }, + { url = "https://files.pythonhosted.org/packages/c0/42/8161164fb26ac1a656f5fb5e3aa3aa9dcf4d06f6d9553fc596fa6f0ae3ad/backports_zstd-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3f2a3f898dd1eaba5c8f0ea0e1bd23d993ed86ea7d4c12bf7a0743158d123d6", size = 481888, upload-time = "2025-12-06T20:25:28.198Z" }, + { url = "https://files.pythonhosted.org/packages/c0/2c/5c4f9a54c7c708f38cdb0670804d62237e9cacd1a6ff567f8c5fb8ef5d1a/backports_zstd-1.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:afb95bc88d7623eeda2c0c8fe0388ac8838fa5a09ddbd7dfa72b283de392024b", size = 509480, upload-time = "2025-12-06T20:25:29.438Z" }, + { url = "https://files.pythonhosted.org/packages/13/fc/f7a86785fde290b3280adeb81c577a96323d4b661b9befa0d990aaa67a86/backports_zstd-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:59ddb84b8bd46a4a297cdf92601aaa9f59881c59f4a402a021173d6bb8bc367a", size = 586040, upload-time = "2025-12-06T20:25:30.598Z" }, + { url = "https://files.pythonhosted.org/packages/40/7a/11c709c72abeef82c5ef752718c10a3fde0fba8258c069d717de33d366f7/backports_zstd-1.2.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d3d0d54ef711ea5ffa4e2eebfb70784295eb517bd7ac64545a142ad35c5b02ba", size = 564125, upload-time = "2025-12-06T20:25:31.772Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ec/2ea033ceeca0808d830c3fb1d8ced1b6d2e5c4540ed8bdf66e0ec99180af/backports_zstd-1.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5c170f1329e90614f2d51f6f4622f6f775f51b92b7bc7801fa093b97db6cfc95", size = 632819, upload-time = "2025-12-06T20:25:33.027Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3a/1469312ce7b1c6d98c788b500df01d61468d17bfd58df21266a7160112fe/backports_zstd-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:49c675121210ee23ea9c9b28ad15822e1b0f9182df733f0e1a10a5385f628701", size = 495375, upload-time = "2025-12-06T20:25:34.362Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/27ef9e6a169808fc6777fb58fb55991bdc9ec22eee1aaecaf076be91986b/backports_zstd-1.2.0-cp313-cp313-win32.whl", hash = "sha256:0724fb2958eb2ccc100c9f49315d856a88b5deb985c62953876a78ecb46027ba", size = 288717, upload-time = "2025-12-06T20:25:35.565Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f3/bfe0c470e2cfd0bbc274ca484e7a061f2d505c28df2479c52ed1dcce4fea/backports_zstd-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b80b715170fc590d464d3342757978702ed4b6b41a3b3a0a5dbb46a89f4ccebe", size = 313927, upload-time = "2025-12-06T20:25:36.691Z" }, + { url = "https://files.pythonhosted.org/packages/16/0d/cf6b22e4ca8cfa850061baa89664437b2da65698b2a8a02eb67de9d6b69e/backports_zstd-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c09f5e985142fc04581c12e635dfd8cdc64b2d595479c1213f0b4db7ee37e3cb", size = 288947, upload-time = "2025-12-06T20:25:37.884Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/285bccc9fddb1b1d7fa379b42407f3e88359db05a6955fbe02ef4d5ed6ce/backports_zstd-1.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:21c68b9d358f1062c5c8baad946e306b54380dcbe2b12d00fdebc42533b5a499", size = 436423, upload-time = "2025-12-06T20:25:39.048Z" }, + { url = "https://files.pythonhosted.org/packages/9d/27/bba33f68cfc57a2885bbd1a1ffc9405cb461b12db5083ca63b15074bfa6d/backports_zstd-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4c3508074a30e3309bcc1bfdfe6cbd6bd3d64567788d3c6e15b1594e63bef276", size = 362704, upload-time = "2025-12-06T20:25:40.232Z" }, + { url = "https://files.pythonhosted.org/packages/42/62/9233d99c1be673188afde322aecc467653023185077064dd3eac18678e22/backports_zstd-1.2.0-cp313-cp313t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:9a0e5f6aebd1ff3c75b26d3e3ac3140996b9f0883b95847fad57992be06fe5d2", size = 507870, upload-time = "2025-12-06T20:25:41.364Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b0/089867ef455f6311caa9224e68020cabcbfc3f8759cfd19931b06ba7bb8d/backports_zstd-1.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b12f415fe62f1e9f7196ce9eaedc699547c38cc263e64b6939b610b2a29a200", size = 475770, upload-time = "2025-12-06T20:25:42.873Z" }, + { url = "https://files.pythonhosted.org/packages/43/d5/8615151ea4bcf5d2dca0755bfa7cad97b7cb8dbd1c9c2e1da57081f1d8a6/backports_zstd-1.2.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00066e7c3df700fe0df66aa197dcbd3d691d55c16f6179c3acd87a6ca22e7993", size = 581191, upload-time = "2025-12-06T20:25:44.156Z" }, + { url = "https://files.pythonhosted.org/packages/f5/71/eb6e7019db30622d2a31faa0e608fc3dc29e336b4a855bd5b0e78ac0943e/backports_zstd-1.2.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c56bdb8e57563d726ce46225011801ff748018cbeff5175576f8a46868e0f706", size = 640185, upload-time = "2025-12-06T20:25:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/d6/64/ebd64f84875f7feb83005bfc3e7352700d26f5396e7e5e494681af18fe18/backports_zstd-1.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f499ec4944c223814be97627d0f1b65c2474ba981e1f21ec8b541f2dff5f960", size = 495073, upload-time = "2025-12-06T20:25:46.546Z" }, + { url = "https://files.pythonhosted.org/packages/45/a7/8f44bbd2a78855680c24e7f90cad69c0c70650f345c4cf13bc7a6dd56231/backports_zstd-1.2.0-cp313-cp313t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7a0957bc3eb84b4a537388c7a62ed055d61c2246c11babf4cbaafb3b5eda0a9c", size = 570664, upload-time = "2025-12-06T20:25:47.731Z" }, + { url = "https://files.pythonhosted.org/packages/85/e3/476a8b3ac1d7d1b41442bf634603d0beb392d9df2a1439f9543312b67bc3/backports_zstd-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e38b22cb32bd9010ad65c0b75a2934448adfdd6bba4387b0f9bacce11e051716", size = 482252, upload-time = "2025-12-06T20:25:49.166Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2f/cebac7f4dc977da1dfbef4004dae6ae12e31f6978db34af530425568dea7/backports_zstd-1.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9af83bbd56f785dffc9a669918e7e7b7f96b0e32fd8877ff90e445ca531463ae", size = 511631, upload-time = "2025-12-06T20:25:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/1e9a135af0141d0e0eabb21491f0f058e73ddb4de356ca174406d60d1dd6/backports_zstd-1.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e3ddf78b1b58291616df13844d5a0e6027b0ce299b15d7d3be2a93b974b7b5f6", size = 585764, upload-time = "2025-12-06T20:25:51.998Z" }, + { url = "https://files.pythonhosted.org/packages/e1/17/e9b29c3d7c4d13e046876fb81b7564a85463d6ab65c10f7fc42a7c658042/backports_zstd-1.2.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:93332bf758954da70b1803ccc73697c7cdec475e4ec04286ff8c568f3786c398", size = 568581, upload-time = "2025-12-06T20:25:53.895Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b5/e2f2f68c204743a9060e50f6910fd7ae1b484250460fa85e843292e8e8d8/backports_zstd-1.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:8f5ba6a97712794ab9c284382a5faefd31ebcb84fa55cb4621e948264dbc6b97", size = 630790, upload-time = "2025-12-06T20:25:55.071Z" }, + { url = "https://files.pythonhosted.org/packages/78/ff/87fc0e498e6c4ad475da178a4e4c81fbfaf19b043ca586a63068ad704f49/backports_zstd-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d541c0c1992ac25bfd836a1e77cf0e70f18d5505aeeebc56c2cf06ffbc209f5", size = 499701, upload-time = "2025-12-06T20:25:56.265Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2e/e10d1b10f087094a108f542b91b44efe928800b6ecd8f1619c9967b6da5b/backports_zstd-1.2.0-cp313-cp313t-win32.whl", hash = "sha256:6c2bc4f7154d5c166912c5b7ee6cbb3a921726d1c2cee7c0f1bfaae736f7c250", size = 289660, upload-time = "2025-12-06T20:25:57.753Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ae/68e5bca7f9990c45c7b1940deccfa4a106c956c5ddd63393332372dad131/backports_zstd-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b7b83331878041a259ec0b384bee7dbfd486b2a2579cd730aca718897d045d7", size = 315026, upload-time = "2025-12-06T20:25:58.952Z" }, + { url = "https://files.pythonhosted.org/packages/e4/33/a519b4da2015069fb36cded5181ff078ecceb852861b675e2c79547ad10d/backports_zstd-1.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:a884be79cd0897436e1e06566d0b6bcad2360afca8e8e27fb19422ba0cca4d7a", size = 289583, upload-time = "2025-12-06T20:26:00.127Z" }, + { url = "https://files.pythonhosted.org/packages/da/3f/4830b483f79b5aa4ed177034097e9c3998eca7663adf0f1d2bc285a4f55f/backports_zstd-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b628fa060ec2190df655b03797be29b93f8e634bc424ef9bd41259ee83f09b6a", size = 436086, upload-time = "2025-12-06T20:26:01.213Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/dcb01ce50e1722fabdbf61875a3b02a8c57a500889ec3c301d7b67a200f6/backports_zstd-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a543a73d90b1870a6f6c0c4eb2dc33a76c7b9eac7726f3daac054b01da1de84d", size = 362445, upload-time = "2025-12-06T20:26:02.39Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9c/49d848425ef5969b8d3e3f20277eaa6fd6c31e18efa34be4017b5447ebd0/backports_zstd-1.2.0-cp39-cp39-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:ca65733cfc10c25653dbe8658e8779071190009fc60a1985e19acc30bbbb0136", size = 506326, upload-time = "2025-12-06T20:26:03.537Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/64e517da07675d28a8d2083f7e3e81e122a9f08c727612681f5489de9b46/backports_zstd-1.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceb76524ec695d579b97816a5353196d6a647df7f37694e4d96ff5a6f041f97", size = 475972, upload-time = "2025-12-06T20:26:04.718Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/0b3c0d66a1f64e835587aa8b028096c9b1e4b1479d6142e804c8e0c5fbbf/backports_zstd-1.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d8bc650565208c429e6886bd943f57215b8fbf75ba2d0bb18b6e114273acb07", size = 581646, upload-time = "2025-12-06T20:26:05.906Z" }, + { url = "https://files.pythonhosted.org/packages/4d/34/9c913beaf971cf9e01472db757c596e96757b85705d2b019ab23b8f7088c/backports_zstd-1.2.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c1b3038c435882917582247cd03e16a50f495e1ae9c80bc21fb32a05e5c0b8c", size = 641303, upload-time = "2025-12-06T20:26:07.472Z" }, + { url = "https://files.pythonhosted.org/packages/ee/85/27e8c2d64de841e6579c3b1736b4f6d95ab397c1d1ba0131dadd87aecf00/backports_zstd-1.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f75dfdfdc7a11e8dba094e09981eede1c1fc8bd5f68a94c4b4d729876741cf5b", size = 491433, upload-time = "2025-12-06T20:26:08.687Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d9/a94646e79f682a2077d58aeb1442d413530107412aefaa43d471c07ab2be/backports_zstd-1.2.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12112452040f341ea9f5e0bb8bb496e954696078c2ad0055e2c1136e02df9492", size = 565382, upload-time = "2025-12-06T20:26:10.269Z" }, + { url = "https://files.pythonhosted.org/packages/52/88/e5e0a9e758656e3eed0999bd8566c3066f0405d05509b50047cfffdc4389/backports_zstd-1.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e9eea5ced6796db3e0d0243d5836fc4c11d94110da20fa774c5384c65acb635", size = 482001, upload-time = "2025-12-06T20:26:11.662Z" }, + { url = "https://files.pythonhosted.org/packages/70/9f/4b7bfd659f0315657e64b91c1e29b2a6395508bc08caefef9eb59c7cb93b/backports_zstd-1.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:118d9ff455bb7fb477c30e41f6c1abbe16da63ffd566d210348fa884e07b8f75", size = 509851, upload-time = "2025-12-06T20:26:13.326Z" }, + { url = "https://files.pythonhosted.org/packages/9d/5a/a1e083ea8104adb9b601f3028ed6d41ce6fe73e7e7887018610bbc087bf2/backports_zstd-1.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8f476ac0792d4096ae011887a6f3e5b9c6f78fdca2d9d7c8bca469bb0e7acb", size = 585995, upload-time = "2025-12-06T20:26:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/33472ced7490cf0306fe88d6a03c9dd7ee27854bf57220bd1b4c314633d5/backports_zstd-1.2.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:6a6f5f53c8b33b4a2192a6a7132921fd3c1338d1961f7c932d077414aacf4039", size = 563295, upload-time = "2025-12-06T20:26:15.913Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e3/12be51b5f9f8681f92328530346624311ef3f761529558630cb73d3fab5f/backports_zstd-1.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9a46613ce06dd8179c221cae299e04dc5fba3bafe297c88a4826b826b76e0b9d", size = 631842, upload-time = "2025-12-06T20:26:17.099Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/b447deb989fe91c89ea4a4483c1744d52dd5f745c9ef8395c81509f98fbe/backports_zstd-1.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2b855ca6b343129966b498d7343094c719c35d6f7a6f807c953dc00b00edc41e", size = 495542, upload-time = "2025-12-06T20:26:18.324Z" }, + { url = "https://files.pythonhosted.org/packages/96/42/9b444f6260a0d1fa110bf5357531e8b7cc5ad5879d3b557ef4fa47989876/backports_zstd-1.2.0-cp39-cp39-win32.whl", hash = "sha256:570b2d499f4cb59e70b7eccbe4ae0d94a8d80696731993f70dc608a52bd1407c", size = 288894, upload-time = "2025-12-06T20:26:19.529Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/06fdaafd094c688b8b54e53427947115f461452184974784ad830b373364/backports_zstd-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:82224c54b5ef014a310cc0790b1cb70adab9ab9b0d6b7971e68513c8c0156b15", size = 313781, upload-time = "2025-12-06T20:26:20.74Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/c5f3404c2c8619ae30c23e2e1a1dea967a5fa32b708d6436f24f2a71c3d7/backports_zstd-1.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:4200498fd135cf7ef2c96859b11ba3ad95c10be797d8b85da9f027206bbdba4a", size = 289010, upload-time = "2025-12-06T20:26:21.952Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/0be0f44bfd5a77b6dc476eae791bb2847f786bef717aab510b0764aba2f9/backports_zstd-1.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f94f434e2265c067a7e6e2ea50f93e709421f2c9e4a2458a80284065a79caefd", size = 410041, upload-time = "2025-12-06T20:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7a/1d2390341fb97e9fa9c3242dce6825646bd6f47d96ca862bf070dce0c943/backports_zstd-1.2.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0d9e4d77f03524b59bc2d8d9636e5d808e50ef0d20c56f0ab2ab8ee00b6a367a", size = 339556, upload-time = "2025-12-06T20:26:24.718Z" }, + { url = "https://files.pythonhosted.org/packages/5f/21/36a2a17f5cd360ddd89bc6d24d2cfb1f6b1e4051fe70da9e172697763d7d/backports_zstd-1.2.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:5d8014aeaec1f47f3209bd5e9e29282fcafa7b9076f89cc342a5dab3f298fdec", size = 420605, upload-time = "2025-12-06T20:26:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/ee54f99fda973cf18dcb5ef7fb481449b1e5770af4f449ac06af77c995d2/backports_zstd-1.2.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35b8390fde5644a067a66cbd6f1b1293f48264f7bd867f0148b09d4f206005bf", size = 394149, upload-time = "2025-12-06T20:26:27.57Z" }, + { url = "https://files.pythonhosted.org/packages/36/d6/1e41a5469606fbe100b4841f03c42275a1b114fa02921cbdedb0aadeaa00/backports_zstd-1.2.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb3b0e175170422b1d095709356cb688b664d381f3dba424ae5107990ca913ee", size = 414113, upload-time = "2025-12-06T20:26:29.072Z" }, + { url = "https://files.pythonhosted.org/packages/46/68/26c9802339a885f567f1c7bbfa5d5b786545e5bb754ba385f81dd6d2ccc1/backports_zstd-1.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5f7e28d44e322c16aaf8973ce3c062105b6d88fe2b4f4611b40e410176a4fd40", size = 299966, upload-time = "2025-12-06T20:26:30.333Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b1/2b7b0e1dcd165cf0e0fc792b74138489bfb90d66d7ded86f7f7e91f6764c/backports_zstd-1.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c3a1748eaac8fd1c862d3e16c6beb023f118a82d7230a32d33f6ce65752a2d6", size = 409938, upload-time = "2025-12-06T20:26:31.55Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/c91b6a4681eb8f13c7919ce551d4b5364e9fd6f07e770e4e01ca2c0b1f92/backports_zstd-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9e126bd32c97b1f4717d30530a3762c1f9a85d5d629fdc2ad210e6427fd6849c", size = 339472, upload-time = "2025-12-06T20:26:32.765Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e6/abb1b8e6e5c9dfb8cbf05669745de81273b46fef5bafee00fb1698c75ce8/backports_zstd-1.2.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:e4524beed644c4074ff017e96afc91c5e98064f40680fa859bddee5974641805", size = 420603, upload-time = "2025-12-06T20:26:34.374Z" }, + { url = "https://files.pythonhosted.org/packages/50/2e/ca206b678cdbd2eca56aa2ce49996f6d6cd21db840efa6e2e6f73d4cb7db/backports_zstd-1.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72bc2b03590d66d8706e4717da25cc6c192f5a6bfc3f6148f671f79e73afd4e8", size = 394149, upload-time = "2025-12-06T20:26:35.563Z" }, + { url = "https://files.pythonhosted.org/packages/50/c2/ba7bcfe28dc3f8e8848419757883c8a0c7a4263dcd5d3988dd7f49818ca8/backports_zstd-1.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89afe7d6e86bedbf2bac083beb096732a1e92025a5efa9c972941a6140994485", size = 414114, upload-time = "2025-12-06T20:26:36.808Z" }, + { url = "https://files.pythonhosted.org/packages/71/ad/d5e8a3b28150e4f310999ef26db1e6b5f3bbb899c07d88ebd910954fcaf2/backports_zstd-1.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f00066de6ffd72c653b43afb9aaa36969fd0e2c91f66adb45a11f73e6423263a", size = 299968, upload-time = "2025-12-06T20:26:38.382Z" }, ] [[package]] name = "beautifulsoup4" -version = "4.13.4" +version = "4.14.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] name = "boto3" -version = "1.40.0" +version = "1.42.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/34/298ef2023d7d88069776c9cc26b42ba6f05d143a1c9b44a0f65cd795c65b/boto3-1.40.0.tar.gz", hash = "sha256:fc1b3ca3baf3d8820c6faddf47cbba8ad3cd16f8e8d7e2f76d304bf995932eb7", size = 111847, upload-time = "2025-07-31T19:21:06.735Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/66/ffe9623d64e97800ff6bac26953cd9ef99410fb864a0b26a0ea2e09b97f0/boto3-1.42.12.tar.gz", hash = "sha256:649b134d25b278c24fcc8b3f94519de3884283b7848dc32f42b0ffdd9d19ce99", size = 112868, upload-time = "2025-12-17T20:30:42.394Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/44/158581021038c5fc886ffa27fa4731fb4939258da7a23e0bc70b2d5757c9/boto3-1.40.0-py3-none-any.whl", hash = "sha256:959443055d2af676c336cc6033b3f870a8a924384b70d0b2905081d649378179", size = 139882, upload-time = "2025-07-31T19:21:04.65Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8b/20a90c75499e3c3a8e3eb5607d930c723577ef8c64968b9be6b743f18158/boto3-1.42.12-py3-none-any.whl", hash = "sha256:8112e1beb5978bb455ea4b41a9ef26fc408f6340d8ff69ef93dded4f80fd53e9", size = 140573, upload-time = "2025-12-17T20:30:40.063Z" }, ] [[package]] name = "botocore" -version = "1.40.0" +version = "1.42.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/e7/770ce910457ac6c68ea79b83892ab7a7cb08528f5d1dd77e51bf02a8529e/botocore-1.40.0.tar.gz", hash = "sha256:850242560dc8e74d542045a81eb6cc15f1b730b4ba55ba5b30e6d686548dfcaf", size = 14262316, upload-time = "2025-07-31T19:20:56.662Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/b6/9b7988a8476712cdbfeeb68c733933005465c85ebf0ee469a6ea5ca3415c/botocore-1.42.12.tar.gz", hash = "sha256:1f9f63c3d6bb1f768519da30d6018706443c5d8af5472274d183a4945f3d81f8", size = 14879004, upload-time = "2025-12-17T20:30:29.542Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/5a/bebc53f022514412613615b09aef20fbe804abb3ea26ec27e504a2d21c8f/botocore-1.40.0-py3-none-any.whl", hash = "sha256:2063e6d035a6a382b2ae37e40f5144044e55d4e091910d0c9f1be3121ad3e4e6", size = 13921768, upload-time = "2025-07-31T19:20:51.487Z" }, + { url = "https://files.pythonhosted.org/packages/8f/73/22764d0a17130b7d95b2a4104607e6db5487a0e5afb68f5691260ae9c3dc/botocore-1.42.12-py3-none-any.whl", hash = "sha256:4f163880350f6d831857ce5d023875b7c6534be862e5affd9fcf82b8d1ab3537", size = 14552878, upload-time = "2025-12-17T20:30:24.671Z" }, ] [[package]] name = "certifi" -version = "2025.7.14" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -345,76 +354,107 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, - { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, - { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, - { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, - { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, - { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" }, - { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" }, - { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" }, - { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" }, - { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" }, - { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" }, - { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] @@ -434,19 +474,18 @@ wheels = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.14.*'", - "python_full_version >= '3.15' or (python_full_version >= '3.11' and python_full_version < '3.14')", + "python_full_version >= '3.11'", "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -460,51 +499,97 @@ wheels = [ [[package]] name = "coverage" -version = "7.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/d3/3ec80acdd57a0d6a1111b978ade388824f37126446fd6750d38bfaca949c/coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8", size = 798314, upload-time = "2024-04-23T17:42:35.508Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/db/08d54dbc12fdfe5857b06105fd1235bdebb7da7c11cd1a0fae936556162a/coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c", size = 210025, upload-time = "2024-04-23T17:40:22.328Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ff/02c4bcff1025b4a788aa3933e1cd1474d79de43e0d859273b3319ef43cd3/coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b", size = 210499, upload-time = "2024-04-23T17:40:25.747Z" }, - { url = "https://files.pythonhosted.org/packages/ab/b1/7820a8ef62adeebd37612af9d2369f4467a3bc2641dea1243450def5489e/coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932", size = 238399, upload-time = "2024-04-23T17:40:27.591Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/23a388f3ce16c5ea01a454fef6a9039115abd40b748027d4fef18b3628a7/coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3", size = 236676, upload-time = "2024-04-23T17:40:30.455Z" }, - { url = "https://files.pythonhosted.org/packages/f8/81/e871b0d58ca5d6cc27d00b2f668ce09c4643ef00512341f3a592a81fb6cd/coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517", size = 237467, upload-time = "2024-04-23T17:40:32.704Z" }, - { url = "https://files.pythonhosted.org/packages/95/cb/42a6d34d5840635394f1e172aaa0e7cbd9346155e5004a8ee75d8e434c6b/coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a", size = 243539, upload-time = "2024-04-23T17:40:35.068Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6a/18b3819919fdfd3e2062a75219b363f895f24ae5b80e72ffe5dfb1a7e9c8/coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880", size = 241725, upload-time = "2024-04-23T17:40:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/a0650978e8b8f78d269358421b7401acaf7cb89e957b2e1be5205ea5940e/coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58", size = 242913, upload-time = "2024-04-23T17:40:39.992Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fe/95a74158fa0eda56d39783e918edc6fbb3dd3336be390557fc0a2815ecd4/coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4", size = 212381, upload-time = "2024-04-23T17:40:42.632Z" }, - { url = "https://files.pythonhosted.org/packages/4c/26/b276e0c70cba5059becce2594a268a2731d5b4f2386e9a6afdf37ffa3d44/coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a", size = 213225, upload-time = "2024-04-23T17:40:45.175Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/964bb667ea37d64b25f04d4cfaf6232cdb7a6472e1f4a4faf0459ddcec40/coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375", size = 210130, upload-time = "2024-04-23T17:40:47.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/56/31edd4baa132fe2b991437e0acf3e36c50418370044a89b65518e5581f4c/coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb", size = 210617, upload-time = "2024-04-23T17:40:49.82Z" }, - { url = "https://files.pythonhosted.org/packages/26/6d/4cd14bd0221180c307fae4f8ef00dbd86a13507c25081858c620aa6fafd8/coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95", size = 242048, upload-time = "2024-04-23T17:40:52.779Z" }, - { url = "https://files.pythonhosted.org/packages/84/60/7eb84255bd9947b140e0382721b0a1b25fd670b4f0f176f11f90b5632d02/coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d", size = 239619, upload-time = "2024-04-23T17:40:54.847Z" }, - { url = "https://files.pythonhosted.org/packages/76/6b/e8f4696194fdf3c19422f2a80ac10e03a9322f93e6c9ef57a89e03a8c8f7/coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743", size = 241321, upload-time = "2024-04-23T17:40:57.092Z" }, - { url = "https://files.pythonhosted.org/packages/3f/1c/6a6990fd2e6890807775852882b1ed0a8e50519a525252490b0c219aa8a5/coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1", size = 250419, upload-time = "2024-04-23T17:40:59.051Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/b6422a1422381704dd015cc23e503acd1a44a6bdc4e59c75f8c6a2b24151/coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de", size = 248794, upload-time = "2024-04-23T17:41:01.803Z" }, - { url = "https://files.pythonhosted.org/packages/9b/93/e8231000754d4a31fe9a6c550f6a436eacd2e50763ba2b418f10b2308e45/coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff", size = 249873, upload-time = "2024-04-23T17:41:04.719Z" }, - { url = "https://files.pythonhosted.org/packages/d3/6f/eb5aae80bf9d01d0f293121d4caa660ac968da2cb967f82547a7b5e8d65b/coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d", size = 212380, upload-time = "2024-04-23T17:41:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/30/73/b70ab57f11b62f5ca9a83f43cae752fbbb4417bea651875235c32eb2fc2e/coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656", size = 213316, upload-time = "2024-04-23T17:41:09.233Z" }, - { url = "https://files.pythonhosted.org/packages/36/db/f4e17ffb5ac2d125c72ee3b235c2e04f85a4296a6a9e17730e218af113d8/coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9", size = 210340, upload-time = "2024-04-23T17:41:11.811Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bc/d7e832280f269be9e8d46cff5c4031b4840f1844674dc53ad93c5a9c1da6/coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64", size = 210612, upload-time = "2024-04-23T17:41:14.256Z" }, - { url = "https://files.pythonhosted.org/packages/54/84/543e2cd6c1de30c7522a0afcb040677957bac756dd8677bade8bdd9274ba/coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af", size = 242926, upload-time = "2024-04-23T17:41:16.284Z" }, - { url = "https://files.pythonhosted.org/packages/ad/06/570533f747141b4fd727a193317e16c6e677ed7945e23a195b8f64e685a2/coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc", size = 240294, upload-time = "2024-04-23T17:41:19.099Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d9/ec4ba0913195d240d026670d41b91f3e5b9a8a143a385f93a09e97c90f5c/coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2", size = 242232, upload-time = "2024-04-23T17:41:21.05Z" }, - { url = "https://files.pythonhosted.org/packages/d9/3f/1a613c32aa1980d20d6ca2f54faf800df04aafad6016d7132b3276d8715d/coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1", size = 249171, upload-time = "2024-04-23T17:41:23.723Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3b/e16b12693572fd69148453abc6ddcd20cbeae6f0a040b5ed6af2f75b646f/coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb", size = 247073, upload-time = "2024-04-23T17:41:25.719Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3e/04a05d40bb09f90a312296a32fb2c5ade2dfcf803edf777ad18b97547503/coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2", size = 248812, upload-time = "2024-04-23T17:41:27.951Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f7/3a8b7b0affe548227f3d45e248c0f22c5b55bff0ee062b49afc165b3ff25/coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4", size = 212634, upload-time = "2024-04-23T17:41:30.114Z" }, - { url = "https://files.pythonhosted.org/packages/7c/31/5f5286d2a5e21e1fe5670629bb24c79bf46383a092e74e00077e7a178e5c/coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475", size = 213460, upload-time = "2024-04-23T17:41:32.683Z" }, - { url = "https://files.pythonhosted.org/packages/62/18/5573216d5b8db7d9f29189350dcd81830a03a624966c35f8201ae10df09c/coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1", size = 210014, upload-time = "2024-04-23T17:41:56.535Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0e/e98d6c6d569d65ff3195f095e6b006b3d7780fd6182322a25e7dfe0d53d3/coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5", size = 210494, upload-time = "2024-04-23T17:41:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/d3/63/98e5a6b7ed1bfca874729ee309cc49a6d6658ab9e479a2b6d223ccc96e03/coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631", size = 237996, upload-time = "2024-04-23T17:42:01.514Z" }, - { url = "https://files.pythonhosted.org/packages/76/e4/d3c67a0a092127b8a3dffa2f75334a8cdb2cefc99e3d75a7f42cf1ff98a9/coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46", size = 236287, upload-time = "2024-04-23T17:42:03.838Z" }, - { url = "https://files.pythonhosted.org/packages/12/7f/9b787ffc31bc39aa9e98c7005b698e7c6539bd222043e4a9c83b83c782a2/coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e", size = 237070, upload-time = "2024-04-23T17:42:06.993Z" }, - { url = "https://files.pythonhosted.org/packages/31/ee/9998a0d855cad5f8e04062f7428b83c34aa643e5df468409593a480d5585/coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be", size = 243115, upload-time = "2024-04-23T17:42:09.281Z" }, - { url = "https://files.pythonhosted.org/packages/16/94/1e348cd4445404c588ec8199adde0b45727b1d7989d8fb097d39c93e3da5/coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b", size = 241315, upload-time = "2024-04-23T17:42:11.836Z" }, - { url = "https://files.pythonhosted.org/packages/28/17/6fe1695d2a706e586b87a407598f4ed82dd218b2b43cdc790f695f259849/coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0", size = 242467, upload-time = "2024-04-23T17:42:14.019Z" }, - { url = "https://files.pythonhosted.org/packages/81/a2/1e550272c8b1f89b980504230b1a929de83d8f3d5ecb268477b32e5996a6/coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7", size = 212394, upload-time = "2024-04-23T17:42:17.655Z" }, - { url = "https://files.pythonhosted.org/packages/c9/48/7d3c31064c5adcc743fe5370cf7e198cee06cc0e2d37b5cbe930691a3f54/coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493", size = 213246, upload-time = "2024-04-23T17:42:19.777Z" }, - { url = "https://files.pythonhosted.org/packages/34/81/f00ce7ef95479085feb01fa9e352b2b5b2b9d24767acf2266d6267a6dba9/coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067", size = 202381, upload-time = "2024-04-23T17:42:22.127Z" }, +version = "7.10.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/1d/2e64b43d978b5bd184e0756a41415597dfef30fcbd90b747474bd749d45f/coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", size = 217025, upload-time = "2025-08-29T15:32:57.169Z" }, + { url = "https://files.pythonhosted.org/packages/23/62/b1e0f513417c02cc10ef735c3ee5186df55f190f70498b3702d516aad06f/coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", size = 217419, upload-time = "2025-08-29T15:32:59.908Z" }, + { url = "https://files.pythonhosted.org/packages/e7/16/b800640b7a43e7c538429e4d7223e0a94fd72453a1a048f70bf766f12e96/coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", size = 244180, upload-time = "2025-08-29T15:33:01.608Z" }, + { url = "https://files.pythonhosted.org/packages/fb/6f/5e03631c3305cad187eaf76af0b559fff88af9a0b0c180d006fb02413d7a/coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", size = 245992, upload-time = "2025-08-29T15:33:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a1/f30ea0fb400b080730125b490771ec62b3375789f90af0bb68bfb8a921d7/coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", size = 247851, upload-time = "2025-08-29T15:33:04.603Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/cfa8fee8e8ef9a6bb76c7bef039f3302f44e615d2194161a21d3d83ac2e9/coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", size = 245891, upload-time = "2025-08-29T15:33:06.176Z" }, + { url = "https://files.pythonhosted.org/packages/93/a9/51be09b75c55c4f6c16d8d73a6a1d46ad764acca0eab48fa2ffaef5958fe/coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", size = 243909, upload-time = "2025-08-29T15:33:07.74Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a6/ba188b376529ce36483b2d585ca7bdac64aacbe5aa10da5978029a9c94db/coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", size = 244786, upload-time = "2025-08-29T15:33:08.965Z" }, + { url = "https://files.pythonhosted.org/packages/d0/4c/37ed872374a21813e0d3215256180c9a382c3f5ced6f2e5da0102fc2fd3e/coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", size = 219521, upload-time = "2025-08-29T15:33:10.599Z" }, + { url = "https://files.pythonhosted.org/packages/8e/36/9311352fdc551dec5b973b61f4e453227ce482985a9368305880af4f85dd/coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", size = 220417, upload-time = "2025-08-29T15:33:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, + { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, + { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, + { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, + { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, + { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, + { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, + { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, + { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, + { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, + { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, + { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, + { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, + { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, + { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" }, + { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" }, + { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" }, + { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" }, + { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" }, + { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" }, + { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" }, + { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" }, + { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" }, + { url = "https://files.pythonhosted.org/packages/91/70/f73ad83b1d2fd2d5825ac58c8f551193433a7deaf9b0d00a8b69ef61cd9a/coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352", size = 217009, upload-time = "2025-08-29T15:34:57.381Z" }, + { url = "https://files.pythonhosted.org/packages/01/e8/099b55cd48922abbd4b01ddd9ffa352408614413ebfc965501e981aced6b/coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612", size = 217400, upload-time = "2025-08-29T15:34:58.985Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d1/c6bac7c9e1003110a318636fef3b5c039df57ab44abcc41d43262a163c28/coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b", size = 243835, upload-time = "2025-08-29T15:35:00.541Z" }, + { url = "https://files.pythonhosted.org/packages/01/f9/82c6c061838afbd2172e773156c0aa84a901d59211b4975a4e93accf5c89/coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144", size = 245658, upload-time = "2025-08-29T15:35:02.135Z" }, + { url = "https://files.pythonhosted.org/packages/81/6a/35674445b1d38161148558a3ff51b0aa7f0b54b1def3abe3fbd34efe05bc/coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b", size = 247433, upload-time = "2025-08-29T15:35:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/98c99e7cafb288730a93535092eb433b5503d529869791681c4f2e2012a8/coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862", size = 245315, upload-time = "2025-08-29T15:35:05.629Z" }, + { url = "https://files.pythonhosted.org/packages/09/05/123e0dba812408c719c319dea05782433246f7aa7b67e60402d90e847545/coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2", size = 243385, upload-time = "2025-08-29T15:35:07.494Z" }, + { url = "https://files.pythonhosted.org/packages/67/52/d57a42502aef05c6325f28e2e81216c2d9b489040132c18725b7a04d1448/coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78", size = 244343, upload-time = "2025-08-29T15:35:09.55Z" }, + { url = "https://files.pythonhosted.org/packages/6b/22/7f6fad7dbb37cf99b542c5e157d463bd96b797078b1ec506691bc836f476/coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c", size = 219530, upload-time = "2025-08-29T15:35:11.167Z" }, + { url = "https://files.pythonhosted.org/packages/62/30/e2fda29bfe335026027e11e6a5e57a764c9df13127b5cf42af4c3e99b937/coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf", size = 220432, upload-time = "2025-08-29T15:35:12.902Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, ] [package.optional-dependencies] @@ -639,67 +724,67 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.1" +version = "46.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/62/e3664e6ffd7743e1694b244dde70b43a394f6f7fbcacf7014a8ff5197c73/cryptography-46.0.1.tar.gz", hash = "sha256:ed570874e88f213437f5cf758f9ef26cbfc3f336d889b1e592ee11283bb8d1c7", size = 749198, upload-time = "2025-09-17T00:10:35.797Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/8c/44ee01267ec01e26e43ebfdae3f120ec2312aa72fa4c0507ebe41a26739f/cryptography-46.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:1cd6d50c1a8b79af1a6f703709d8973845f677c8e97b1268f5ff323d38ce8475", size = 7285044, upload-time = "2025-09-17T00:08:36.807Z" }, - { url = "https://files.pythonhosted.org/packages/22/59/9ae689a25047e0601adfcb159ec4f83c0b4149fdb5c3030cc94cd218141d/cryptography-46.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ff483716be32690c14636e54a1f6e2e1b7bf8e22ca50b989f88fa1b2d287080", size = 4308182, upload-time = "2025-09-17T00:08:39.388Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/ca6cc9df7118f2fcd142c76b1da0f14340d77518c05b1ebfbbabca6b9e7d/cryptography-46.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9873bf7c1f2a6330bdfe8621e7ce64b725784f9f0c3a6a55c3047af5849f920e", size = 4572393, upload-time = "2025-09-17T00:08:41.663Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a3/0f5296f63815d8e985922b05c31f77ce44787b3127a67c0b7f70f115c45f/cryptography-46.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0dfb7c88d4462a0cfdd0d87a3c245a7bc3feb59de101f6ff88194f740f72eda6", size = 4308400, upload-time = "2025-09-17T00:08:43.559Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8c/74fcda3e4e01be1d32775d5b4dd841acaac3c1b8fa4d0774c7ac8d52463d/cryptography-46.0.1-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e22801b61613ebdebf7deb18b507919e107547a1d39a3b57f5f855032dd7cfb8", size = 4015786, upload-time = "2025-09-17T00:08:45.758Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b8/85d23287baeef273b0834481a3dd55bbed3a53587e3b8d9f0898235b8f91/cryptography-46.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:757af4f6341ce7a1e47c326ca2a81f41d236070217e5fbbad61bbfe299d55d28", size = 4982606, upload-time = "2025-09-17T00:08:47.602Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d3/de61ad5b52433b389afca0bc70f02a7a1f074651221f599ce368da0fe437/cryptography-46.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f7a24ea78de345cfa7f6a8d3bde8b242c7fac27f2bd78fa23474ca38dfaeeab9", size = 4604234, upload-time = "2025-09-17T00:08:49.879Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1f/dbd4d6570d84748439237a7478d124ee0134bf166ad129267b7ed8ea6d22/cryptography-46.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e8776dac9e660c22241b6587fae51a67b4b0147daa4d176b172c3ff768ad736", size = 4307669, upload-time = "2025-09-17T00:08:52.321Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fd/ca0a14ce7f0bfe92fa727aacaf2217eb25eb7e4ed513b14d8e03b26e63ed/cryptography-46.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9f40642a140c0c8649987027867242b801486865277cbabc8c6059ddef16dc8b", size = 4947579, upload-time = "2025-09-17T00:08:54.697Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/09c30543bb93401f6f88fce556b3bdbb21e55ae14912c04b7bf355f5f96c/cryptography-46.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:449ef2b321bec7d97ef2c944173275ebdab78f3abdd005400cc409e27cd159ab", size = 4603669, upload-time = "2025-09-17T00:08:57.16Z" }, - { url = "https://files.pythonhosted.org/packages/23/9a/38cb01cb09ce0adceda9fc627c9cf98eb890fc8d50cacbe79b011df20f8a/cryptography-46.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2dd339ba3345b908fa3141ddba4025568fa6fd398eabce3ef72a29ac2d73ad75", size = 4435828, upload-time = "2025-09-17T00:08:59.606Z" }, - { url = "https://files.pythonhosted.org/packages/0f/53/435b5c36a78d06ae0bef96d666209b0ecd8f8181bfe4dda46536705df59e/cryptography-46.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7411c910fb2a412053cf33cfad0153ee20d27e256c6c3f14d7d7d1d9fec59fd5", size = 4709553, upload-time = "2025-09-17T00:09:01.832Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c4/0da6e55595d9b9cd3b6eb5dc22f3a07ded7f116a3ea72629cab595abb804/cryptography-46.0.1-cp311-abi3-win32.whl", hash = "sha256:cbb8e769d4cac884bb28e3ff620ef1001b75588a5c83c9c9f1fdc9afbe7f29b0", size = 3058327, upload-time = "2025-09-17T00:09:03.726Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/cd29a35e0d6e78a0ee61793564c8cff0929c38391cb0de27627bdc7525aa/cryptography-46.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:92e8cfe8bd7dd86eac0a677499894862cd5cc2fd74de917daa881d00871ac8e7", size = 3523893, upload-time = "2025-09-17T00:09:06.272Z" }, - { url = "https://files.pythonhosted.org/packages/f2/dd/eea390f3e78432bc3d2f53952375f8b37cb4d37783e626faa6a51e751719/cryptography-46.0.1-cp311-abi3-win_arm64.whl", hash = "sha256:db5597a4c7353b2e5fb05a8e6cb74b56a4658a2b7bf3cb6b1821ae7e7fd6eaa0", size = 2932145, upload-time = "2025-09-17T00:09:08.568Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fb/c73588561afcd5e24b089952bd210b14676c0c5bf1213376350ae111945c/cryptography-46.0.1-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:4c49eda9a23019e11d32a0eb51a27b3e7ddedde91e099c0ac6373e3aacc0d2ee", size = 7193928, upload-time = "2025-09-17T00:09:10.595Z" }, - { url = "https://files.pythonhosted.org/packages/26/34/0ff0bb2d2c79f25a2a63109f3b76b9108a906dd2a2eb5c1d460b9938adbb/cryptography-46.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9babb7818fdd71394e576cf26c5452df77a355eac1a27ddfa24096665a27f8fd", size = 4293515, upload-time = "2025-09-17T00:09:12.861Z" }, - { url = "https://files.pythonhosted.org/packages/df/b7/d4f848aee24ecd1be01db6c42c4a270069a4f02a105d9c57e143daf6cf0f/cryptography-46.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9f2c4cc63be3ef43c0221861177cee5d14b505cd4d4599a89e2cd273c4d3542a", size = 4545619, upload-time = "2025-09-17T00:09:15.397Z" }, - { url = "https://files.pythonhosted.org/packages/44/a5/42fedefc754fd1901e2d95a69815ea4ec8a9eed31f4c4361fcab80288661/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:41c281a74df173876da1dc9a9b6953d387f06e3d3ed9284e3baae3ab3f40883a", size = 4299160, upload-time = "2025-09-17T00:09:17.155Z" }, - { url = "https://files.pythonhosted.org/packages/86/a1/cd21174f56e769c831fbbd6399a1b7519b0ff6280acec1b826d7b072640c/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0a17377fa52563d730248ba1f68185461fff36e8bc75d8787a7dd2e20a802b7a", size = 3994491, upload-time = "2025-09-17T00:09:18.971Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2f/a8cbfa1c029987ddc746fd966711d4fa71efc891d37fbe9f030fe5ab4eec/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0d1922d9280e08cde90b518a10cd66831f632960a8d08cb3418922d83fce6f12", size = 4960157, upload-time = "2025-09-17T00:09:20.923Z" }, - { url = "https://files.pythonhosted.org/packages/67/ae/63a84e6789e0d5a2502edf06b552bcb0fa9ff16147265d5c44a211942abe/cryptography-46.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:af84e8e99f1a82cea149e253014ea9dc89f75b82c87bb6c7242203186f465129", size = 4577263, upload-time = "2025-09-17T00:09:23.356Z" }, - { url = "https://files.pythonhosted.org/packages/ef/8f/1b9fa8e92bd9cbcb3b7e1e593a5232f2c1e6f9bd72b919c1a6b37d315f92/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ef648d2c690703501714588b2ba640facd50fd16548133b11b2859e8655a69da", size = 4298703, upload-time = "2025-09-17T00:09:25.566Z" }, - { url = "https://files.pythonhosted.org/packages/c3/af/bb95db070e73fea3fae31d8a69ac1463d89d1c084220f549b00dd01094a8/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:e94eb5fa32a8a9f9bf991f424f002913e3dd7c699ef552db9b14ba6a76a6313b", size = 4926363, upload-time = "2025-09-17T00:09:27.451Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3b/d8fb17ffeb3a83157a1cc0aa5c60691d062aceecba09c2e5e77ebfc1870c/cryptography-46.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:534b96c0831855e29fc3b069b085fd185aa5353033631a585d5cd4dd5d40d657", size = 4576958, upload-time = "2025-09-17T00:09:29.924Z" }, - { url = "https://files.pythonhosted.org/packages/d9/46/86bc3a05c10c8aa88c8ae7e953a8b4e407c57823ed201dbcba55c4d655f4/cryptography-46.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9b55038b5c6c47559aa33626d8ecd092f354e23de3c6975e4bb205df128a2a0", size = 4422507, upload-time = "2025-09-17T00:09:32.222Z" }, - { url = "https://files.pythonhosted.org/packages/a8/4e/387e5a21dfd2b4198e74968a541cfd6128f66f8ec94ed971776e15091ac3/cryptography-46.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ec13b7105117dbc9afd023300fb9954d72ca855c274fe563e72428ece10191c0", size = 4683964, upload-time = "2025-09-17T00:09:34.118Z" }, - { url = "https://files.pythonhosted.org/packages/25/a3/f9f5907b166adb8f26762071474b38bbfcf89858a5282f032899075a38a1/cryptography-46.0.1-cp314-cp314t-win32.whl", hash = "sha256:504e464944f2c003a0785b81668fe23c06f3b037e9cb9f68a7c672246319f277", size = 3029705, upload-time = "2025-09-17T00:09:36.381Z" }, - { url = "https://files.pythonhosted.org/packages/12/66/4d3a4f1850db2e71c2b1628d14b70b5e4c1684a1bd462f7fffb93c041c38/cryptography-46.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c52fded6383f7e20eaf70a60aeddd796b3677c3ad2922c801be330db62778e05", size = 3502175, upload-time = "2025-09-17T00:09:38.261Z" }, - { url = "https://files.pythonhosted.org/packages/52/c7/9f10ad91435ef7d0d99a0b93c4360bea3df18050ff5b9038c489c31ac2f5/cryptography-46.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:9495d78f52c804b5ec8878b5b8c7873aa8e63db9cd9ee387ff2db3fffe4df784", size = 2912354, upload-time = "2025-09-17T00:09:40.078Z" }, - { url = "https://files.pythonhosted.org/packages/98/e5/fbd632385542a3311915976f88e0dfcf09e62a3fc0aff86fb6762162a24d/cryptography-46.0.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d84c40bdb8674c29fa192373498b6cb1e84f882889d21a471b45d1f868d8d44b", size = 7255677, upload-time = "2025-09-17T00:09:42.407Z" }, - { url = "https://files.pythonhosted.org/packages/56/3e/13ce6eab9ad6eba1b15a7bd476f005a4c1b3f299f4c2f32b22408b0edccf/cryptography-46.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9ed64e5083fa806709e74fc5ea067dfef9090e5b7a2320a49be3c9df3583a2d8", size = 4301110, upload-time = "2025-09-17T00:09:45.614Z" }, - { url = "https://files.pythonhosted.org/packages/a2/67/65dc233c1ddd688073cf7b136b06ff4b84bf517ba5529607c9d79720fc67/cryptography-46.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:341fb7a26bc9d6093c1b124b9f13acc283d2d51da440b98b55ab3f79f2522ead", size = 4562369, upload-time = "2025-09-17T00:09:47.601Z" }, - { url = "https://files.pythonhosted.org/packages/17/db/d64ae4c6f4e98c3dac5bf35dd4d103f4c7c345703e43560113e5e8e31b2b/cryptography-46.0.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6ef1488967e729948d424d09c94753d0167ce59afba8d0f6c07a22b629c557b2", size = 4302126, upload-time = "2025-09-17T00:09:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/3d/19/5f1eea17d4805ebdc2e685b7b02800c4f63f3dd46cfa8d4c18373fea46c8/cryptography-46.0.1-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7823bc7cdf0b747ecfb096d004cc41573c2f5c7e3a29861603a2871b43d3ef32", size = 4009431, upload-time = "2025-09-17T00:09:51.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/229ba6088fe7abccbfe4c5edb96c7a5ad547fac5fdd0d40aa6ea540b2985/cryptography-46.0.1-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f736ab8036796f5a119ff8211deda416f8c15ce03776db704a7a4e17381cb2ef", size = 4980739, upload-time = "2025-09-17T00:09:54.181Z" }, - { url = "https://files.pythonhosted.org/packages/3a/9c/50aa38907b201e74bc43c572f9603fa82b58e831bd13c245613a23cff736/cryptography-46.0.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e46710a240a41d594953012213ea8ca398cd2448fbc5d0f1be8160b5511104a0", size = 4592289, upload-time = "2025-09-17T00:09:56.731Z" }, - { url = "https://files.pythonhosted.org/packages/5a/33/229858f8a5bb22f82468bb285e9f4c44a31978d5f5830bb4ea1cf8a4e454/cryptography-46.0.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:84ef1f145de5aee82ea2447224dc23f065ff4cc5791bb3b506615957a6ba8128", size = 4301815, upload-time = "2025-09-17T00:09:58.548Z" }, - { url = "https://files.pythonhosted.org/packages/52/cb/b76b2c87fbd6ed4a231884bea3ce073406ba8e2dae9defad910d33cbf408/cryptography-46.0.1-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9394c7d5a7565ac5f7d9ba38b2617448eba384d7b107b262d63890079fad77ca", size = 4943251, upload-time = "2025-09-17T00:10:00.475Z" }, - { url = "https://files.pythonhosted.org/packages/94/0f/f66125ecf88e4cb5b8017ff43f3a87ede2d064cb54a1c5893f9da9d65093/cryptography-46.0.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ed957044e368ed295257ae3d212b95456bd9756df490e1ac4538857f67531fcc", size = 4591247, upload-time = "2025-09-17T00:10:02.874Z" }, - { url = "https://files.pythonhosted.org/packages/f6/22/9f3134ae436b63b463cfdf0ff506a0570da6873adb4bf8c19b8a5b4bac64/cryptography-46.0.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f7de12fa0eee6234de9a9ce0ffcfa6ce97361db7a50b09b65c63ac58e5f22fc7", size = 4428534, upload-time = "2025-09-17T00:10:04.994Z" }, - { url = "https://files.pythonhosted.org/packages/89/39/e6042bcb2638650b0005c752c38ea830cbfbcbb1830e4d64d530000aa8dc/cryptography-46.0.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7fab1187b6c6b2f11a326f33b036f7168f5b996aedd0c059f9738915e4e8f53a", size = 4699541, upload-time = "2025-09-17T00:10:06.925Z" }, - { url = "https://files.pythonhosted.org/packages/68/46/753d457492d15458c7b5a653fc9a84a1c9c7a83af6ebdc94c3fc373ca6e8/cryptography-46.0.1-cp38-abi3-win32.whl", hash = "sha256:45f790934ac1018adeba46a0f7289b2b8fe76ba774a88c7f1922213a56c98bc1", size = 3043779, upload-time = "2025-09-17T00:10:08.951Z" }, - { url = "https://files.pythonhosted.org/packages/2f/50/b6f3b540c2f6ee712feeb5fa780bb11fad76634e71334718568e7695cb55/cryptography-46.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:7176a5ab56fac98d706921f6416a05e5aff7df0e4b91516f450f8627cda22af3", size = 3517226, upload-time = "2025-09-17T00:10:10.769Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e8/77d17d00981cdd27cc493e81e1749a0b8bbfb843780dbd841e30d7f50743/cryptography-46.0.1-cp38-abi3-win_arm64.whl", hash = "sha256:efc9e51c3e595267ff84adf56e9b357db89ab2279d7e375ffcaf8f678606f3d9", size = 2923149, upload-time = "2025-09-17T00:10:13.236Z" }, - { url = "https://files.pythonhosted.org/packages/14/b9/b260180b31a66859648cfed5c980544ee22b15f8bd20ef82a23f58c0b83e/cryptography-46.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd4b5e2ee4e60425711ec65c33add4e7a626adef79d66f62ba0acfd493af282d", size = 3714683, upload-time = "2025-09-17T00:10:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5a/1cd3ef86e5884edcbf8b27c3aa8f9544e9b9fcce5d3ed8b86959741f4f8e/cryptography-46.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:48948940d0ae00483e85e9154bb42997d0b77c21e43a77b7773c8c80de532ac5", size = 3443784, upload-time = "2025-09-17T00:10:18.014Z" }, - { url = "https://files.pythonhosted.org/packages/27/27/077e09fd92075dd1338ea0ffaf5cfee641535545925768350ad90d8c36ca/cryptography-46.0.1-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b9c79af2c3058430d911ff1a5b2b96bbfe8da47d5ed961639ce4681886614e70", size = 3722319, upload-time = "2025-09-17T00:10:20.273Z" }, - { url = "https://files.pythonhosted.org/packages/db/32/6fc7250280920418651640d76cee34d91c1e0601d73acd44364570cf041f/cryptography-46.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0ca4be2af48c24df689a150d9cd37404f689e2968e247b6b8ff09bff5bcd786f", size = 4249030, upload-time = "2025-09-17T00:10:22.396Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/8d5398b2da15a15110b2478480ab512609f95b45ead3a105c9a9c76f9980/cryptography-46.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:13e67c4d3fb8b6bc4ef778a7ccdd8df4cd15b4bcc18f4239c8440891a11245cc", size = 4528009, upload-time = "2025-09-17T00:10:24.418Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1c/4012edad2a8977ab386c36b6e21f5065974d37afa3eade83a9968cba4855/cryptography-46.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:15b5fd9358803b0d1cc42505a18d8bca81dabb35b5cfbfea1505092e13a9d96d", size = 4248902, upload-time = "2025-09-17T00:10:26.255Z" }, - { url = "https://files.pythonhosted.org/packages/58/a3/257cd5ae677302de8fa066fca9de37128f6729d1e63c04dd6a15555dd450/cryptography-46.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e34da95e29daf8a71cb2841fd55df0511539a6cdf33e6f77c1e95e44006b9b46", size = 4527150, upload-time = "2025-09-17T00:10:28.28Z" }, - { url = "https://files.pythonhosted.org/packages/6a/cd/fe6b65e1117ec7631f6be8951d3db076bac3e1b096e3e12710ed071ffc3c/cryptography-46.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:34f04b7311174469ab3ac2647469743720f8b6c8b046f238e5cb27905695eb2a", size = 3448210, upload-time = "2025-09-17T00:10:30.145Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, ] [[package]] @@ -715,11 +800,27 @@ wheels = [ name = "dnspython" version = "2.7.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, ] +[[package]] +name = "dnspython" +version = "2.8.0" +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/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -731,14 +832,14 @@ wheels = [ [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] @@ -761,115 +862,196 @@ wheels = [ [[package]] name = "gevent" -version = "25.5.1" +version = "25.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, - { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, - { name = "zope-event" }, - { name = "zope-interface" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/58/267e8160aea00ab00acd2de97197eecfe307064a376fb5c892870a8a6159/gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61", size = 6388207, upload-time = "2025-05-12T12:57:59.833Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/a7/438568c37fb255f80e710318bfcad04731b92ce764bc16adee278fdc6b4d/gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9", size = 2922800, upload-time = "2025-05-12T11:11:46.728Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b3/b44d8b1c4a4d01097a7f82ffbc582d054007365c27b28867f0b2d4241d73/gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52", size = 1812954, upload-time = "2025-05-12T11:52:27.059Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c6/935b4c973ad827c9ec49c354d68d047da1d23e3018bda63d3723cce43178/gevent-25.5.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ab729d50ae85077a68e0385f129f5b01052d01a0ae6d7fdc1824f5337905e4", size = 1900169, upload-time = "2025-05-12T11:54:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/38/8a/b745bddfec35fb723cafb036f191e5e0a0013f1698bf0ba4fa2cb8e01879/gevent-25.5.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80d20592aeabcc4e294fd441fd43d45cb537437fd642c374ea9d964622fad229", size = 1849786, upload-time = "2025-05-12T12:00:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b3/7aa7b09d91207bebe7608699558bbadd34f63e32904351867c29f8be25de/gevent-25.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8ba0257542ccbb72a8229dc34d00844ccdfba110417e4b7b34599548d0e20e9", size = 2139021, upload-time = "2025-05-12T11:32:58.961Z" }, - { url = "https://files.pythonhosted.org/packages/74/da/cf52ae0c84361f4164a04f3338508b1234331ce79719db103e50dbc5598c/gevent-25.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cad0821dff998c7c60dd238f92cd61380342c47fb9e92e1a8705d9b5ac7c16e8", size = 1830758, upload-time = "2025-05-12T11:59:55.666Z" }, - { url = "https://files.pythonhosted.org/packages/93/93/73a49b896d78eec27f0895ce3008f9825db748a5aacbca47404d1014da4b/gevent-25.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:017a7384c0cd1a5907751c991535a0699596e89725468a7fc39228312e10efa1", size = 2199993, upload-time = "2025-05-12T11:40:50.845Z" }, - { url = "https://files.pythonhosted.org/packages/df/c7/34680b7d2a75492fa032fa8ecaacc03c1940767a35125f6740954a0132a3/gevent-25.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:469c86d02fccad7e2a3d82fe22237e47ecb376fbf4710bc18747b49c50716817", size = 1652665, upload-time = "2025-05-12T12:35:58.105Z" }, - { url = "https://files.pythonhosted.org/packages/c6/eb/015e93f16a718e2f836ecebecae9bcd7b4d2a5695d1c8bd5bba2d5d91548/gevent-25.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:12380aba5c316e9ff53cc21d8ab80f4a91c0df3ada58f65d4f5eb2cf693db00e", size = 2877441, upload-time = "2025-05-12T11:14:57.735Z" }, - { url = "https://files.pythonhosted.org/packages/7b/86/42d191a6f6672ca59d6d79b4cd9b89d4a15f59c843fbbad42f2b749f8ea9/gevent-25.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f0694daab1a041b69a53f53c2141c12994892b2503870515cabe6a5dbd2a928", size = 1774873, upload-time = "2025-05-12T11:52:29.015Z" }, - { url = "https://files.pythonhosted.org/packages/f5/9f/42dd255849c9ca2e814f5cbe180980594007ba19044a132cf674069e38bf/gevent-25.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2797885e9aeffdc98e1846723e5aa212e7ce53007dbef40d6fd2add264235c41", size = 1857911, upload-time = "2025-05-12T11:54:19.523Z" }, - { url = "https://files.pythonhosted.org/packages/3e/fc/8e799a733be48f6114bfc531b94e28812741664d8af89872dd90e117f8a4/gevent-25.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde6aaac36b54332e10ea2a5bc0de6a8aba6c205c92603fe4396e3777c88e05d", size = 1812751, upload-time = "2025-05-12T12:00:03.719Z" }, - { url = "https://files.pythonhosted.org/packages/52/4f/a3f3acd961887da10cb0b49c3d915201973d59ce6bf49e2922eaf2058d5f/gevent-25.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24484f80f14befb8822bf29554cfb3a26a26cb69cd1e5a8be9e23b4bd7a96e25", size = 2087115, upload-time = "2025-05-12T11:33:01.128Z" }, - { url = "https://files.pythonhosted.org/packages/b6/27/bb38e005106a53787c13ad1f9f73ed990e403e462108acae6320ab11d442/gevent-25.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc7446895fa184890d8ca5ea61e502691114f9db55c9b76adc33f3086c4368", size = 1793549, upload-time = "2025-05-12T11:59:57.854Z" }, - { url = "https://files.pythonhosted.org/packages/ee/56/da817bc69e1f0ae8438f12f2cd150656b09a8c3576c6d12f992dc9ca64ef/gevent-25.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b6106e2414b1797133786258fa1962a5e836480e4d5e861577f9fc63b673a5a", size = 2145899, upload-time = "2025-05-12T11:40:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/b8/42/989403abbdbb1346a1507083c02018bee3fedaef3f9648940c767d8c0958/gevent-25.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:bc899212d90f311784c58938a9c09c59802fb6dc287a35fabdc36d180f57f575", size = 1635771, upload-time = "2025-05-12T12:26:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/58/c5/cf71423666a0b83db3d7e3f85788bc47d573fca5fe62b798fe2c4273de7c/gevent-25.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f", size = 2909333, upload-time = "2025-05-12T11:11:34.883Z" }, - { url = "https://files.pythonhosted.org/packages/26/7e/d2f174ee8bec6eb85d961ca203bc599d059c857b8412e367b8fa206603a5/gevent-25.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649", size = 1788420, upload-time = "2025-05-12T11:52:30.306Z" }, - { url = "https://files.pythonhosted.org/packages/fe/f3/3aba8c147b9108e62ba348c726fe38ae69735a233db425565227336e8ce6/gevent-25.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519", size = 1868854, upload-time = "2025-05-12T11:54:21.564Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b1/11a5453f8fcebe90a456471fad48bd154c6a62fcb96e3475a5e408d05fc8/gevent-25.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec", size = 1833946, upload-time = "2025-05-12T12:00:05.514Z" }, - { url = "https://files.pythonhosted.org/packages/70/1c/37d4a62303f86e6af67660a8df38c1171b7290df61b358e618c6fea79567/gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837", size = 2070583, upload-time = "2025-05-12T11:33:02.803Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8f/3b14929ff28263aba1d268ea97bcf104be1a86ba6f6bb4633838e7a1905e/gevent-25.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310", size = 1808341, upload-time = "2025-05-12T11:59:59.154Z" }, - { url = "https://files.pythonhosted.org/packages/2f/fc/674ec819fb8a96e482e4d21f8baa43d34602dba09dfce7bbdc8700899d1b/gevent-25.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c", size = 2137974, upload-time = "2025-05-12T11:40:54.78Z" }, - { url = "https://files.pythonhosted.org/packages/05/9a/048b7f5e28c54e4595ad4a8ad3c338fa89560e558db2bbe8273f44f030de/gevent-25.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806", size = 1638344, upload-time = "2025-05-12T12:08:31.776Z" }, - { url = "https://files.pythonhosted.org/packages/10/25/2162b38d7b48e08865db6772d632bd1648136ce2bb50e340565e45607cad/gevent-25.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc", size = 2928044, upload-time = "2025-05-12T11:11:36.33Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e0/dbd597a964ed00176da122ea759bf2a6c1504f1e9f08e185379f92dc355f/gevent-25.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922", size = 1788751, upload-time = "2025-05-12T11:52:32.643Z" }, - { url = "https://files.pythonhosted.org/packages/f1/74/960cc4cf4c9c90eafbe0efc238cdf588862e8e278d0b8c0d15a0da4ed480/gevent-25.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d", size = 1869766, upload-time = "2025-05-12T11:54:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/56/78/fa84b1c7db79b156929685db09a7c18c3127361dca18a09e998e98118506/gevent-25.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d", size = 1835358, upload-time = "2025-05-12T12:00:06.794Z" }, - { url = "https://files.pythonhosted.org/packages/00/5c/bfefe3822bbca5b83bfad256c82251b3f5be13d52d14e17a786847b9b625/gevent-25.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e", size = 2073071, upload-time = "2025-05-12T11:33:04.2Z" }, - { url = "https://files.pythonhosted.org/packages/20/e4/08a77a3839a37db96393dea952e992d5846a881b887986dde62ead6b48a1/gevent-25.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce", size = 1809805, upload-time = "2025-05-12T12:00:00.537Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ac/28848348f790c1283df74b0fc0a554271d0606676470f848eccf84eae42a/gevent-25.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276", size = 2138305, upload-time = "2025-05-12T11:40:56.566Z" }, - { url = "https://files.pythonhosted.org/packages/52/9e/0e9e40facd2d714bfb00f71fc6dacaacc82c24c1c2e097bf6461e00dec9f/gevent-25.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896", size = 1637444, upload-time = "2025-05-12T12:17:45.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/16/b71171e97ec7b4ded8669542f4369d88d5a289e2704efbbde51e858e062a/gevent-25.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0bacf89a65489d26c7087669af89938d5bfd9f7afb12a07b57855b9fad6ccbd0", size = 2937113, upload-time = "2025-05-12T11:12:03.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/54/e5908beb092c2745aa8390f15b9559cc3ebd77bf1ba71c81c606f7b1fb92/gevent-25.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30169ef9cc0a57930bfd8fe14d86bc9d39fb96d278e3891e85cbe7b46058a97", size = 2147450, upload-time = "2025-05-12T11:33:05.883Z" }, - { url = "https://files.pythonhosted.org/packages/ee/39/206c9da2395a7df11c13e2989f7c7c65a7799babdb8b4b055cccae4d5c14/gevent-25.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e72ad5f8d9c92df017fb91a1f6a438cfb63b0eff4b40904ff81b40cb8150078c", size = 2210122, upload-time = "2025-05-12T11:40:58.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/18/d10ca3841b686143c1973cac816651a72ff77ad9e79a5300cbbbe310fced/gevent-25.5.1-cp39-cp39-win32.whl", hash = "sha256:e5f358e81e27b1a7f2fb2f5219794e13ab5f59ce05571aa3877cfac63adb97db", size = 1548447, upload-time = "2025-05-12T12:48:21.565Z" }, - { url = "https://files.pythonhosted.org/packages/ac/9d/48c01ff8324ce4bfaba0760c0f1db6f4e2c976838655f6b80333cfd47999/gevent-25.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b83aff2441c7d4ee93e519989713b7c2607d4510abe990cd1d04f641bc6c03af", size = 1659832, upload-time = "2025-05-12T12:45:00.794Z" }, - { url = "https://files.pythonhosted.org/packages/11/81/834da3c1ea5e71e4dc1a78a034a15f2813d9760d135464aae5d1f058a8c6/gevent-25.5.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:60ad4ca9ca2c4cc8201b607c229cd17af749831e371d006d8a91303bb5568eb1", size = 1291540, upload-time = "2025-05-12T11:11:55.456Z" }, + { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and platform_python_implementation == 'CPython'" }, + { name = "greenlet", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and platform_python_implementation == 'CPython'" }, + { name = "zope-event", version = "6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "zope-event", version = "6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "zope-interface", version = "8.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "zope-interface", version = "8.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/48/b3ef2673ffb940f980966694e40d6d32560f3ffa284ecaeb5ea3a90a6d3f/gevent-25.9.1.tar.gz", hash = "sha256:adf9cd552de44a4e6754c51ff2e78d9193b7fa6eab123db9578a210e657235dd", size = 5059025, upload-time = "2025-09-17T16:15:34.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/c7/2c60fc4e5c9144f2b91e23af8d87c626870ad3183cfd09d2b3ba6d699178/gevent-25.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:856b990be5590e44c3a3dc6c8d48a40eaccbb42e99d2b791d11d1e7711a4297e", size = 1831980, upload-time = "2025-09-17T15:41:22.597Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ae/49bf0a01f95a1c92c001d7b3f482a2301626b8a0617f448c4cd14ca9b5d4/gevent-25.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fe1599d0b30e6093eb3213551751b24feeb43db79f07e89d98dd2f3330c9063e", size = 1918777, upload-time = "2025-09-17T15:48:57.223Z" }, + { url = "https://files.pythonhosted.org/packages/88/3f/266d2eb9f5d75c184a55a39e886b53a4ea7f42ff31f195220a363f0e3f9e/gevent-25.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:f0d8b64057b4bf1529b9ef9bd2259495747fba93d1f836c77bfeaacfec373fd0", size = 1869235, upload-time = "2025-09-17T15:49:18.255Z" }, + { url = "https://files.pythonhosted.org/packages/76/24/c0c7c7db70ca74c7b1918388ebda7c8c2a3c3bff0bbfbaa9280ed04b3340/gevent-25.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b56cbc820e3136ba52cd690bdf77e47a4c239964d5f80dc657c1068e0fe9521c", size = 2177334, upload-time = "2025-09-17T15:15:10.073Z" }, + { url = "https://files.pythonhosted.org/packages/4c/1e/de96bd033c03955f54c455b51a5127b1d540afcfc97838d1801fafce6d2e/gevent-25.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5fa9ce5122c085983e33e0dc058f81f5264cebe746de5c401654ab96dddfca8", size = 1847708, upload-time = "2025-09-17T15:52:38.475Z" }, + { url = "https://files.pythonhosted.org/packages/26/8b/6851e9cd3e4f322fa15c1d196cbf1a8a123da69788b078227dd13dd4208f/gevent-25.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:03c74fec58eda4b4edc043311fca8ba4f8744ad1632eb0a41d5ec25413581975", size = 2234274, upload-time = "2025-09-17T15:24:07.797Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d8/b1178b70538c91493bec283018b47c16eab4bac9ddf5a3d4b7dd905dab60/gevent-25.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a8ae9f895e8651d10b0a8328a61c9c53da11ea51b666388aa99b0ce90f9fdc27", size = 1695326, upload-time = "2025-09-17T20:10:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/81/86/03f8db0704fed41b0fa830425845f1eb4e20c92efa3f18751ee17809e9c6/gevent-25.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5aff9e8342dc954adb9c9c524db56c2f3557999463445ba3d9cbe3dada7b7", size = 1792418, upload-time = "2025-09-17T15:41:24.384Z" }, + { url = "https://files.pythonhosted.org/packages/5f/35/f6b3a31f0849a62cfa2c64574bcc68a781d5499c3195e296e892a121a3cf/gevent-25.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1cdf6db28f050ee103441caa8b0448ace545364f775059d5e2de089da975c457", size = 1875700, upload-time = "2025-09-17T15:48:59.652Z" }, + { url = "https://files.pythonhosted.org/packages/66/1e/75055950aa9b48f553e061afa9e3728061b5ccecca358cef19166e4ab74a/gevent-25.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:812debe235a8295be3b2a63b136c2474241fa5c58af55e6a0f8cfc29d4936235", size = 1831365, upload-time = "2025-09-17T15:49:19.426Z" }, + { url = "https://files.pythonhosted.org/packages/31/e8/5c1f6968e5547e501cfa03dcb0239dff55e44c3660a37ec534e32a0c008f/gevent-25.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b28b61ff9216a3d73fe8f35669eefcafa957f143ac534faf77e8a19eb9e6883a", size = 2122087, upload-time = "2025-09-17T15:15:12.329Z" }, + { url = "https://files.pythonhosted.org/packages/c0/2c/ebc5d38a7542af9fb7657bfe10932a558bb98c8a94e4748e827d3823fced/gevent-25.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5e4b6278b37373306fc6b1e5f0f1cf56339a1377f67c35972775143d8d7776ff", size = 1808776, upload-time = "2025-09-17T15:52:40.16Z" }, + { url = "https://files.pythonhosted.org/packages/e6/26/e1d7d6c8ffbf76fe1fbb4e77bdb7f47d419206adc391ec40a8ace6ebbbf0/gevent-25.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d99f0cb2ce43c2e8305bf75bee61a8bde06619d21b9d0316ea190fc7a0620a56", size = 2179141, upload-time = "2025-09-17T15:24:09.895Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6c/bb21fd9c095506aeeaa616579a356aa50935165cc0f1e250e1e0575620a7/gevent-25.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:72152517ecf548e2f838c61b4be76637d99279dbaa7e01b3924df040aa996586", size = 1677941, upload-time = "2025-09-17T19:59:50.185Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/e55930ba5259629eb28ac7ee1abbca971996a9165f902f0249b561602f24/gevent-25.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:46b188248c84ffdec18a686fcac5dbb32365d76912e14fda350db5dc0bfd4f86", size = 2955991, upload-time = "2025-09-17T14:52:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/aa/88/63dc9e903980e1da1e16541ec5c70f2b224ec0a8e34088cb42794f1c7f52/gevent-25.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f2b54ea3ca6f0c763281cd3f96010ac7e98c2e267feb1221b5a26e2ca0b9a692", size = 1808503, upload-time = "2025-09-17T15:41:25.59Z" }, + { url = "https://files.pythonhosted.org/packages/7a/8d/7236c3a8f6ef7e94c22e658397009596fa90f24c7d19da11ad7ab3a9248e/gevent-25.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7a834804ac00ed8a92a69d3826342c677be651b1c3cd66cc35df8bc711057aa2", size = 1890001, upload-time = "2025-09-17T15:49:01.227Z" }, + { url = "https://files.pythonhosted.org/packages/4f/63/0d7f38c4a2085ecce26b50492fc6161aa67250d381e26d6a7322c309b00f/gevent-25.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:323a27192ec4da6b22a9e51c3d9d896ff20bc53fdc9e45e56eaab76d1c39dd74", size = 1855335, upload-time = "2025-09-17T15:49:20.582Z" }, + { url = "https://files.pythonhosted.org/packages/95/18/da5211dfc54c7a57e7432fd9a6ffeae1ce36fe5a313fa782b1c96529ea3d/gevent-25.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ea78b39a2c51d47ff0f130f4c755a9a4bbb2dd9721149420ad4712743911a51", size = 2109046, upload-time = "2025-09-17T15:15:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5a/7bb5ec8e43a2c6444853c4a9f955f3e72f479d7c24ea86c95fb264a2de65/gevent-25.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dc45cd3e1cc07514a419960af932a62eb8515552ed004e56755e4bf20bad30c5", size = 1827099, upload-time = "2025-09-17T15:52:41.384Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d4/b63a0a60635470d7d986ef19897e893c15326dd69e8fb342c76a4f07fe9e/gevent-25.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34e01e50c71eaf67e92c186ee0196a039d6e4f4b35670396baed4a2d8f1b347f", size = 2172623, upload-time = "2025-09-17T15:24:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/d5/98/caf06d5d22a7c129c1fb2fc1477306902a2c8ddfd399cd26bbbd4caf2141/gevent-25.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acd6bcd5feabf22c7c5174bd3b9535ee9f088d2bbce789f740ad8d6554b18f3", size = 1682837, upload-time = "2025-09-17T19:48:47.318Z" }, + { url = "https://files.pythonhosted.org/packages/5a/77/b97f086388f87f8ad3e01364f845004aef0123d4430241c7c9b1f9bde742/gevent-25.9.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:4f84591d13845ee31c13f44bdf6bd6c3dbf385b5af98b2f25ec328213775f2ed", size = 2973739, upload-time = "2025-09-17T14:53:30.279Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/9d5f204ead343e5b27bbb2fedaec7cd0009d50696b2266f590ae845d0331/gevent-25.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9cdbb24c276a2d0110ad5c978e49daf620b153719ac8a548ce1250a7eb1b9245", size = 1809165, upload-time = "2025-09-17T15:41:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/10/3e/791d1bf1eb47748606d5f2c2aa66571f474d63e0176228b1f1fd7b77ab37/gevent-25.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:88b6c07169468af631dcf0fdd3658f9246d6822cc51461d43f7c44f28b0abb82", size = 1890638, upload-time = "2025-09-17T15:49:02.45Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5c/9ad0229b2b4d81249ca41e4f91dd8057deaa0da6d4fbe40bf13cdc5f7a47/gevent-25.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b7bb0e29a7b3e6ca9bed2394aa820244069982c36dc30b70eb1004dd67851a48", size = 1857118, upload-time = "2025-09-17T15:49:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/49/2a/3010ed6c44179a3a5c5c152e6de43a30ff8bc2c8de3115ad8733533a018f/gevent-25.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2951bb070c0ee37b632ac9134e4fdaad70d2e660c931bb792983a0837fe5b7d7", size = 2111598, upload-time = "2025-09-17T15:15:15.226Z" }, + { url = "https://files.pythonhosted.org/packages/08/75/6bbe57c19a7aa4527cc0f9afcdf5a5f2aed2603b08aadbccb5bf7f607ff4/gevent-25.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4e17c2d57e9a42e25f2a73d297b22b60b2470a74be5a515b36c984e1a246d47", size = 1829059, upload-time = "2025-09-17T15:52:42.596Z" }, + { url = "https://files.pythonhosted.org/packages/06/6e/19a9bee9092be45679cb69e4dd2e0bf5f897b7140b4b39c57cc123d24829/gevent-25.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d94936f8f8b23d9de2251798fcb603b84f083fdf0d7f427183c1828fb64f117", size = 2173529, upload-time = "2025-09-17T15:24:13.897Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4f/50de9afd879440e25737e63f5ba6ee764b75a3abe17376496ab57f432546/gevent-25.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb51c5f9537b07da673258b4832f6635014fee31690c3f0944d34741b69f92fa", size = 1681518, upload-time = "2025-09-17T19:39:47.488Z" }, + { url = "https://files.pythonhosted.org/packages/15/1a/948f8167b2cdce573cf01cec07afc64d0456dc134b07900b26ac7018b37e/gevent-25.9.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1a3fe4ea1c312dbf6b375b416925036fe79a40054e6bf6248ee46526ea628be1", size = 2982934, upload-time = "2025-09-17T14:54:11.302Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ec/726b146d1d3aad82e03d2e1e1507048ab6072f906e83f97f40667866e582/gevent-25.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0adb937f13e5fb90cca2edf66d8d7e99d62a299687400ce2edee3f3504009356", size = 1813982, upload-time = "2025-09-17T15:41:28.506Z" }, + { url = "https://files.pythonhosted.org/packages/35/5d/5f83f17162301662bd1ce702f8a736a8a8cac7b7a35e1d8b9866938d1f9d/gevent-25.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:427f869a2050a4202d93cf7fd6ab5cffb06d3e9113c10c967b6e2a0d45237cb8", size = 1894902, upload-time = "2025-09-17T15:49:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/83/cd/cf5e74e353f60dab357829069ffc300a7bb414c761f52cf8c0c6e9728b8d/gevent-25.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c049880175e8c93124188f9d926af0a62826a3b81aa6d3074928345f8238279e", size = 1861792, upload-time = "2025-09-17T15:49:23.279Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/b9a4526d4a4edce26fe4b3b993914ec9dc64baabad625a3101e51adb17f3/gevent-25.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b5a67a0974ad9f24721034d1e008856111e0535f1541499f72a733a73d658d1c", size = 2113215, upload-time = "2025-09-17T15:15:16.34Z" }, + { url = "https://files.pythonhosted.org/packages/e5/be/7d35731dfaf8370795b606e515d964a0967e129db76ea7873f552045dd39/gevent-25.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d0f5d8d73f97e24ea8d24d8be0f51e0cf7c54b8021c1fddb580bf239474690f", size = 1833449, upload-time = "2025-09-17T15:52:43.75Z" }, + { url = "https://files.pythonhosted.org/packages/65/58/7bc52544ea5e63af88c4a26c90776feb42551b7555a1c89c20069c168a3f/gevent-25.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddd3ff26e5c4240d3fbf5516c2d9d5f2a998ef87cfb73e1429cfaeaaec860fa6", size = 2176034, upload-time = "2025-09-17T15:24:15.676Z" }, + { url = "https://files.pythonhosted.org/packages/c2/69/a7c4ba2ffbc7c7dbf6d8b4f5d0f0a421f7815d229f4909854266c445a3d4/gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7", size = 1703019, upload-time = "2025-09-17T19:30:55.272Z" }, + { url = "https://files.pythonhosted.org/packages/60/45/8b6b68ac1d4ef97ae62058aeaf53be75290bc2d2fb940b2a73dfbf8c3260/gevent-25.9.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f18f80aef6b1f6907219affe15b36677904f7cfeed1f6a6bc198616e507ae2d7", size = 2181754, upload-time = "2025-09-17T15:15:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/07/c0/a7b1f25c42db3c5dc787ea7e23b87ec9a534ca757a25906849b7e7cc765e/gevent-25.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b274a53e818124a281540ebb4e7a2c524778f745b7a99b01bdecf0ca3ac0ddb0", size = 2239168, upload-time = "2025-09-17T15:24:17.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/8acba57c18ab5aacd1a88a82590c7ca43292b710a2265b75ad7056c5225b/gevent-25.9.1-cp39-cp39-win32.whl", hash = "sha256:c6c91f7e33c7f01237755884316110ee7ea076f5bdb9aa0982b6dc63243c0a38", size = 1570442, upload-time = "2025-09-17T20:18:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/f3/06/7e576ec64711778fa6fd40a5ee7c5311052bf2dee107ea31c6a5b63c1e2f/gevent-25.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:012a44b0121f3d7c800740ff80351c897e85e76a7e4764690f35c5ad9ec17de5", size = 1698985, upload-time = "2025-09-17T20:14:00.918Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" }, + { url = "https://files.pythonhosted.org/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" }, + { url = "https://files.pythonhosted.org/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" }, + { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, + { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, + { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, + { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c0/93885c4106d2626bf51fdec377d6aef740dfa5c4877461889a7cf8e565cc/greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", size = 269859, upload-time = "2025-08-07T13:16:16.003Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f5/33f05dc3ba10a02dedb1485870cf81c109227d3d3aa280f0e48486cac248/greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", size = 627610, upload-time = "2025-08-07T13:43:01.345Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/9476decef51a0844195f99ed5dc611d212e9b3515512ecdf7321543a7225/greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58", size = 639417, upload-time = "2025-08-07T13:45:32.094Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e0/849b9159cbb176f8c0af5caaff1faffdece7a8417fcc6fe1869770e33e21/greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", size = 634751, upload-time = "2025-08-07T13:53:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d3/844e714a9bbd39034144dca8b658dcd01839b72bb0ec7d8014e33e3705f0/greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433", size = 634020, upload-time = "2025-08-07T13:18:36.841Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4c/f3de2a8de0e840ecb0253ad0dc7e2bb3747348e798ec7e397d783a3cb380/greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", size = 582817, upload-time = "2025-08-07T13:18:35.48Z" }, + { url = "https://files.pythonhosted.org/packages/89/80/7332915adc766035c8980b161c2e5d50b2f941f453af232c164cff5e0aeb/greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", size = 1111985, upload-time = "2025-08-07T13:42:42.425Z" }, + { url = "https://files.pythonhosted.org/packages/66/71/1928e2c80197353bcb9b50aa19c4d8e26ee6d7a900c564907665cf4b9a41/greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", size = 1136137, upload-time = "2025-08-07T13:18:26.168Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/7bd33643e48ed45dcc0e22572f650767832bd4e1287f97434943cc402148/greenlet-3.2.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10", size = 1542941, upload-time = "2025-11-04T12:42:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/9b/74/4bc433f91d0d09a1c22954a371f9df928cb85e72640870158853a83415e5/greenlet-3.2.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be", size = 1609685, upload-time = "2025-11-04T12:42:29.242Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/a5dc74dde38aeb2b15d418cec76ed50e1dd3d620ccda84d8199703248968/greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", size = 281400, upload-time = "2025-08-07T14:02:20.263Z" }, + { url = "https://files.pythonhosted.org/packages/e5/44/342c4591db50db1076b8bda86ed0ad59240e3e1da17806a4cf10a6d0e447/greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", size = 298533, upload-time = "2025-08-07T13:56:34.168Z" }, ] [[package]] name = "greenlet" -version = "3.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/db/b4c12cff13ebac2786f4f217f06588bccd8b53d260453404ef22b121fc3a/greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be", size = 268977, upload-time = "2025-06-05T16:10:24.001Z" }, - { url = "https://files.pythonhosted.org/packages/52/61/75b4abd8147f13f70986df2801bf93735c1bd87ea780d70e3b3ecda8c165/greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac", size = 627351, upload-time = "2025-06-05T16:38:50.685Z" }, - { url = "https://files.pythonhosted.org/packages/35/aa/6894ae299d059d26254779a5088632874b80ee8cf89a88bca00b0709d22f/greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392", size = 638599, upload-time = "2025-06-05T16:41:34.057Z" }, - { url = "https://files.pythonhosted.org/packages/30/64/e01a8261d13c47f3c082519a5e9dbf9e143cc0498ed20c911d04e54d526c/greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c", size = 634482, upload-time = "2025-06-05T16:48:16.26Z" }, - { url = "https://files.pythonhosted.org/packages/47/48/ff9ca8ba9772d083a4f5221f7b4f0ebe8978131a9ae0909cf202f94cd879/greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db", size = 633284, upload-time = "2025-06-05T16:13:01.599Z" }, - { url = "https://files.pythonhosted.org/packages/e9/45/626e974948713bc15775b696adb3eb0bd708bec267d6d2d5c47bb47a6119/greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b", size = 582206, upload-time = "2025-06-05T16:12:48.51Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8e/8b6f42c67d5df7db35b8c55c9a850ea045219741bb14416255616808c690/greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712", size = 1111412, upload-time = "2025-06-05T16:36:45.479Z" }, - { url = "https://files.pythonhosted.org/packages/05/46/ab58828217349500a7ebb81159d52ca357da747ff1797c29c6023d79d798/greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00", size = 1135054, upload-time = "2025-06-05T16:12:36.478Z" }, - { url = "https://files.pythonhosted.org/packages/68/7f/d1b537be5080721c0f0089a8447d4ef72839039cdb743bdd8ffd23046e9a/greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302", size = 296573, upload-time = "2025-06-05T16:34:26.521Z" }, - { url = "https://files.pythonhosted.org/packages/fc/2e/d4fcb2978f826358b673f779f78fa8a32ee37df11920dc2bb5589cbeecef/greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", size = 270219, upload-time = "2025-06-05T16:10:10.414Z" }, - { url = "https://files.pythonhosted.org/packages/16/24/929f853e0202130e4fe163bc1d05a671ce8dcd604f790e14896adac43a52/greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", size = 630383, upload-time = "2025-06-05T16:38:51.785Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b2/0320715eb61ae70c25ceca2f1d5ae620477d246692d9cc284c13242ec31c/greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", size = 642422, upload-time = "2025-06-05T16:41:35.259Z" }, - { url = "https://files.pythonhosted.org/packages/bd/49/445fd1a210f4747fedf77615d941444349c6a3a4a1135bba9701337cd966/greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b", size = 638375, upload-time = "2025-06-05T16:48:18.235Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c8/ca19760cf6eae75fa8dc32b487e963d863b3ee04a7637da77b616703bc37/greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", size = 637627, upload-time = "2025-06-05T16:13:02.858Z" }, - { url = "https://files.pythonhosted.org/packages/65/89/77acf9e3da38e9bcfca881e43b02ed467c1dedc387021fc4d9bd9928afb8/greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", size = 585502, upload-time = "2025-06-05T16:12:49.642Z" }, - { url = "https://files.pythonhosted.org/packages/97/c6/ae244d7c95b23b7130136e07a9cc5aadd60d59b5951180dc7dc7e8edaba7/greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", size = 1114498, upload-time = "2025-06-05T16:36:46.598Z" }, - { url = "https://files.pythonhosted.org/packages/89/5f/b16dec0cbfd3070658e0d744487919740c6d45eb90946f6787689a7efbce/greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba", size = 1139977, upload-time = "2025-06-05T16:12:38.262Z" }, - { url = "https://files.pythonhosted.org/packages/66/77/d48fb441b5a71125bcac042fc5b1494c806ccb9a1432ecaa421e72157f77/greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34", size = 297017, upload-time = "2025-06-05T16:25:05.225Z" }, - { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" }, - { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701, upload-time = "2025-06-05T16:48:19.604Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" }, - { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055, upload-time = "2025-06-05T16:12:40.457Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817, upload-time = "2025-06-05T16:29:49.244Z" }, - { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, - { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, - { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, - { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, - { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, - { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, - { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" }, - { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" }, - { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" }, - { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" }, - { url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443, upload-time = "2025-06-05T16:48:23.113Z" }, - { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" }, - { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d9/a3114df5fba2bf9823e0acc01e9e2abdcd8ea4c5487cf1c3dcd4cc0b48cf/greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64", size = 267769, upload-time = "2025-06-05T16:10:44.802Z" }, - { url = "https://files.pythonhosted.org/packages/bc/da/47dfc50f6e5673116e66a737dc58d1eca651db9a9aa8797c1d27e940e211/greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7", size = 625472, upload-time = "2025-06-05T16:38:56.882Z" }, - { url = "https://files.pythonhosted.org/packages/f5/74/f6ef9f85d981b2fcd665bbee3e69e3c0a10fb962eb4c6a5889ac3b6debfa/greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805", size = 637253, upload-time = "2025-06-05T16:41:40.542Z" }, - { url = "https://files.pythonhosted.org/packages/66/69/4919bb1c9e43bfc16dc886e7a37fe1bc04bfa4101aba177936a10f313cad/greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72", size = 632611, upload-time = "2025-06-05T16:48:24.976Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/97d988d019f40b6b360b0c71c99e5b4c877a3d92666fe48b081d0e1ea1cd/greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904", size = 631843, upload-time = "2025-06-05T16:13:09.476Z" }, - { url = "https://files.pythonhosted.org/packages/59/24/d5e1504ec00768755d4ccc2168b76d9f4524e96694a14ad45bd87796e9bb/greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26", size = 580781, upload-time = "2025-06-05T16:12:55.029Z" }, - { url = "https://files.pythonhosted.org/packages/9c/df/d009bcca566dbfd2283b306b4e424f4c0e59bf984868f8b789802fe9e607/greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da", size = 1109903, upload-time = "2025-06-05T16:36:51.491Z" }, - { url = "https://files.pythonhosted.org/packages/33/54/5036097197a78388aa6901a5b90b562f3a154a9fbee89c301a26f56f3942/greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4", size = 1133975, upload-time = "2025-06-05T16:12:43.866Z" }, - { url = "https://files.pythonhosted.org/packages/e2/15/b001456a430805fdd8b600a788d19a790664eee8863739523395f68df752/greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57", size = 279320, upload-time = "2025-06-05T16:43:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4c/bf2100cbc1bd07f39bee3b09e7eef39beffe29f5453dc2477a2693737913/greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322", size = 296444, upload-time = "2025-06-05T16:39:22.664Z" }, +version = "3.3.0" +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/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/6a/33d1702184d94106d3cdd7bfb788e19723206fce152e303473ca3b946c7b/greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d", size = 273658, upload-time = "2025-12-04T14:23:37.494Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b7/2b5805bbf1907c26e434f4e448cd8b696a0b71725204fa21a211ff0c04a7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb", size = 574810, upload-time = "2025-12-04T14:50:04.154Z" }, + { url = "https://files.pythonhosted.org/packages/94/38/343242ec12eddf3d8458c73f555c084359883d4ddc674240d9e61ec51fd6/greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd", size = 586248, upload-time = "2025-12-04T14:57:39.35Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/0ae86792fb212e4384041e0ef8e7bc66f59a54912ce407d26a966ed2914d/greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b", size = 597403, upload-time = "2025-12-04T15:07:10.831Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a8/15d0aa26c0036a15d2659175af00954aaaa5d0d66ba538345bd88013b4d7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5", size = 586910, upload-time = "2025-12-04T14:25:59.705Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9b/68d5e3b7ccaba3907e5532cf8b9bf16f9ef5056a008f195a367db0ff32db/greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9", size = 1547206, upload-time = "2025-12-04T15:04:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/66/bd/e3086ccedc61e49f91e2cfb5ffad9d8d62e5dc85e512a6200f096875b60c/greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d", size = 1613359, upload-time = "2025-12-04T14:27:26.548Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/d4e73f5dfa888364bbf02efa85616c6714ae7c631c201349782e5b428925/greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082", size = 300740, upload-time = "2025-12-04T14:47:52.773Z" }, + { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, + { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, + { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" }, + { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, + { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, + { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, + { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, + { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, + { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, + { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" }, + { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" }, + { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, + { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, ] [[package]] @@ -911,11 +1093,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -932,7 +1114,7 @@ name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp", marker = "python_full_version != '3.14.*'" }, + { name = "zipp" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ @@ -943,11 +1125,27 @@ wheels = [ name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +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/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -971,153 +1169,181 @@ wheels = [ [[package]] name = "librt" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/66/79a14e672256ef58144a24eb49adb338ec02de67ff4b45320af6504682ab/librt-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2682162855a708e3270eba4b92026b93f8257c3e65278b456c77631faf0f4f7a", size = 54707, upload-time = "2025-12-06T19:03:10.881Z" }, - { url = "https://files.pythonhosted.org/packages/58/fa/b709c65a9d5eab85f7bcfe0414504d9775aaad6e78727a0327e175474caa/librt-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:440c788f707c061d237c1e83edf6164ff19f5c0f823a3bf054e88804ebf971ec", size = 56670, upload-time = "2025-12-06T19:03:12.107Z" }, - { url = "https://files.pythonhosted.org/packages/3a/56/0685a0772ec89ddad4c00e6b584603274c3d818f9a68e2c43c4eb7b39ee9/librt-0.7.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399938edbd3d78339f797d685142dd8a623dfaded023cf451033c85955e4838a", size = 161045, upload-time = "2025-12-06T19:03:13.444Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d9/863ada0c5ce48aefb89df1555e392b2209fcb6daee4c153c031339b9a89b/librt-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1975eda520957c6e0eb52d12968dd3609ffb7eef05d4223d097893d6daf1d8a7", size = 169532, upload-time = "2025-12-06T19:03:14.699Z" }, - { url = "https://files.pythonhosted.org/packages/68/a0/71da6c8724fd16c31749905ef1c9e11de206d9301b5be984bf2682b4efb3/librt-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9da128d0edf990cf0d2ca011b02cd6f639e79286774bd5b0351245cbb5a6e51", size = 183277, upload-time = "2025-12-06T19:03:16.446Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/9c97bf2f8338ba1914de233ea312bba2bbd7c59f43f807b3e119796bab18/librt-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19acfde38cb532a560b98f473adc741c941b7a9bc90f7294bc273d08becb58b", size = 179045, upload-time = "2025-12-06T19:03:17.838Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b1/ceea067f489e904cb4ddcca3c9b06ba20229bc3fa7458711e24a5811f162/librt-0.7.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b4f57f7a0c65821c5441d98c47ff7c01d359b1e12328219709bdd97fdd37f90", size = 173521, upload-time = "2025-12-06T19:03:19.17Z" }, - { url = "https://files.pythonhosted.org/packages/7a/41/6cb18f5da9c89ed087417abb0127a445a50ad4eaf1282ba5b52588187f47/librt-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:256793988bff98040de23c57cf36e1f4c2f2dc3dcd17537cdac031d3b681db71", size = 193592, upload-time = "2025-12-06T19:03:20.637Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3c/fcef208746584e7c78584b7aedc617130c4a4742cb8273361bbda8b183b5/librt-0.7.3-cp310-cp310-win32.whl", hash = "sha256:fcb72249ac4ea81a7baefcbff74df7029c3cb1cf01a711113fa052d563639c9c", size = 47201, upload-time = "2025-12-06T19:03:21.764Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bf/d8a6c35d1b2b789a4df9b3ddb1c8f535ea373fde2089698965a8f0d62138/librt-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4887c29cadbdc50640179e3861c276325ff2986791e6044f73136e6e798ff806", size = 54371, upload-time = "2025-12-06T19:03:23.231Z" }, - { url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" }, - { url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" }, - { url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" }, - { url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" }, - { url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" }, - { url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" }, - { url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" }, - { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, - { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, - { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, - { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, - { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, - { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, - { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, - { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, - { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" }, - { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" }, - { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" }, - { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" }, - { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" }, - { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" }, - { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" }, - { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" }, - { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" }, - { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" }, - { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" }, - { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" }, - { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" }, - { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" }, - { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" }, - { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" }, - { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" }, - { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" }, - { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" }, - { url = "https://files.pythonhosted.org/packages/e1/70/b3f19e3bb34f44e218c8271dc0b2b14eb6b183fbccbececf94c71e2b5e69/librt-0.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd8551aa21df6c60baa2624fd086ae7486bdde00c44097b32e1d1b1966e365e0", size = 54850, upload-time = "2025-12-06T19:04:32.742Z" }, - { url = "https://files.pythonhosted.org/packages/a0/97/6599ed7726aaa9b5bacea206d5861b94e76866240e2f394a59594bf3db46/librt-0.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6eb9295c730e26b849ed1f4022735f36863eb46b14b6e10604c1c39b8b5efaea", size = 56797, upload-time = "2025-12-06T19:04:34.193Z" }, - { url = "https://files.pythonhosted.org/packages/33/83/216db13224a6f688787f456909bbc50f9d951c0f4bea8ba38a2eb931d581/librt-0.7.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3edbf257c40d21a42615e9e332a6b10a8bacaaf58250aed8552a14a70efd0d65", size = 159681, upload-time = "2025-12-06T19:04:35.554Z" }, - { url = "https://files.pythonhosted.org/packages/83/23/0a490c8ba3bc90090647ac7b9b3c63c16af7378bcabe3ff4c7d7890d66e5/librt-0.7.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b29e97273bd6999e2bfe9fe3531b1f4f64effd28327bced048a33e49b99674a", size = 168505, upload-time = "2025-12-06T19:04:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/5e/16/b47c60805285caa06728d61d933fdd6db5b7321f375ce496cb7fdbeb1a44/librt-0.7.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e40520c37926166c24d0c2e0f3bc3a5f46646c34bdf7b4ea9747c297d6ee809", size = 182234, upload-time = "2025-12-06T19:04:37.889Z" }, - { url = "https://files.pythonhosted.org/packages/2d/2f/bef211d7f0d55fa2484d2c644b2cdae8c9c5eec050754b0516e6582ad452/librt-0.7.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6bdd9adfca615903578d2060ee8a6eb1c24eaf54919ff0ddc820118e5718931b", size = 178276, upload-time = "2025-12-06T19:04:39.408Z" }, - { url = "https://files.pythonhosted.org/packages/3d/dd/5a3e7762b086b62fabb31fd4deaaf3ba888cfdd3b8f2e3247f076c18a6ff/librt-0.7.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f57aca20e637750a2c18d979f7096e2c2033cc40cf7ed201494318de1182f135", size = 172602, upload-time = "2025-12-06T19:04:40.619Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d8/533d5bfd5b377eb03ed54101814b530fc1f9bbe0e79971c641a3f15bfb33/librt-0.7.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cad9971881e4fec00d96af7eaf4b63aa7a595696fc221808b0d3ce7ca9743258", size = 192741, upload-time = "2025-12-06T19:04:41.738Z" }, - { url = "https://files.pythonhosted.org/packages/9f/69/0b87ce8e95f65ebc864f390f1139b8fe9fac6fb64b797307447b1719610c/librt-0.7.3-cp39-cp39-win32.whl", hash = "sha256:170cdb8436188347af17bf9cccf3249ba581c933ed56d926497119d4cf730cec", size = 47154, upload-time = "2025-12-06T19:04:42.96Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1c/070dee0add2d6e742be4d8b965d5a37c24562b43e8ef7deba8ed5b5d3c0f/librt-0.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:b278a9248a4e3260fee3db7613772ca9ab6763a129d6d6f29555e2f9b168216d", size = 54339, upload-time = "2025-12-06T19:04:44.415Z" }, +version = "0.7.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/1e/3e61dff6c07a3b400fe907d3164b92b3b3023ef86eac1ee236869dc276f7/librt-0.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc300cb5a5a01947b1ee8099233156fdccd5001739e5f596ecfbc0dab07b5a3b", size = 54708, upload-time = "2025-12-15T16:51:03.752Z" }, + { url = "https://files.pythonhosted.org/packages/87/98/ab2428b0a80d0fd67decaeea84a5ec920e3dd4d95ecfd074c71f51bd7315/librt-0.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee8d3323d921e0f6919918a97f9b5445a7dfe647270b2629ec1008aa676c0bc0", size = 56656, upload-time = "2025-12-15T16:51:05.038Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/de1fad3a16e4fb5b6605bd6cbe6d0e5207cc8eca58993835749a1da0812b/librt-0.7.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:95cb80854a355b284c55f79674f6187cc9574df4dc362524e0cce98c89ee8331", size = 161024, upload-time = "2025-12-15T16:51:06.31Z" }, + { url = "https://files.pythonhosted.org/packages/88/00/ddfcdc1147dd7fb68321d7b064b12f0b9101d85f466a46006f86096fde8d/librt-0.7.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca1caedf8331d8ad6027f93b52d68ed8f8009f5c420c246a46fe9d3be06be0f", size = 169529, upload-time = "2025-12-15T16:51:07.907Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b3/915702c7077df2483b015030d1979404474f490fe9a071e9576f7b26fef6/librt-0.7.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a6f1236151e6fe1da289351b5b5bce49651c91554ecc7b70a947bced6fe212", size = 183270, upload-time = "2025-12-15T16:51:09.164Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/ab2f217e8ec509fca4ea9e2e5022b9f72c1a7b7195f5a5770d299df807ea/librt-0.7.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7766b57aeebaf3f1dac14fdd4a75c9a61f2ed56d8ebeefe4189db1cb9d2a3783", size = 179038, upload-time = "2025-12-15T16:51:10.538Z" }, + { url = "https://files.pythonhosted.org/packages/10/1c/d40851d187662cf50312ebbc0b277c7478dd78dbaaf5ee94056f1d7f2f83/librt-0.7.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1c4c89fb01157dd0a3bfe9e75cd6253b0a1678922befcd664eca0772a4c6c979", size = 173502, upload-time = "2025-12-15T16:51:11.888Z" }, + { url = "https://files.pythonhosted.org/packages/07/52/d5880835c772b22c38db18660420fa6901fd9e9a433b65f0ba9b0f4da764/librt-0.7.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7fa8beef580091c02b4fd26542de046b2abfe0aaefa02e8bcf68acb7618f2b3", size = 193570, upload-time = "2025-12-15T16:51:13.168Z" }, + { url = "https://files.pythonhosted.org/packages/f1/35/22d3c424b82f86ce019c0addadf001d459dfac8036aecc07fadc5c541053/librt-0.7.4-cp310-cp310-win32.whl", hash = "sha256:543c42fa242faae0466fe72d297976f3c710a357a219b1efde3a0539a68a6997", size = 42596, upload-time = "2025-12-15T16:51:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/e7c316ac5fe60ac1fdfe515198087205220803c4cf923ee63e1cb8380b17/librt-0.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:25cc40d8eb63f0a7ea4c8f49f524989b9df901969cb860a2bc0e4bad4b8cb8a8", size = 48972, upload-time = "2025-12-15T16:51:15.516Z" }, + { url = "https://files.pythonhosted.org/packages/84/64/44089b12d8b4714a7f0e2f33fb19285ba87702d4be0829f20b36ebeeee07/librt-0.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3485b9bb7dfa66167d5500ffdafdc35415b45f0da06c75eb7df131f3357b174a", size = 54709, upload-time = "2025-12-15T16:51:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/26/ef/6fa39fb5f37002f7d25e0da4f24d41b457582beea9369eeb7e9e73db5508/librt-0.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:188b4b1a770f7f95ea035d5bbb9d7367248fc9d12321deef78a269ebf46a5729", size = 56663, upload-time = "2025-12-15T16:51:17.856Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e4/cbaca170a13bee2469c90df9e47108610b4422c453aea1aec1779ac36c24/librt-0.7.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b668b1c840183e4e38ed5a99f62fac44c3a3eef16870f7f17cfdfb8b47550ed", size = 161703, upload-time = "2025-12-15T16:51:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/d0/32/0b2296f9cc7e693ab0d0835e355863512e5eac90450c412777bd699c76ae/librt-0.7.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e8f864b521f6cfedb314d171630f827efee08f5c3462bcbc2244ab8e1768cd6", size = 171027, upload-time = "2025-12-15T16:51:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/d8/33/c70b6d40f7342716e5f1353c8da92d9e32708a18cbfa44897a93ec2bf879/librt-0.7.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df7c9def4fc619a9c2ab402d73a0c5b53899abe090e0100323b13ccb5a3dd82", size = 184700, upload-time = "2025-12-15T16:51:22.272Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c8/555c405155da210e4c4113a879d378f54f850dbc7b794e847750a8fadd43/librt-0.7.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f79bc3595b6ed159a1bf0cdc70ed6ebec393a874565cab7088a219cca14da727", size = 180719, upload-time = "2025-12-15T16:51:23.561Z" }, + { url = "https://files.pythonhosted.org/packages/6b/88/34dc1f1461c5613d1b73f0ecafc5316cc50adcc1b334435985b752ed53e5/librt-0.7.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77772a4b8b5f77d47d883846928c36d730b6e612a6388c74cba33ad9eb149c11", size = 174535, upload-time = "2025-12-15T16:51:25.031Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5a/f3fafe80a221626bcedfa9fe5abbf5f04070989d44782f579b2d5920d6d0/librt-0.7.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:064a286e6ab0b4c900e228ab4fa9cb3811b4b83d3e0cc5cd816b2d0f548cb61c", size = 195236, upload-time = "2025-12-15T16:51:26.328Z" }, + { url = "https://files.pythonhosted.org/packages/d8/77/5c048d471ce17f4c3a6e08419be19add4d291e2f7067b877437d482622ac/librt-0.7.4-cp311-cp311-win32.whl", hash = "sha256:42da201c47c77b6cc91fc17e0e2b330154428d35d6024f3278aa2683e7e2daf2", size = 42930, upload-time = "2025-12-15T16:51:27.853Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3b/514a86305a12c3d9eac03e424b07cd312c7343a9f8a52719aa079590a552/librt-0.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:d31acb5886c16ae1711741f22504195af46edec8315fe69b77e477682a87a83e", size = 49240, upload-time = "2025-12-15T16:51:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/3b7b1914f565926b780a734fac6e9a4d2c7aefe41f4e89357d73697a9457/librt-0.7.4-cp311-cp311-win_arm64.whl", hash = "sha256:114722f35093da080a333b3834fff04ef43147577ed99dd4db574b03a5f7d170", size = 42613, upload-time = "2025-12-15T16:51:30.194Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e7/b805d868d21f425b7e76a0ea71a2700290f2266a4f3c8357fcf73efc36aa/librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92", size = 55688, upload-time = "2025-12-15T16:51:31.571Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/69a2b02e62a14cfd5bfd9f1e9adea294d5bcfeea219c7555730e5d068ee4/librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108", size = 57141, upload-time = "2025-12-15T16:51:32.714Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/05dba608aae1272b8ea5ff8ef12c47a4a099a04d1e00e28a94687261d403/librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94", size = 165322, upload-time = "2025-12-15T16:51:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bc/199533d3fc04a4cda8d7776ee0d79955ab0c64c79ca079366fbc2617e680/librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab", size = 174216, upload-time = "2025-12-15T16:51:35.384Z" }, + { url = "https://files.pythonhosted.org/packages/62/ec/09239b912a45a8ed117cb4a6616d9ff508f5d3131bd84329bf2f8d6564f1/librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba", size = 189005, upload-time = "2025-12-15T16:51:36.687Z" }, + { url = "https://files.pythonhosted.org/packages/46/2e/e188313d54c02f5b0580dd31476bb4b0177514ff8d2be9f58d4a6dc3a7ba/librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9", size = 183960, upload-time = "2025-12-15T16:51:37.977Z" }, + { url = "https://files.pythonhosted.org/packages/eb/84/f1d568d254518463d879161d3737b784137d236075215e56c7c9be191cee/librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74", size = 177609, upload-time = "2025-12-15T16:51:40.584Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/060bbc1c002f0d757c33a1afe6bf6a565f947a04841139508fc7cef6c08b/librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f", size = 199269, upload-time = "2025-12-15T16:51:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7f/708f8f02d8012ee9f366c07ea6a92882f48bd06cc1ff16a35e13d0fbfb08/librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286", size = 43186, upload-time = "2025-12-15T16:51:43.149Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a5/4e051b061c8b2509be31b2c7ad4682090502c0a8b6406edcf8c6b4fe1ef7/librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20", size = 49455, upload-time = "2025-12-15T16:51:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d2/90d84e9f919224a3c1f393af1636d8638f54925fdc6cd5ee47f1548461e5/librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a", size = 42828, upload-time = "2025-12-15T16:51:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" }, + { url = "https://files.pythonhosted.org/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" }, + { url = "https://files.pythonhosted.org/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" }, + { url = "https://files.pythonhosted.org/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" }, + { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" }, + { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" }, + { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" }, + { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" }, + { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" }, + { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" }, + { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" }, + { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" }, + { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" }, + { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" }, + { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" }, + { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" }, + { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" }, + { url = "https://files.pythonhosted.org/packages/41/95/680a17d56b75ca3a29c1e4a1f29f5fe73bb75b8f1ad6f593f5bf608af0d2/librt-0.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fc4aa67fedd827a601f97f0e61cc72711d0a9165f2c518e9a7c38fc1568b9ad", size = 54838, upload-time = "2025-12-15T16:52:30.967Z" }, + { url = "https://files.pythonhosted.org/packages/71/a7/59937bf107923bba5b9cb2907f1113ee370a130833986c4c3c89b23fa695/librt-0.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e710c983d29d9cc4da29113b323647db286eaf384746344f4a233708cca1a82c", size = 56808, upload-time = "2025-12-15T16:52:32.097Z" }, + { url = "https://files.pythonhosted.org/packages/6a/13/41752a15d65000a22660dbb0ac3226370f52063aa3c3e6ad7590c394cb07/librt-0.7.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:43a2515a33f2bc17b15f7fb49ff6426e49cb1d5b2539bc7f8126b9c5c7f37164", size = 159679, upload-time = "2025-12-15T16:52:33.293Z" }, + { url = "https://files.pythonhosted.org/packages/6c/98/e3350986cd9e90ca32c51d14575a767af435b07eddacdbc513d85b66d8fd/librt-0.7.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fd766bb9ace3498f6b93d32f30c0e7c8ce6b727fecbc84d28160e217bb66254", size = 168514, upload-time = "2025-12-15T16:52:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/328993841687a6e4916d4213b64e0a6abad1517003ec0fc37e35a05976fe/librt-0.7.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce1b44091355b68cffd16e2abac07c1cafa953fa935852d3a4dd8975044ca3bf", size = 182229, upload-time = "2025-12-15T16:52:36.183Z" }, + { url = "https://files.pythonhosted.org/packages/59/42/c28819460b9780669420c1a5c2cacd1239e2cd952811bfa3b4f40be2f57b/librt-0.7.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a72b905420c4bb2c10c87b5c09fe6faf4a76d64730e3802feef255e43dfbf5a", size = 178265, upload-time = "2025-12-15T16:52:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/bf/11/2dd429f03a2bbf9f1be48d3a8264d963ced8acb9c0df3f23b3f024850a10/librt-0.7.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07c4d7c9305e75a0edd3427b79c7bd1d019cd7eddaa7c89dbb10e0c7946bffbb", size = 172567, upload-time = "2025-12-15T16:52:39.129Z" }, + { url = "https://files.pythonhosted.org/packages/92/4e/71e099b7665ad8e5fd74ef63b1c9d9b2b9d28208901ffbe4684da7fb3521/librt-0.7.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2e734c2c54423c6dcc77f58a8585ba83b9f72e422f9edf09cab1096d4a4bdc82", size = 192744, upload-time = "2025-12-15T16:52:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bd/06c59188a55a5b7fbb38d9b31f7ac32aabc66b912f0c3596c6c0fbfc1ffe/librt-0.7.4-cp39-cp39-win32.whl", hash = "sha256:a34ae11315d4e26326aaf04e21ccd8d9b7de983635fba38d73e203a9c8e3fe3d", size = 42551, upload-time = "2025-12-15T16:52:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/93/c6/532c79748f64d520d130fd4b7aa9d313d6abc614854113bc4de29b6a1eda/librt-0.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:7e4b5ffa1614ad4f32237d739699be444be28de95071bfa4e66a8da9fa777798", size = 48953, upload-time = "2025-12-15T16:52:42.653Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" }, - { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" }, - { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" }, - { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" }, - { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" }, - { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" }, - { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" }, - { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" }, - { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, ] [[package]] @@ -1218,11 +1444,11 @@ wheels = [ [[package]] name = "pip" -version = "25.2" +version = "25.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/16/650289cd3f43d5a2fadfd98c68bd1e1e7f2550a1a5326768cddfbcedb2c5/pip-25.2.tar.gz", hash = "sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2", size = 1840021, upload-time = "2025-07-30T21:50:15.401Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014, upload-time = "2025-10-25T00:55:41.394Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/3f/945ef7ab14dc4f9d7f40288d2df998d1837ee0888ec3659c813487572faa/pip-25.2-py3-none-any.whl", hash = "sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717", size = 1752557, upload-time = "2025-07-30T21:50:13.323Z" }, + { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, ] [[package]] @@ -1257,11 +1483,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.22" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] @@ -1283,7 +1509,8 @@ sdist = { url = "https://files.pythonhosted.org/packages/07/e9/ae44ea7d7605df9e5 name = "pymongo" source = { editable = "." } dependencies = [ - { name = "dnspython" }, + { name = "dnspython", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "dnspython", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] [package.optional-dependencies] @@ -1296,7 +1523,8 @@ docs = [ { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinx-autobuild" }, + { name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-rtd-theme" }, { name = "sphinxcontrib-shellcheck" }, ] @@ -1307,7 +1535,8 @@ encryption = [ ] gssapi = [ { name = "pykerberos", marker = "os_name != 'nt'" }, - { name = "winkerberos", marker = "os_name == 'nt'" }, + { name = "winkerberos", version = "0.12.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and os_name == 'nt'" }, + { name = "winkerberos", version = "0.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and os_name == 'nt'" }, ] ocsp = [ { name = "certifi", marker = "os_name == 'nt' or sys_platform == 'darwin'" }, @@ -1321,8 +1550,10 @@ snappy = [ ] test = [ { name = "importlib-metadata", marker = "python_full_version < '3.13'" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] zstd = [ { name = "backports-zstd", marker = "python_full_version < '3.14'" }, @@ -1330,7 +1561,7 @@ zstd = [ [package.dev-dependencies] coverage = [ - { name = "coverage" }, + { name = "coverage", extra = ["toml"] }, { name = "pytest-cov" }, ] gevent = [ @@ -1357,21 +1588,21 @@ requires-dist = [ { name = "backports-zstd", marker = "python_full_version < '3.14' and extra == 'zstd'", specifier = ">=1.0.0" }, { name = "certifi", marker = "(os_name == 'nt' and extra == 'encryption') or (sys_platform == 'darwin' and extra == 'encryption')", specifier = ">=2023.7.22" }, { name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')", specifier = ">=2023.7.22" }, - { name = "cryptography", marker = "extra == 'ocsp'", specifier = ">=2.5" }, + { name = "cryptography", marker = "extra == 'ocsp'", specifier = ">=42.0.0" }, { name = "dnspython", specifier = ">=2.6.1,<3.0.0" }, { name = "furo", marker = "extra == 'docs'", specifier = "==2025.9.25" }, { name = "importlib-metadata", marker = "python_full_version < '3.13' and extra == 'test'", specifier = ">=7.0" }, - { name = "pykerberos", marker = "os_name != 'nt' and extra == 'gssapi'" }, + { name = "pykerberos", marker = "os_name != 'nt' and extra == 'gssapi'", specifier = ">=1.2.4" }, { name = "pymongo-auth-aws", marker = "extra == 'aws'", specifier = ">=1.1.0,<2.0.0" }, { name = "pymongo-auth-aws", marker = "extra == 'encryption'", specifier = ">=1.1.0,<2.0.0" }, { name = "pymongocrypt", marker = "extra == 'encryption'", specifier = ">=1.13.0,<2.0.0" }, - { name = "pyopenssl", marker = "extra == 'ocsp'", specifier = ">=17.2.0" }, + { name = "pyopenssl", marker = "extra == 'ocsp'", specifier = ">=23.2.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.2" }, { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.24.0" }, - { name = "python-snappy", marker = "extra == 'snappy'" }, + { name = "python-snappy", marker = "extra == 'snappy'", specifier = ">=0.6.0" }, { name = "readthedocs-sphinx-search", marker = "extra == 'docs'", specifier = "~=0.3" }, - { name = "requests", marker = "extra == 'ocsp'", specifier = "<3.0.0" }, - { name = "service-identity", marker = "extra == 'ocsp'", specifier = ">=18.1.0" }, + { name = "requests", marker = "extra == 'ocsp'", specifier = ">=2.23.0,<3.0" }, + { name = "service-identity", marker = "extra == 'ocsp'", specifier = ">=23.1.0" }, { name = "sphinx", marker = "extra == 'docs'", specifier = ">=5.3,<9" }, { name = "sphinx-autobuild", marker = "extra == 'docs'", specifier = ">=2020.9.1" }, { name = "sphinx-rtd-theme", marker = "extra == 'docs'", specifier = ">=2,<4" }, @@ -1382,19 +1613,19 @@ provides-extras = ["aws", "docs", "encryption", "gssapi", "ocsp", "snappy", "tes [package.metadata.requires-dev] coverage = [ - { name = "coverage", specifier = ">=5,<=7.10.6" }, - { name = "pytest-cov" }, + { name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.6" }, + { name = "pytest-cov", specifier = ">=4.0.0" }, ] dev = [] -gevent = [{ name = "gevent", specifier = ">=20.6.0" }] +gevent = [{ name = "gevent", specifier = ">=21.12" }] mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mockup-db?rev=master" }] perf = [{ name = "simplejson", specifier = ">=3.17.0" }] -pip = [{ name = "pip" }] +pip = [{ name = "pip", specifier = ">=20.2" }] typing = [ { name = "mypy", specifier = "==1.19.1" }, - { name = "pip" }, + { name = "pip", specifier = ">=20.2" }, { name = "pyright", specifier = "==1.1.407" }, - { name = "typing-extensions" }, + { name = "typing-extensions", specifier = ">=3.7.4.2" }, ] [[package]] @@ -1412,7 +1643,7 @@ wheels = [ [[package]] name = "pymongocrypt" -version = "1.16.0" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, @@ -1420,12 +1651,12 @@ dependencies = [ { name = "httpx" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/dd9ed710e8fd4eec127dac1db3b3e9156ffcf340a0463a82087a12ae924e/pymongocrypt-1.16.0.tar.gz", hash = "sha256:0db0812055d00e6f5562a8d66711c4cba4b75014c363306c9b298a9fd68fccdd", size = 65354, upload-time = "2025-09-09T18:54:25.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/34/033ec24a6463c137ee2e7bae0acdc8fcbc6264bbce4c17b6bbdde9b9d4fd/pymongocrypt-1.17.0.tar.gz", hash = "sha256:f2cc38f8e29c41355631eba3257efda01914e56cac1dde1c3a6ec14ac93a95df", size = 65603, upload-time = "2025-11-19T17:46:07.643Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/8b/dda0f19ce16f7b257e4aa2a8831a1a1307c1ea124a00f571cda83a04adcb/pymongocrypt-1.16.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:fbd85534880ea8525956b96e583a7021c721abbf3b51a6dbe48a57d7eba8e74a", size = 4721169, upload-time = "2025-09-09T18:54:18.642Z" }, - { url = "https://files.pythonhosted.org/packages/99/48/512a5b597d71407f9b06a14cd8e5ac376e06b780d4d54a4e69726bd48703/pymongocrypt-1.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:85df0a78480e91bdd3a5a6da3e4cdc7d9700de8a871aa8168588981c041f1914", size = 4038242, upload-time = "2025-09-09T18:54:20.496Z" }, - { url = "https://files.pythonhosted.org/packages/3f/67/3bdeda347191d6c1ee257eb3da8c85f1278d86dfb493cc9bc26352a41d0a/pymongocrypt-1.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d2ebeb1b5e4f4554bf44f726e8009c59c4d7d0b412beebfece875991714676", size = 3775742, upload-time = "2025-09-09T18:54:22.254Z" }, - { url = "https://files.pythonhosted.org/packages/dc/81/70f6947afbd1ac7be54482b44cb1b99e8e9b9cac41985e6250c4fc279e58/pymongocrypt-1.16.0-py3-none-win_amd64.whl", hash = "sha256:c20afcd89ec5fc53305e924c05c4a0321ddc73f1e4e7c8240ee2fd0123e23609", size = 1607917, upload-time = "2025-09-09T18:54:24.182Z" }, + { url = "https://files.pythonhosted.org/packages/a5/08/3b7c412be8583b9f1d8fdc1522bb7e651e074fdbe4756236c5880aa404c7/pymongocrypt-1.17.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:6d6110623ca286c829eaea4ccb660a12e28f15afe771199657add0bbfae336d3", size = 4735549, upload-time = "2025-11-19T17:45:59.813Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c5/a97a19836bc2dd1966c3320c1f34af4b9ba7c3cb8a7672bd4f5b40100283/pymongocrypt-1.17.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f330fc28db2e2ea7d2bcdaec86dbe2413c8716703c6ff15e48a9907eeaec4a89", size = 4084094, upload-time = "2025-11-19T17:46:01.554Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8b/1b70ef0cd168160b9e64f4a945343f455cbb31816415319c76d55eb7ecb5/pymongocrypt-1.17.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e06349fd9fff6f6a2b334a7510da3cdfa698cfaea68e3c23c2a5de46c33c06a", size = 3810950, upload-time = "2025-11-19T17:46:03.07Z" }, + { url = "https://files.pythonhosted.org/packages/de/cf/7012422d482bec142ce627646fc5cead3675ad4337951d027f5e93a2b870/pymongocrypt-1.17.0-py3-none-win_amd64.whl", hash = "sha256:b1282ce2ee029abecdb11d5be41a12728bd83370059d0c7e76e2e1a652cbc1ec", size = 1614952, upload-time = "2025-11-19T17:46:05.75Z" }, ] [[package]] @@ -1456,48 +1687,95 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +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/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, - { name = "pytest" }, + { name = "backports-asyncio-runner", marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] name = "pytest-cov" -version = "6.2.1" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]] @@ -1535,39 +1813,51 @@ wheels = [ [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] name = "roman-numerals-py" -version = "3.1.0" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +dependencies = [ + { name = "roman-numerals", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, ] [[package]] name = "s3transfer" -version = "0.13.1" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589, upload-time = "2025-07-18T19:22:42.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, ] [[package]] @@ -1596,76 +1886,76 @@ wheels = [ [[package]] name = "simplejson" -version = "3.20.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/92/51b417685abd96b31308b61b9acce7ec50d8e1de8fbc39a7fd4962c60689/simplejson-3.20.1.tar.gz", hash = "sha256:e64139b4ec4f1f24c142ff7dcafe55a22b811a74d86d66560c8815687143037d", size = 85591, upload-time = "2025-02-15T05:18:53.15Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/c4/627214fb418cd4a17fb0230ff0b6c3bb4a85cbb48dd69c85dcc3b85df828/simplejson-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e580aa65d5f6c3bf41b9b4afe74be5d5ddba9576701c107c772d936ea2b5043a", size = 93790, upload-time = "2025-02-15T05:15:32.954Z" }, - { url = "https://files.pythonhosted.org/packages/15/ca/56a6a2a33cbcf330c4d71af3f827c47e4e0ba791e78f2642f3d1ab02ff31/simplejson-3.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a586ce4f78cec11f22fe55c5bee0f067e803aab9bad3441afe2181693b5ebb5", size = 75707, upload-time = "2025-02-15T05:15:34.954Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c8/3d92b67e03a3b6207d97202669f9454ed700b35ade9bd4428265a078fb6c/simplejson-3.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74a1608f9e6e8c27a4008d70a54270868306d80ed48c9df7872f9f4b8ac87808", size = 75700, upload-time = "2025-02-15T05:15:37.144Z" }, - { url = "https://files.pythonhosted.org/packages/74/30/20001219d6fdca4aaa3974c96dfb6955a766b4e2cc950505a5b51fd050b0/simplejson-3.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03db8cb64154189a92a7786209f24e391644f3a3fa335658be2df2af1960b8d8", size = 138672, upload-time = "2025-02-15T05:15:38.547Z" }, - { url = "https://files.pythonhosted.org/packages/21/47/50157810876c2a7ebbd6e6346ec25eda841fe061fecaa02538a7742a3d2a/simplejson-3.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eea7e2b7d858f6fdfbf0fe3cb846d6bd8a45446865bc09960e51f3d473c2271b", size = 146616, upload-time = "2025-02-15T05:15:39.871Z" }, - { url = "https://files.pythonhosted.org/packages/95/60/8c97cdc93096437b0aca2745aca63c880fe2315fd7f6a6ce6edbb344a2ae/simplejson-3.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e66712b17d8425bb7ff8968d4c7c7fd5a2dd7bd63728b28356223c000dd2f91f", size = 134344, upload-time = "2025-02-15T05:15:42.091Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9e/da184f0e9bb3a5d7ffcde713bd41b4fe46cca56b6f24d9bd155fac56805a/simplejson-3.20.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cc4f6486f9f515b62f5831ff1888886619b84fc837de68f26d919ba7bbdcbc", size = 138017, upload-time = "2025-02-15T05:15:43.542Z" }, - { url = "https://files.pythonhosted.org/packages/31/db/00d1a8d9b036db98f678c8a3c69ed17d2894d1768d7a00576e787ad3e546/simplejson-3.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3c2df555ee4016148fa192e2b9cd9e60bc1d40769366134882685e90aee2a1e", size = 140118, upload-time = "2025-02-15T05:15:45.7Z" }, - { url = "https://files.pythonhosted.org/packages/52/21/57fc47eab8c1c73390b933a5ba9271f08e3e1ec83162c580357f28f5b97c/simplejson-3.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:78520f04b7548a5e476b5396c0847e066f1e0a4c0c5e920da1ad65e95f410b11", size = 140314, upload-time = "2025-02-15T05:16:07.949Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cc/7cfd78d1e0fa5e57350b98cfe77353b6dfa13dce21afa4060e1019223852/simplejson-3.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f4bd49ecde87b0fe9f55cc971449a32832bca9910821f7072bbfae1155eaa007", size = 148544, upload-time = "2025-02-15T05:16:09.455Z" }, - { url = "https://files.pythonhosted.org/packages/63/26/1c894a1c2bd95dc8be0cf5a2fa73b0d173105b6ca18c90cb981ff10443d0/simplejson-3.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7eaae2b88eb5da53caaffdfa50e2e12022553949b88c0df4f9a9663609373f72", size = 141172, upload-time = "2025-02-15T05:16:10.966Z" }, - { url = "https://files.pythonhosted.org/packages/93/27/0717dccc10cd9988dbf1314def52ab32678a95a95328bb37cafacf499400/simplejson-3.20.1-cp310-cp310-win32.whl", hash = "sha256:e836fb88902799eac8debc2b642300748f4860a197fa3d9ea502112b6bb8e142", size = 74181, upload-time = "2025-02-15T05:16:12.361Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/593f896573f306519332d4287b1ab8b7b888c239bbd5159f7054d7055c2d/simplejson-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a19b552b212fc3b5b96fc5ce92333d4a9ac0a800803e1f17ebb16dac4be5", size = 75738, upload-time = "2025-02-15T05:16:14.438Z" }, - { url = "https://files.pythonhosted.org/packages/76/59/74bc90d1c051bc2432c96b34bd4e8036875ab58b4fcbe4d6a5a76985f853/simplejson-3.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:325b8c107253d3217e89d7b50c71015b5b31e2433e6c5bf38967b2f80630a8ca", size = 92132, upload-time = "2025-02-15T05:16:15.743Z" }, - { url = "https://files.pythonhosted.org/packages/71/c7/1970916e0c51794fff89f76da2f632aaf0b259b87753c88a8c409623d3e1/simplejson-3.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88a7baa8211089b9e58d78fbc1b0b322103f3f3d459ff16f03a36cece0d0fcf0", size = 74956, upload-time = "2025-02-15T05:16:17.062Z" }, - { url = "https://files.pythonhosted.org/packages/c8/0d/98cc5909180463f1d75fac7180de62d4cdb4e82c4fef276b9e591979372c/simplejson-3.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:299b1007b8101d50d95bc0db1bf5c38dc372e85b504cf77f596462083ee77e3f", size = 74772, upload-time = "2025-02-15T05:16:19.204Z" }, - { url = "https://files.pythonhosted.org/packages/e1/94/a30a5211a90d67725a3e8fcc1c788189f2ae2ed2b96b63ed15d0b7f5d6bb/simplejson-3.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ec618ed65caab48e81e3ed29586236a8e57daef792f1f3bb59504a7e98cd10", size = 143575, upload-time = "2025-02-15T05:16:21.337Z" }, - { url = "https://files.pythonhosted.org/packages/ee/08/cdb6821f1058eb5db46d252de69ff7e6c53f05f1bae6368fe20d5b51d37e/simplejson-3.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2cdead1d3197f0ff43373cf4730213420523ba48697743e135e26f3d179f38", size = 153241, upload-time = "2025-02-15T05:16:22.859Z" }, - { url = "https://files.pythonhosted.org/packages/4c/2d/ca3caeea0bdc5efc5503d5f57a2dfb56804898fb196dfada121323ee0ccb/simplejson-3.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3466d2839fdc83e1af42e07b90bc8ff361c4e8796cd66722a40ba14e458faddd", size = 141500, upload-time = "2025-02-15T05:16:25.068Z" }, - { url = "https://files.pythonhosted.org/packages/e1/33/d3e0779d5c58245e7370c98eb969275af6b7a4a5aec3b97cbf85f09ad328/simplejson-3.20.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d492ed8e92f3a9f9be829205f44b1d0a89af6582f0cf43e0d129fa477b93fe0c", size = 144757, upload-time = "2025-02-15T05:16:28.301Z" }, - { url = "https://files.pythonhosted.org/packages/54/53/2d93128bb55861b2fa36c5944f38da51a0bc6d83e513afc6f7838440dd15/simplejson-3.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f924b485537b640dc69434565463fd6fc0c68c65a8c6e01a823dd26c9983cf79", size = 144409, upload-time = "2025-02-15T05:16:29.687Z" }, - { url = "https://files.pythonhosted.org/packages/99/4c/dac310a98f897ad3435b4bdc836d92e78f09e38c5dbf28211ed21dc59fa2/simplejson-3.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e8eacf6a3491bf76ea91a8d46726368a6be0eb94993f60b8583550baae9439e", size = 146082, upload-time = "2025-02-15T05:16:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/ee/22/d7ba958cfed39827335b82656b1c46f89678faecda9a7677b47e87b48ee6/simplejson-3.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d04bf90b4cea7c22d8b19091633908f14a096caa301b24c2f3d85b5068fb8", size = 154339, upload-time = "2025-02-15T05:16:32.719Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c8/b072b741129406a7086a0799c6f5d13096231bf35fdd87a0cffa789687fc/simplejson-3.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69dd28d4ce38390ea4aaf212902712c0fd1093dc4c1ff67e09687c3c3e15a749", size = 147915, upload-time = "2025-02-15T05:16:34.291Z" }, - { url = "https://files.pythonhosted.org/packages/6c/46/8347e61e9cf3db5342a42f7fd30a81b4f5cf85977f916852d7674a540907/simplejson-3.20.1-cp311-cp311-win32.whl", hash = "sha256:dfe7a9da5fd2a3499436cd350f31539e0a6ded5da6b5b3d422df016444d65e43", size = 73972, upload-time = "2025-02-15T05:16:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/01/85/b52f24859237b4e9d523d5655796d911ba3d46e242eb1959c45b6af5aedd/simplejson-3.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:896a6c04d7861d507d800da7642479c3547060bf97419d9ef73d98ced8258766", size = 75595, upload-time = "2025-02-15T05:16:36.957Z" }, - { url = "https://files.pythonhosted.org/packages/8d/eb/34c16a1ac9ba265d024dc977ad84e1659d931c0a700967c3e59a98ed7514/simplejson-3.20.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f31c4a3a7ab18467ee73a27f3e59158255d1520f3aad74315edde7a940f1be23", size = 93100, upload-time = "2025-02-15T05:16:38.801Z" }, - { url = "https://files.pythonhosted.org/packages/41/fc/2c2c007d135894971e6814e7c0806936e5bade28f8db4dd7e2a58b50debd/simplejson-3.20.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884e6183d16b725e113b83a6fc0230152ab6627d4d36cb05c89c2c5bccfa7bc6", size = 75464, upload-time = "2025-02-15T05:16:40.905Z" }, - { url = "https://files.pythonhosted.org/packages/0f/05/2b5ecb33b776c34bb5cace5de5d7669f9b60e3ca13c113037b2ca86edfbd/simplejson-3.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03d7a426e416fe0d3337115f04164cd9427eb4256e843a6b8751cacf70abc832", size = 75112, upload-time = "2025-02-15T05:16:42.246Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/1f3609a2792f06cd4b71030485f78e91eb09cfd57bebf3116bf2980a8bac/simplejson-3.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:000602141d0bddfcff60ea6a6e97d5e10c9db6b17fd2d6c66199fa481b6214bb", size = 150182, upload-time = "2025-02-15T05:16:43.557Z" }, - { url = "https://files.pythonhosted.org/packages/2f/b0/053fbda38b8b602a77a4f7829def1b4f316cd8deb5440a6d3ee90790d2a4/simplejson-3.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af8377a8af78226e82e3a4349efdde59ffa421ae88be67e18cef915e4023a595", size = 158363, upload-time = "2025-02-15T05:16:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/d1/4b/2eb84ae867539a80822e92f9be4a7200dffba609275faf99b24141839110/simplejson-3.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c7de4c88ab2fbcb8781a3b982ef883696736134e20b1210bca43fb42ff1acf", size = 148415, upload-time = "2025-02-15T05:16:47.861Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bd/400b0bd372a5666addf2540c7358bfc3841b9ce5cdbc5cc4ad2f61627ad8/simplejson-3.20.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455a882ff3f97d810709f7b620007d4e0aca8da71d06fc5c18ba11daf1c4df49", size = 152213, upload-time = "2025-02-15T05:16:49.25Z" }, - { url = "https://files.pythonhosted.org/packages/50/12/143f447bf6a827ee9472693768dc1a5eb96154f8feb140a88ce6973a3cfa/simplejson-3.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc0f523ce923e7f38eb67804bc80e0a028c76d7868500aa3f59225574b5d0453", size = 150048, upload-time = "2025-02-15T05:16:51.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ea/dd9b3e8e8ed710a66f24a22c16a907c9b539b6f5f45fd8586bd5c231444e/simplejson-3.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76461ec929282dde4a08061071a47281ad939d0202dc4e63cdd135844e162fbc", size = 151668, upload-time = "2025-02-15T05:16:53Z" }, - { url = "https://files.pythonhosted.org/packages/99/af/ee52a8045426a0c5b89d755a5a70cc821815ef3c333b56fbcad33c4435c0/simplejson-3.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19c2da8c043607bde4d4ef3a6b633e668a7d2e3d56f40a476a74c5ea71949f", size = 158840, upload-time = "2025-02-15T05:16:54.851Z" }, - { url = "https://files.pythonhosted.org/packages/68/db/ab32869acea6b5de7d75fa0dac07a112ded795d41eaa7e66c7813b17be95/simplejson-3.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2578bedaedf6294415197b267d4ef678fea336dd78ee2a6d2f4b028e9d07be3", size = 154212, upload-time = "2025-02-15T05:16:56.318Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/e3132d454977d75a3bf9a6d541d730f76462ebf42a96fea2621498166f41/simplejson-3.20.1-cp312-cp312-win32.whl", hash = "sha256:339f407373325a36b7fd744b688ba5bae0666b5d340ec6d98aebc3014bf3d8ea", size = 74101, upload-time = "2025-02-15T05:16:57.746Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5d/4e243e937fa3560107c69f6f7c2eed8589163f5ed14324e864871daa2dd9/simplejson-3.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:627d4486a1ea7edf1f66bb044ace1ce6b4c1698acd1b05353c97ba4864ea2e17", size = 75736, upload-time = "2025-02-15T05:16:59.017Z" }, - { url = "https://files.pythonhosted.org/packages/c4/03/0f453a27877cb5a5fff16a975925f4119102cc8552f52536b9a98ef0431e/simplejson-3.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71e849e7ceb2178344998cbe5ade101f1b329460243c79c27fbfc51c0447a7c3", size = 93109, upload-time = "2025-02-15T05:17:00.377Z" }, - { url = "https://files.pythonhosted.org/packages/74/1f/a729f4026850cabeaff23e134646c3f455e86925d2533463420635ae54de/simplejson-3.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b63fdbab29dc3868d6f009a59797cefaba315fd43cd32ddd998ee1da28e50e29", size = 75475, upload-time = "2025-02-15T05:17:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/e2/14/50a2713fee8ff1f8d655b1a14f4a0f1c0c7246768a1b3b3d12964a4ed5aa/simplejson-3.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1190f9a3ce644fd50ec277ac4a98c0517f532cfebdcc4bd975c0979a9f05e1fb", size = 75112, upload-time = "2025-02-15T05:17:03.875Z" }, - { url = "https://files.pythonhosted.org/packages/45/86/ea9835abb646755140e2d482edc9bc1e91997ed19a59fd77ae4c6a0facea/simplejson-3.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1336ba7bcb722ad487cd265701ff0583c0bb6de638364ca947bb84ecc0015d1", size = 150245, upload-time = "2025-02-15T05:17:06.899Z" }, - { url = "https://files.pythonhosted.org/packages/12/b4/53084809faede45da829fe571c65fbda8479d2a5b9c633f46b74124d56f5/simplejson-3.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e975aac6a5acd8b510eba58d5591e10a03e3d16c1cf8a8624ca177491f7230f0", size = 158465, upload-time = "2025-02-15T05:17:08.707Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7d/d56579468d1660b3841e1f21c14490d103e33cf911886b22652d6e9683ec/simplejson-3.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a6dd11ee282937ad749da6f3b8d87952ad585b26e5edfa10da3ae2536c73078", size = 148514, upload-time = "2025-02-15T05:17:11.323Z" }, - { url = "https://files.pythonhosted.org/packages/19/e3/874b1cca3d3897b486d3afdccc475eb3a09815bf1015b01cf7fcb52a55f0/simplejson-3.20.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab980fcc446ab87ea0879edad41a5c28f2d86020014eb035cf5161e8de4474c6", size = 152262, upload-time = "2025-02-15T05:17:13.543Z" }, - { url = "https://files.pythonhosted.org/packages/32/84/f0fdb3625292d945c2bd13a814584603aebdb38cfbe5fe9be6b46fe598c4/simplejson-3.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f5aee2a4cb6b146bd17333ac623610f069f34e8f31d2f4f0c1a2186e50c594f0", size = 150164, upload-time = "2025-02-15T05:17:15.021Z" }, - { url = "https://files.pythonhosted.org/packages/95/51/6d625247224f01eaaeabace9aec75ac5603a42f8ebcce02c486fbda8b428/simplejson-3.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:652d8eecbb9a3b6461b21ec7cf11fd0acbab144e45e600c817ecf18e4580b99e", size = 151795, upload-time = "2025-02-15T05:17:16.542Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d9/bb921df6b35be8412f519e58e86d1060fddf3ad401b783e4862e0a74c4c1/simplejson-3.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c09948f1a486a89251ee3a67c9f8c969b379f6ffff1a6064b41fea3bce0a112", size = 159027, upload-time = "2025-02-15T05:17:18.083Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/5950605e4ad023a6621cf4c931b29fd3d2a9c1f36be937230bfc83d7271d/simplejson-3.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd7b215ad4fc6f058b5dd4c26ee5c59f72e031dfda3ac183d7968a99e4ca3a", size = 154380, upload-time = "2025-02-15T05:17:20.334Z" }, - { url = "https://files.pythonhosted.org/packages/66/ad/b74149557c5ec1e4e4d55758bda426f5d2ec0123cd01a53ae63b8de51fa3/simplejson-3.20.1-cp313-cp313-win32.whl", hash = "sha256:ae81e482476eaa088ef9d0120ae5345de924f23962c0c1e20abbdff597631f87", size = 74102, upload-time = "2025-02-15T05:17:22.475Z" }, - { url = "https://files.pythonhosted.org/packages/db/a9/25282fdd24493e1022f30b7f5cdf804255c007218b2bfaa655bd7ad34b2d/simplejson-3.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b9fd15853b90aec3b1739f4471efbf1ac05066a2c7041bf8db821bb73cd2ddc", size = 75736, upload-time = "2025-02-15T05:17:24.122Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ba/d32fe890a5edaf4a8518adf043bccf7866b600123f512a6de0988cf36810/simplejson-3.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8011f1dd1d676befcd4d675ebdbfdbbefd3bf350052b956ba8c699fca7d8cef", size = 93773, upload-time = "2025-02-15T05:18:28.231Z" }, - { url = "https://files.pythonhosted.org/packages/48/c7/361e7f6695b56001a04e0a5cc623cd6c82ea2f45e872e61213e405cc8a24/simplejson-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e91703a4c5fec53e36875ae426ad785f4120bd1d93b65bed4752eeccd1789e0c", size = 75697, upload-time = "2025-02-15T05:18:30.006Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2f/d0ff0b772d4ef092876eb85c99bc591c446b0502715551dad7dfc7f7c2c0/simplejson-3.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e39eaa57c7757daa25bcd21f976c46be443b73dd6c3da47fe5ce7b7048ccefe2", size = 75692, upload-time = "2025-02-15T05:18:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/26/94/cab4db9530b6ca9d62f16a260e8311b04130ccd670dab75e958fcb44590e/simplejson-3.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceab2ce2acdc7fbaa433a93006758db6ba9a659e80c4faa13b80b9d2318e9b17", size = 138106, upload-time = "2025-02-15T05:18:32.907Z" }, - { url = "https://files.pythonhosted.org/packages/40/22/11c0f746bdb44c297cea8a37d8f7ccb75ea6681132aadfb9f820d9a52647/simplejson-3.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d4f320c33277a5b715db5bf5b10dae10c19076bd6d66c2843e04bd12d1f1ea5", size = 146242, upload-time = "2025-02-15T05:18:35.223Z" }, - { url = "https://files.pythonhosted.org/packages/78/e9/b7c4c26f29b41cc41ba5f0224c47adbfa7f28427418edfd58ab122f3b584/simplejson-3.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b6436c48e64378fa844d8c9e58a5ed0352bbcfd4028369a9b46679b7ab79d2d", size = 133866, upload-time = "2025-02-15T05:18:36.998Z" }, - { url = "https://files.pythonhosted.org/packages/09/68/1e81ed83f38906c8859f2b973afb19302357d6003e724a6105cee0f61ec7/simplejson-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e18345c8dda5d699be8166b61f9d80aaee4545b709f1363f60813dc032dac53", size = 137444, upload-time = "2025-02-15T05:18:38.763Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6b/8d1e076c543277c1d603230eec24f4dd75ebce46d351c0679526d202981f/simplejson-3.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:90b573693d1526bed576f6817e2a492eaaef68f088b57d7a9e83d122bbb49e51", size = 139617, upload-time = "2025-02-15T05:18:40.36Z" }, - { url = "https://files.pythonhosted.org/packages/d1/46/7b74803de10d4157c5cd2e89028897fa733374667bc5520a44b23b6c887a/simplejson-3.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:272cc767826e924a6bd369ea3dbf18e166ded29059c7a4d64d21a9a22424b5b5", size = 139725, upload-time = "2025-02-15T05:18:42.012Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8f/9991582665a7b6d95415e439bb4fbaa4faf0f77231666675a0fd1de54107/simplejson-3.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:51b41f284d603c4380732d7d619f8b34bd04bc4aa0ed0ed5f4ffd0539b14da44", size = 148010, upload-time = "2025-02-15T05:18:43.749Z" }, - { url = "https://files.pythonhosted.org/packages/54/ee/3c6e91989cdf65ec75e75662d9f15cfe167a792b893806169ea5b1da6fd2/simplejson-3.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6e6697a3067d281f01de0fe96fc7cba4ea870d96d7deb7bfcf85186d74456503", size = 140624, upload-time = "2025-02-15T05:18:45.498Z" }, - { url = "https://files.pythonhosted.org/packages/9d/bd/05e13ebb7ead81c8b555f4ccc741ea7dfa0ef5c2a0c183d6a7bc50a02bca/simplejson-3.20.1-cp39-cp39-win32.whl", hash = "sha256:6dd3a1d5aca87bf947f3339b0f8e8e329f1badf548bdbff37fac63c17936da8e", size = 74148, upload-time = "2025-02-15T05:18:47.27Z" }, - { url = "https://files.pythonhosted.org/packages/88/c9/d8bf87aaebec5a4c3ccfd5228689578e2fe77027d6114a259255d54969bf/simplejson-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:463f1fca8fbf23d088e5850fdd0dd4d5faea8900a9f9680270bd98fd649814ca", size = 75732, upload-time = "2025-02-15T05:18:49.598Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/00f02a0a921556dd5a6db1ef2926a1bc7a8bbbfb1c49cfed68a275b8ab2b/simplejson-3.20.1-py3-none-any.whl", hash = "sha256:8a6c1bbac39fa4a79f83cbf1df6ccd8ff7069582a9fd8db1e52cea073bc2c697", size = 57121, upload-time = "2025-02-15T05:18:51.243Z" }, +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784, upload-time = "2025-09-26T16:29:36.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/09/2bf3761de89ea2d91bdce6cf107dcd858892d0adc22c995684878826cc6b/simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5", size = 94039, upload-time = "2025-09-26T16:27:29.283Z" }, + { url = "https://files.pythonhosted.org/packages/0f/33/c3277db8931f0ae9e54b9292668863365672d90fb0f632f4cf9829cb7d68/simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d", size = 75894, upload-time = "2025-09-26T16:27:30.378Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ea/ae47b04d03c7c8a7b7b1a8b39a6e27c3bd424e52f4988d70aca6293ff5e5/simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1", size = 76116, upload-time = "2025-09-26T16:27:31.42Z" }, + { url = "https://files.pythonhosted.org/packages/4b/42/6c9af551e5a8d0f171d6dce3d9d1260068927f7b80f1f09834e07887c8c4/simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6", size = 138827, upload-time = "2025-09-26T16:27:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/2b/22/5e268bbcbe9f75577491e406ec0a5536f5b2fa91a3b52031fea51cd83e1d/simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01", size = 146772, upload-time = "2025-09-26T16:27:34.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/b4/800f14728e2ad666f420dfdb57697ca128aeae7f991b35759c09356b829a/simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3", size = 134497, upload-time = "2025-09-26T16:27:35.211Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b9/c54eef4226c6ac8e9a389bbe5b21fef116768f97a2dc1a683c716ffe66ef/simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda", size = 138172, upload-time = "2025-09-26T16:27:36.44Z" }, + { url = "https://files.pythonhosted.org/packages/09/36/4e282f5211b34620f1b2e4b51d9ddaab5af82219b9b7b78360a33f7e5387/simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2", size = 140272, upload-time = "2025-09-26T16:27:37.605Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/94ad2cf32f477c449e1f63c863d8a513e2408d651c4e58fe4b6a7434e168/simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4", size = 140468, upload-time = "2025-09-26T16:27:39.015Z" }, + { url = "https://files.pythonhosted.org/packages/e5/46/827731e4163be3f987cb8ee90f5d444161db8f540b5e735355faa098d9bc/simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8", size = 148700, upload-time = "2025-09-26T16:27:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/c7/28/c32121064b1ec2fb7b5d872d9a1abda62df064d35e0160eddfa907118343/simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7", size = 141323, upload-time = "2025-09-26T16:27:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/46/b6/c897c54326fe86dd12d101981171a49361949f4728294f418c3b86a1af77/simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53", size = 74377, upload-time = "2025-09-26T16:27:42.533Z" }, + { url = "https://files.pythonhosted.org/packages/ad/87/a6e03d4d80cca99c1fee4e960f3440e2f21be9470e537970f960ca5547f1/simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476", size = 76081, upload-time = "2025-09-26T16:27:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3e/96898c6c66d9dca3f9bd14d7487bf783b4acc77471b42f979babbb68d4ca/simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f", size = 92633, upload-time = "2025-09-26T16:27:45.028Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a2/cd2e10b880368305d89dd540685b8bdcc136df2b3c76b5ddd72596254539/simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8", size = 75309, upload-time = "2025-09-26T16:27:46.142Z" }, + { url = "https://files.pythonhosted.org/packages/5d/02/290f7282eaa6ebe945d35c47e6534348af97472446951dce0d144e013f4c/simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a", size = 75308, upload-time = "2025-09-26T16:27:47.542Z" }, + { url = "https://files.pythonhosted.org/packages/43/91/43695f17b69e70c4b0b03247aa47fb3989d338a70c4b726bbdc2da184160/simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51", size = 143733, upload-time = "2025-09-26T16:27:48.673Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4b/fdcaf444ac1c3cbf1c52bf00320c499e1cf05d373a58a3731ae627ba5e2d/simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd", size = 153397, upload-time = "2025-09-26T16:27:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/c4/83/21550f81a50cd03599f048a2d588ffb7f4c4d8064ae091511e8e5848eeaa/simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013", size = 141654, upload-time = "2025-09-26T16:27:51.168Z" }, + { url = "https://files.pythonhosted.org/packages/cf/54/d76c0e72ad02450a3e723b65b04f49001d0e73218ef6a220b158a64639cb/simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81", size = 144913, upload-time = "2025-09-26T16:27:52.331Z" }, + { url = "https://files.pythonhosted.org/packages/3f/49/976f59b42a6956d4aeb075ada16ad64448a985704bc69cd427a2245ce835/simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa", size = 144568, upload-time = "2025-09-26T16:27:53.41Z" }, + { url = "https://files.pythonhosted.org/packages/60/c7/30bae30424ace8cd791ca660fed454ed9479233810fe25c3f3eab3d9dc7b/simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb", size = 146239, upload-time = "2025-09-26T16:27:54.502Z" }, + { url = "https://files.pythonhosted.org/packages/79/3e/7f3b7b97351c53746e7b996fcd106986cda1954ab556fd665314756618d2/simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2", size = 154497, upload-time = "2025-09-26T16:27:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/1d/48/7241daa91d0bf19126589f6a8dcbe8287f4ed3d734e76fd4a092708947be/simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413", size = 148069, upload-time = "2025-09-26T16:27:57.039Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/ef18d2962fe53e7be5123d3784e623859eec7ed97060c9c8536c69d34836/simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961", size = 74158, upload-time = "2025-09-26T16:27:58.265Z" }, + { url = "https://files.pythonhosted.org/packages/35/fd/3d1158ecdc573fdad81bf3cc78df04522bf3959758bba6597ba4c956c74d/simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c", size = 75911, upload-time = "2025-09-26T16:27:59.292Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9e/1a91e7614db0416885eab4136d49b7303de20528860ffdd798ce04d054db/simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6", size = 93523, upload-time = "2025-09-26T16:28:00.356Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2b/d2413f5218fc25608739e3d63fe321dfa85c5f097aa6648dbe72513a5f12/simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259", size = 75844, upload-time = "2025-09-26T16:28:01.756Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f1/efd09efcc1e26629e120fef59be059ce7841cc6e1f949a4db94f1ae8a918/simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8", size = 75655, upload-time = "2025-09-26T16:28:03.037Z" }, + { url = "https://files.pythonhosted.org/packages/97/ec/5c6db08e42f380f005d03944be1af1a6bd501cc641175429a1cbe7fb23b9/simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9", size = 150335, upload-time = "2025-09-26T16:28:05.027Z" }, + { url = "https://files.pythonhosted.org/packages/81/f5/808a907485876a9242ec67054da7cbebefe0ee1522ef1c0be3bfc90f96f6/simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868", size = 158519, upload-time = "2025-09-26T16:28:06.5Z" }, + { url = "https://files.pythonhosted.org/packages/66/af/b8a158246834645ea890c36136584b0cc1c0e4b83a73b11ebd9c2a12877c/simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b", size = 148571, upload-time = "2025-09-26T16:28:07.715Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/ed9b2571bbf38f1a2425391f18e3ac11cb1e91482c22d644a1640dea9da7/simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e", size = 152367, upload-time = "2025-09-26T16:28:08.921Z" }, + { url = "https://files.pythonhosted.org/packages/81/2c/bad68b05dd43e93f77994b920505634d31ed239418eb6a88997d06599983/simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c", size = 150205, upload-time = "2025-09-26T16:28:10.086Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/90c7fc878061adafcf298ce60cecdee17a027486e9dce507e87396d68255/simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970", size = 151823, upload-time = "2025-09-26T16:28:11.329Z" }, + { url = "https://files.pythonhosted.org/packages/ab/27/b85b03349f825ae0f5d4f780cdde0bbccd4f06c3d8433f6a3882df887481/simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e", size = 158997, upload-time = "2025-09-26T16:28:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/71/ad/d7f3c331fb930638420ac6d236db68e9f4c28dab9c03164c3cd0e7967e15/simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544", size = 154367, upload-time = "2025-09-26T16:28:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/f0/46/5c67324addd40fa2966f6e886cacbbe0407c03a500db94fb8bb40333fcdf/simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54", size = 74285, upload-time = "2025-09-26T16:28:15.931Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c9/5cc2189f4acd3a6e30ffa9775bf09b354302dbebab713ca914d7134d0f29/simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab", size = 75969, upload-time = "2025-09-26T16:28:17.017Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9e/f326d43f6bf47f4e7704a4426c36e044c6bedfd24e072fb8e27589a373a5/simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86", size = 93530, upload-time = "2025-09-26T16:28:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/35/28/5a4b8f3483fbfb68f3f460bc002cef3a5735ef30950e7c4adce9c8da15c7/simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74", size = 75846, upload-time = "2025-09-26T16:28:19.12Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4d/30dfef83b9ac48afae1cf1ab19c2867e27b8d22b5d9f8ca7ce5a0a157d8c/simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726", size = 75661, upload-time = "2025-09-26T16:28:20.219Z" }, + { url = "https://files.pythonhosted.org/packages/09/1d/171009bd35c7099d72ef6afd4bb13527bab469965c968a17d69a203d62a6/simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5", size = 150579, upload-time = "2025-09-26T16:28:21.337Z" }, + { url = "https://files.pythonhosted.org/packages/61/ae/229bbcf90a702adc6bfa476e9f0a37e21d8c58e1059043038797cbe75b8c/simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d", size = 158797, upload-time = "2025-09-26T16:28:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/90/c5/fefc0ac6b86b9108e302e0af1cf57518f46da0baedd60a12170791d56959/simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0", size = 148851, upload-time = "2025-09-26T16:28:23.733Z" }, + { url = "https://files.pythonhosted.org/packages/43/f1/b392952200f3393bb06fbc4dd975fc63a6843261705839355560b7264eb2/simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b", size = 152598, upload-time = "2025-09-26T16:28:24.962Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b4/d6b7279e52a3e9c0fa8c032ce6164e593e8d9cf390698ee981ed0864291b/simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f", size = 150498, upload-time = "2025-09-26T16:28:26.114Z" }, + { url = "https://files.pythonhosted.org/packages/62/22/ec2490dd859224326d10c2fac1353e8ad5c84121be4837a6dd6638ba4345/simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522", size = 152129, upload-time = "2025-09-26T16:28:27.552Z" }, + { url = "https://files.pythonhosted.org/packages/33/ce/b60214d013e93dd9e5a705dcb2b88b6c72bada442a97f79828332217f3eb/simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3", size = 159359, upload-time = "2025-09-26T16:28:28.667Z" }, + { url = "https://files.pythonhosted.org/packages/99/21/603709455827cdf5b9d83abe726343f542491ca8dc6a2528eb08de0cf034/simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769", size = 154717, upload-time = "2025-09-26T16:28:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f9/dc7f7a4bac16cf7eb55a4df03ad93190e11826d2a8950052949d3dfc11e2/simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661", size = 74289, upload-time = "2025-09-26T16:28:31.809Z" }, + { url = "https://files.pythonhosted.org/packages/87/10/d42ad61230436735c68af1120622b28a782877146a83d714da7b6a2a1c4e/simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608", size = 75972, upload-time = "2025-09-26T16:28:32.883Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2d/7c4968c60ddc8b504b77301cc80d6e75cd0269b81a779b01d66d8f36dcb8/simplejson-3.20.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bf76512ccb07d47944ebdca44c65b781612d38b9098566b4bb40f713fc4047", size = 94039, upload-time = "2025-09-26T16:29:17.406Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e4/d96b56fb87f245240b514c1fe552e76c17e09f0faa1f61137b2296f81529/simplejson-3.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:214e26acf2dfb9ff3314e65c4e168a6b125bced0e2d99a65ea7b0f169db1e562", size = 75893, upload-time = "2025-09-26T16:29:18.534Z" }, + { url = "https://files.pythonhosted.org/packages/09/4f/be411eeb52ab21d6d4c00722b632dd2bd430c01a47dfed3c15ef5ad7ee6e/simplejson-3.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fb1259ca9c385b0395bad59cdbf79535a5a84fb1988f339a49bfbc57455a35a", size = 76104, upload-time = "2025-09-26T16:29:19.66Z" }, + { url = "https://files.pythonhosted.org/packages/66/6f/3bd0007b64881a90a058c59a4869b1b4f130ddb86a726f884fafc67e5ef7/simplejson-3.20.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34e028a2ba8553a208ded1da5fa8501833875078c4c00a50dffc33622057881", size = 138261, upload-time = "2025-09-26T16:29:20.822Z" }, + { url = "https://files.pythonhosted.org/packages/15/5d/b6d0b71508e503c759a0a7563cb2c28716ec8af9828ca9f5b59023011406/simplejson-3.20.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b538f9d9e503b0dd43af60496780cb50755e4d8e5b34e5647b887675c1ae9fee", size = 146397, upload-time = "2025-09-26T16:29:22.363Z" }, + { url = "https://files.pythonhosted.org/packages/19/24/40b3e5a3ca5e6f80cc1c639fcd5565ae087e72e8656dea780f02302ddc97/simplejson-3.20.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab998e416ded6c58f549a22b6a8847e75a9e1ef98eb9fbb2863e1f9e61a4105b", size = 134020, upload-time = "2025-09-26T16:29:23.615Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8c/8fc2c2734ac9e514124635b25ca8f7e347db1ded4a30417ee41e78e6d61c/simplejson-3.20.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a8f1c307edf5fbf0c6db3396c5d3471409c4a40c7a2a466fbc762f20d46601a", size = 137598, upload-time = "2025-09-26T16:29:24.835Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d9/15036d7f43c6208fb0fbc827f9f897c1f577fba02aeb7a8a223581da4925/simplejson-3.20.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a7bbac80bdb82a44303f5630baee140aee208e5a4618e8b9fde3fc400a42671", size = 139770, upload-time = "2025-09-26T16:29:26.244Z" }, + { url = "https://files.pythonhosted.org/packages/73/cc/18374fb9dfcb4827b692ca5a33bdb607384ca06cdb645e0b863022dae8a3/simplejson-3.20.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5ef70ec8fe1569872e5a3e4720c1e1dcb823879a3c78bc02589eb88fab920b1f", size = 139884, upload-time = "2025-09-26T16:29:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a2/1526d4152806670124dd499ff831726a92bd7e029e8349c4affa78ea8845/simplejson-3.20.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11c09c99253a74c36925d461c86ea25f0140f3b98ff678322734ddc0f038d7", size = 148166, upload-time = "2025-09-26T16:29:29.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/77/fc16d41b5f67a2591c9b6ff7b0f6aed2b2aed1b6912bb346b61279697638/simplejson-3.20.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66f7c78c6ef776f8bd9afaad455e88b8197a51e95617bcc44b50dd974a7825ba", size = 140778, upload-time = "2025-09-26T16:29:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/4a/97/a26ef6b7387349623c042f329df70a4f3baf3a365fe6d1154d73da1dcf5a/simplejson-3.20.2-cp39-cp39-win32.whl", hash = "sha256:619ada86bfe3a5aa02b8222ca6bfc5aa3e1075c1fb5b3263d24ba579382df472", size = 74339, upload-time = "2025-09-26T16:29:32.648Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b7/94c6049a99e3c04eed2064e91295370b7429e2361188e35a78df562312e0/simplejson-3.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:44a6235e09ca5cc41aa5870a952489c06aa4aee3361ae46daa947d8398e57502", size = 76067, upload-time = "2025-09-26T16:29:34.184Z" }, + { url = "https://files.pythonhosted.org/packages/05/5b/83e1ff87eb60ca706972f7e02e15c0b33396e7bdbd080069a5d1b53cf0d8/simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017", size = 57309, upload-time = "2025-09-26T16:29:35.312Z" }, ] [[package]] @@ -1677,15 +1967,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - [[package]] name = "snowballstemmer" version = "3.0.1" @@ -1697,11 +1978,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.7" +version = "2.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, ] [[package]] @@ -1772,8 +2053,7 @@ name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.14.*'", - "python_full_version >= '3.15' or (python_full_version >= '3.11' and python_full_version < '3.14')", + "python_full_version >= '3.11'", ] dependencies = [ { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -1803,21 +2083,45 @@ wheels = [ name = "sphinx-autobuild" version = "2024.10.3" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] dependencies = [ - { name = "colorama" }, + { name = "colorama", marker = "python_full_version < '3.11'" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "starlette" }, - { name = "uvicorn" }, - { name = "watchfiles" }, - { name = "websockets" }, + { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "uvicorn", marker = "python_full_version < '3.11'" }, + { name = "watchfiles", marker = "python_full_version < '3.11'" }, + { name = "websockets", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" }, ] +[[package]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11'" }, + { name = "watchfiles", marker = "python_full_version >= '3.11'" }, + { name = "websockets", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" @@ -1938,63 +2242,93 @@ wheels = [ [[package]] name = "starlette" -version = "0.47.2" +version = "0.49.3" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "anyio", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031, upload-time = "2025-11-01T15:12:26.13Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340, upload-time = "2025-11-01T15:12:24.387Z" }, +] + +[[package]] +name = "starlette" +version = "0.50.0" +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/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +dependencies = [ + { name = "anyio", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] [[package]] name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] @@ -2011,147 +2345,149 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.14.*'", - "python_full_version >= '3.15' or (python_full_version >= '3.11' and python_full_version < '3.14')", + "python_full_version >= '3.11'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] name = "uvicorn" -version = "0.35.0" +version = "0.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, ] [[package]] name = "watchfiles" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757, upload-time = "2025-06-15T19:04:51.058Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511, upload-time = "2025-06-15T19:04:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739, upload-time = "2025-06-15T19:04:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106, upload-time = "2025-06-15T19:04:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264, upload-time = "2025-06-15T19:04:57.009Z" }, - { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612, upload-time = "2025-06-15T19:04:58.409Z" }, - { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242, upload-time = "2025-06-15T19:04:59.786Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148, upload-time = "2025-06-15T19:05:01.103Z" }, - { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574, upload-time = "2025-06-15T19:05:02.582Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378, upload-time = "2025-06-15T19:05:03.719Z" }, - { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829, upload-time = "2025-06-15T19:05:04.822Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192, upload-time = "2025-06-15T19:05:06.348Z" }, - { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, - { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, - { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, - { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, - { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, - { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, - { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, - { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, - { url = "https://files.pythonhosted.org/packages/47/8a/a45db804b9f0740f8408626ab2bca89c3136432e57c4673b50180bf85dd9/watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa", size = 406400, upload-time = "2025-06-15T19:06:30.233Z" }, - { url = "https://files.pythonhosted.org/packages/64/06/a08684f628fb41addd451845aceedc2407dc3d843b4b060a7c4350ddee0c/watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433", size = 397920, upload-time = "2025-06-15T19:06:31.315Z" }, - { url = "https://files.pythonhosted.org/packages/79/e6/e10d5675af653b1b07d4156906858041149ca222edaf8995877f2605ba9e/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4", size = 451196, upload-time = "2025-06-15T19:06:32.435Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8a/facd6988100cd0f39e89f6c550af80edb28e3a529e1ee662e750663e6b36/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7", size = 458218, upload-time = "2025-06-15T19:06:33.503Z" }, - { url = "https://files.pythonhosted.org/packages/90/26/34cbcbc4d0f2f8f9cc243007e65d741ae039f7a11ef8ec6e9cd25bee08d1/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f", size = 484851, upload-time = "2025-06-15T19:06:34.541Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1f/f59faa9fc4b0e36dbcdd28a18c430416443b309d295d8b82e18192d120ad/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf", size = 599520, upload-time = "2025-06-15T19:06:35.785Z" }, - { url = "https://files.pythonhosted.org/packages/83/72/3637abecb3bf590529f5154ca000924003e5f4bbb9619744feeaf6f0b70b/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29", size = 477956, upload-time = "2025-06-15T19:06:36.965Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f3/d14ffd9acc0c1bd4790378995e320981423263a5d70bd3929e2e0dc87fff/watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e", size = 453196, upload-time = "2025-06-15T19:06:38.024Z" }, - { url = "https://files.pythonhosted.org/packages/7f/38/78ad77bd99e20c0fdc82262be571ef114fc0beef9b43db52adb939768c38/watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86", size = 627479, upload-time = "2025-06-15T19:06:39.442Z" }, - { url = "https://files.pythonhosted.org/packages/e6/cf/549d50a22fcc83f1017c6427b1c76c053233f91b526f4ad7a45971e70c0b/watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f", size = 624414, upload-time = "2025-06-15T19:06:40.859Z" }, - { url = "https://files.pythonhosted.org/packages/72/de/57d6e40dc9140af71c12f3a9fc2d3efc5529d93981cd4d265d484d7c9148/watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267", size = 280020, upload-time = "2025-06-15T19:06:41.89Z" }, - { url = "https://files.pythonhosted.org/packages/88/bb/7d287fc2a762396b128a0fca2dbae29386e0a242b81d1046daf389641db3/watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc", size = 292758, upload-time = "2025-06-15T19:06:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748, upload-time = "2025-06-15T19:06:44.2Z" }, - { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801, upload-time = "2025-06-15T19:06:45.774Z" }, - { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528, upload-time = "2025-06-15T19:06:46.791Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095, upload-time = "2025-06-15T19:06:48.211Z" }, - { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, - { url = "https://files.pythonhosted.org/packages/48/93/5c96bdb65e7f88f7da40645f34c0a3c317a2931ed82161e93c91e8eddd27/watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9", size = 406640, upload-time = "2025-06-15T19:06:54.868Z" }, - { url = "https://files.pythonhosted.org/packages/e3/25/09204836e93e1b99cce88802ce87264a1d20610c7a8f6de24def27ad95b1/watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a", size = 398543, upload-time = "2025-06-15T19:06:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/5e/dc/6f324a6f32c5ab73b54311b5f393a79df34c1584b8d2404cf7e6d780aa5d/watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866", size = 451787, upload-time = "2025-06-15T19:06:56.998Z" }, - { url = "https://files.pythonhosted.org/packages/45/5d/1d02ef4caa4ec02389e72d5594cdf9c67f1800a7c380baa55063c30c6598/watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277", size = 454272, upload-time = "2025-06-15T19:06:58.055Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/a4/68/a7303a15cc797ab04d58f1fea7f67c50bd7f80090dfd7e750e7576e07582/watchfiles-1.1.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70", size = 409220, upload-time = "2025-10-14T15:05:51.917Z" }, + { url = "https://files.pythonhosted.org/packages/99/b8/d1857ce9ac76034c053fa7ef0e0ef92d8bd031e842ea6f5171725d31e88f/watchfiles-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e", size = 396712, upload-time = "2025-10-14T15:05:53.437Z" }, + { url = "https://files.pythonhosted.org/packages/41/7a/da7ada566f48beaa6a30b13335b49d1f6febaf3a5ddbd1d92163a1002cf4/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956", size = 451462, upload-time = "2025-10-14T15:05:54.742Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b2/7cb9e0d5445a8d45c4cccd68a590d9e3a453289366b96ff37d1075aaebef/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c", size = 460811, upload-time = "2025-10-14T15:05:55.743Z" }, + { url = "https://files.pythonhosted.org/packages/04/9d/b07d4491dde6db6ea6c680fdec452f4be363d65c82004faf2d853f59b76f/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c", size = 490576, upload-time = "2025-10-14T15:05:56.983Z" }, + { url = "https://files.pythonhosted.org/packages/56/03/e64dcab0a1806157db272a61b7891b062f441a30580a581ae72114259472/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3", size = 597726, upload-time = "2025-10-14T15:05:57.986Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8e/a827cf4a8d5f2903a19a934dcf512082eb07675253e154d4cd9367978a58/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2", size = 474900, upload-time = "2025-10-14T15:05:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/94fed0b346b85b22303a12eee5f431006fae6af70d841cac2f4403245533/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02", size = 457521, upload-time = "2025-10-14T15:06:00.419Z" }, + { url = "https://files.pythonhosted.org/packages/c4/64/bc3331150e8f3c778d48a4615d4b72b3d2d87868635e6c54bbd924946189/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be", size = 632191, upload-time = "2025-10-14T15:06:01.621Z" }, + { url = "https://files.pythonhosted.org/packages/e4/84/f39e19549c2f3ec97225dcb2ceb9a7bb3c5004ed227aad1f321bf0ff2051/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f", size = 623923, upload-time = "2025-10-14T15:06:02.671Z" }, + { url = "https://files.pythonhosted.org/packages/0e/24/0759ae15d9a0c9c5fe946bd4cf45ab9e7bad7cfede2c06dc10f59171b29f/watchfiles-1.1.1-cp39-cp39-win32.whl", hash = "sha256:6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b", size = 274010, upload-time = "2025-10-14T15:06:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3b/eb26cddd4dfa081e2bf6918be3b2fc05ee3b55c1d21331d5562ee0c6aaad/watchfiles-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957", size = 289090, upload-time = "2025-10-14T15:06:04.821Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/38a2c52fdbbfe2fc7ffaaaaaebc927d52b9f4d5139bba3186c19a7463001/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f", size = 409210, upload-time = "2025-10-14T15:06:14.492Z" }, + { url = "https://files.pythonhosted.org/packages/d1/43/d7e8b71f6c21ff813ee8da1006f89b6c7fff047fb4c8b16ceb5e840599c5/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34", size = 397286, upload-time = "2025-10-14T15:06:16.177Z" }, + { url = "https://files.pythonhosted.org/packages/1f/5d/884074a5269317e75bd0b915644b702b89de73e61a8a7446e2b225f45b1f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc", size = 451768, upload-time = "2025-10-14T15:06:18.266Z" }, + { url = "https://files.pythonhosted.org/packages/17/71/7ffcaa9b5e8961a25026058058c62ec8f604d2a6e8e1e94bee8a09e1593f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e", size = 458561, upload-time = "2025-10-14T15:06:19.323Z" }, ] [[package]] @@ -2234,6 +2570,9 @@ wheels = [ name = "winkerberos" version = "0.12.2" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/2d/75/86d470935167eb1c40d53498993e14cc021d9611a539d61c9b4202c291ab/winkerberos-0.12.2.tar.gz", hash = "sha256:ff91daed04727a0362892802ee093d8da11f08536393526bdf3bc64e04079faa", size = 35672, upload-time = "2025-04-02T14:41:48.274Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/59/ac/c6ce495af45371ffd85a6a3d24c2ced679b8dbcf3b8c6beca093706b1620/winkerberos-0.12.2-cp310-cp310-win32.whl", hash = "sha256:f8b751bd5a28e6a9146f154bed395c30ce4f245448addc763f98cb8843879027", size = 25331, upload-time = "2025-04-02T14:41:36.398Z" }, @@ -2248,6 +2587,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/04/ae42e839e8d836fde613f94f30395953292a7b9be388247237196d1e5caa/winkerberos-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:4b908aab5ab42e98bee44eca67dfebe4733d210bccf021e42b669bf4af2005a4", size = 27663, upload-time = "2025-04-02T14:41:47.294Z" }, ] +[[package]] +name = "winkerberos" +version = "0.13.0" +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/ca/6c/455f043bc28694a278125d1fc2ab7cbf0ce0953c97bbe1021f08fd19c7b8/winkerberos-0.13.0.tar.gz", hash = "sha256:f3fbb67346fe8ed697e125724b0699d5c2a15b9a5f9151d25a1be88df8dac427", size = 35677, upload-time = "2025-12-03T14:17:29.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/45/7199a756e3b25757cbf5986c8af040647aba24b039493eddff7950007f31/winkerberos-0.13.0-cp310-cp310-win32.whl", hash = "sha256:e6df7ab4c4e39e3e1d539b32ea20df84dc7ac32391391bf415c2a8051082051d", size = 25637, upload-time = "2025-12-03T14:17:15.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/90/9b1e787831496683c494f50e05fe08a0579e51c4d3b8bbc90d7fadbf8858/winkerberos-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1293325d69bfd75aefecde45ee1e52a0adfc29f2e19650eea9a87fddaa20b02", size = 27925, upload-time = "2025-12-03T14:17:16.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/09/05c4d2fb93f5478fd1b6146c4fa3fbb80839576a34062e5677f2dec3a430/winkerberos-0.13.0-cp311-cp311-win32.whl", hash = "sha256:a23c83854650416545000c4630e94b16fa14c7b400bd5f08a79718e04eff9135", size = 25642, upload-time = "2025-12-03T14:17:17.42Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5b/bafa1cfb9f047be139ffae330f6eafa0487f8bf82164ead756e0bc2bc047/winkerberos-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bc03e66a737bfd11964e6cdc5f03a8cd0baed798f991b1467075c65980c4157", size = 27931, upload-time = "2025-12-03T14:17:18.708Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fa/02de79d7dbec9122a6778678ed432ebffb228c48b16cfba3007c45a6e8fd/winkerberos-0.13.0-cp312-cp312-win32.whl", hash = "sha256:3454b8bb9c11091e4775a8bd692dfbe45f2eab12f3a4837b820c2505088dfdd2", size = 25675, upload-time = "2025-12-03T14:17:20.052Z" }, + { url = "https://files.pythonhosted.org/packages/52/c2/ff9074cf423d82bdfb48ac89e64f360533ba4e2079e8485be8377a8c54fe/winkerberos-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:59f01879c62adcda5af857fd78d2b2dfdfd99cf6179b92d38e2f2bd12db75bf7", size = 27946, upload-time = "2025-12-03T14:17:21.22Z" }, + { url = "https://files.pythonhosted.org/packages/92/83/b1f52594cc2c3ce18c67a04aecb0cb4fb3f4769c268d194cc5f4863150fa/winkerberos-0.13.0-cp313-cp313-win32.whl", hash = "sha256:38fefdfc77a7f82c3cc9f83c7d1b6f242e6d3ea200bfde9b640f7dfe9fdf9bda", size = 25671, upload-time = "2025-12-03T14:17:23.235Z" }, + { url = "https://files.pythonhosted.org/packages/9c/26/b17649b0707e4d8cd9d0d4ceadcef06eff2fc76fcb444cb187763158ae63/winkerberos-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:c45e84a35a3b87b88d0e6d7b55d40712dc021f80af3cb9e81091651e6a73510d", size = 27941, upload-time = "2025-12-03T14:17:24.627Z" }, + { url = "https://files.pythonhosted.org/packages/80/d9/d12d310fdf9ace70f7469ecfd9f112dc39cb7e1f77348228c06a6bd72c57/winkerberos-0.13.0-cp314-cp314-win32.whl", hash = "sha256:46cc29fa95744076a0dd2a167158574826509a5e4aa052b81a2b535aab4af14a", size = 26185, upload-time = "2025-12-03T14:17:25.63Z" }, + { url = "https://files.pythonhosted.org/packages/97/7c/5a418e8d292e3fea1012ccf029b38fae430542fab1beaf6fc60cf138cc08/winkerberos-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:5d5add54d10e31671f7c28c90ccafe98b45cec6d7519949ba30add51e34aee9a", size = 28476, upload-time = "2025-12-03T14:17:26.629Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/cd8186479046b7a749cee8d4d9fd50e3ce3330d8ea611efe4b8b741f0c3b/winkerberos-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5bc5e40a816d94d4a5abd665fe62088c1ee91ee9a1f5d787032a63004842fedf", size = 26500, upload-time = "2025-12-03T14:17:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a6/cc5f24b3f1a46a826b7e30ef56fdc1fe22315fef96de8e22afbdd5d98e7a/winkerberos-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:441884c0bda4bee0125fdbd7fee6a232dab58b4a64be8950eb17a8a7404a5440", size = 28715, upload-time = "2025-12-03T14:17:28.813Z" }, +] + [[package]] name = "zipp" version = "3.23.0" @@ -2259,53 +2622,111 @@ wheels = [ [[package]] name = "zope-event" -version = "5.1.1" +version = "6.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "setuptools" }, + { name = "setuptools", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/9f/c443569a68d3844c044d9fa9711e08adb33649b527b4d432433f4c2a6a02/zope_event-5.1.1.tar.gz", hash = "sha256:c1ac931abf57efba71a2a313c5f4d57768a19b15c37e3f02f50eb1536be12d4e", size = 18811, upload-time = "2025-07-22T07:04:00.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/d8/9c8b0c6bb1db09725395618f68d3b8a08089fca0aed28437500caaf713ee/zope_event-6.0.tar.gz", hash = "sha256:0ebac894fa7c5f8b7a89141c272133d8c1de6ddc75ea4b1f327f00d1f890df92", size = 18731, upload-time = "2025-09-12T07:10:13.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/04/fd55695f6448abd22295fc68b2d3a135389558f0f49a24b0dffe019d0ecb/zope_event-5.1.1-py3-none-any.whl", hash = "sha256:8d5ea7b992c42ce73a6fa9c2ba99a004c52cd9f05d87f3220768ef0329b92df7", size = 7014, upload-time = "2025-07-22T07:03:59.9Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b5/1abb5a8b443314c978617bf46d5d9ad648bdf21058074e817d7efbb257db/zope_event-6.0-py3-none-any.whl", hash = "sha256:6f0922593407cc673e7d8766b492c519f91bdc99f3080fe43dcec0a800d682a3", size = 6409, upload-time = "2025-09-12T07:10:12.316Z" }, +] + +[[package]] +name = "zope-event" +version = "6.1" +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/46/33/d3eeac228fc14de76615612ee208be2d8a5b5b0fada36bf9b62d6b40600c/zope_event-6.1.tar.gz", hash = "sha256:6052a3e0cb8565d3d4ef1a3a7809336ac519bc4fe38398cb8d466db09adef4f0", size = 18739, upload-time = "2025-11-07T08:05:49.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/b0/956902e5e1302f8c5d124e219c6bf214e2649f92ad5fce85b05c039a04c9/zope_event-6.1-py3-none-any.whl", hash = "sha256:0ca78b6391b694272b23ec1335c0294cc471065ed10f7f606858fc54566c25a0", size = 6414, upload-time = "2025-11-07T08:05:48.874Z" }, ] [[package]] name = "zope-interface" -version = "7.2" +version = "8.0.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/71/e6177f390e8daa7e75378505c5ab974e0bf59c1d3b19155638c7afbf4b2d/zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2", size = 208243, upload-time = "2024-11-28T08:47:29.781Z" }, - { url = "https://files.pythonhosted.org/packages/52/db/7e5f4226bef540f6d55acfd95cd105782bc6ee044d9b5587ce2c95558a5e/zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a", size = 208759, upload-time = "2024-11-28T08:47:31.908Z" }, - { url = "https://files.pythonhosted.org/packages/28/ea/fdd9813c1eafd333ad92464d57a4e3a82b37ae57c19497bcffa42df673e4/zope.interface-7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f1c6588ecc368c9ce13c44a49b8d6b6f3ca7588873c679bd8fd88a1b557b6", size = 254922, upload-time = "2024-11-28T09:18:11.795Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d3/0000a4d497ef9fbf4f66bb6828b8d0a235e690d57c333be877bec763722f/zope.interface-7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ef9e2f865721553c6f22a9ff97da0f0216c074bd02b25cf0d3af60ea4d6931d", size = 249367, upload-time = "2024-11-28T08:48:24.238Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e5/0b359e99084f033d413419eff23ee9c2bd33bca2ca9f4e83d11856f22d10/zope.interface-7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f926f0dcb058211a3bb3e0e501c69759613b17a553788b2caeb991bed3b61d", size = 254488, upload-time = "2024-11-28T08:48:28.816Z" }, - { url = "https://files.pythonhosted.org/packages/7b/90/12d50b95f40e3b2fc0ba7f7782104093b9fd62806b13b98ef4e580f2ca61/zope.interface-7.2-cp310-cp310-win_amd64.whl", hash = "sha256:144964649eba4c5e4410bb0ee290d338e78f179cdbfd15813de1a664e7649b3b", size = 211947, upload-time = "2024-11-28T08:48:18.831Z" }, - { url = "https://files.pythonhosted.org/packages/98/7d/2e8daf0abea7798d16a58f2f3a2bf7588872eee54ac119f99393fdd47b65/zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2", size = 208776, upload-time = "2024-11-28T08:47:53.009Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2a/0c03c7170fe61d0d371e4c7ea5b62b8cb79b095b3d630ca16719bf8b7b18/zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22", size = 209296, upload-time = "2024-11-28T08:47:57.993Z" }, - { url = "https://files.pythonhosted.org/packages/49/b4/451f19448772b4a1159519033a5f72672221e623b0a1bd2b896b653943d8/zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7", size = 260997, upload-time = "2024-11-28T09:18:13.935Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/5aa4461c10718062c8f8711161faf3249d6d3679c24a0b81dd6fc8ba1dd3/zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c", size = 255038, upload-time = "2024-11-28T08:48:26.381Z" }, - { url = "https://files.pythonhosted.org/packages/9f/aa/1a28c02815fe1ca282b54f6705b9ddba20328fabdc37b8cf73fc06b172f0/zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a", size = 259806, upload-time = "2024-11-28T08:48:30.78Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2c/82028f121d27c7e68632347fe04f4a6e0466e77bb36e104c8b074f3d7d7b/zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1", size = 212305, upload-time = "2024-11-28T08:49:14.525Z" }, - { url = "https://files.pythonhosted.org/packages/68/0b/c7516bc3bad144c2496f355e35bd699443b82e9437aa02d9867653203b4a/zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7", size = 208959, upload-time = "2024-11-28T08:47:47.788Z" }, - { url = "https://files.pythonhosted.org/packages/a2/e9/1463036df1f78ff8c45a02642a7bf6931ae4a38a4acd6a8e07c128e387a7/zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465", size = 209357, upload-time = "2024-11-28T08:47:50.897Z" }, - { url = "https://files.pythonhosted.org/packages/07/a8/106ca4c2add440728e382f1b16c7d886563602487bdd90004788d45eb310/zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89", size = 264235, upload-time = "2024-11-28T09:18:15.56Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ca/57286866285f4b8a4634c12ca1957c24bdac06eae28fd4a3a578e30cf906/zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54", size = 259253, upload-time = "2024-11-28T08:48:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/96/08/2103587ebc989b455cf05e858e7fbdfeedfc3373358320e9c513428290b1/zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d", size = 264702, upload-time = "2024-11-28T08:48:37.363Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c7/3c67562e03b3752ba4ab6b23355f15a58ac2d023a6ef763caaca430f91f2/zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5", size = 212466, upload-time = "2024-11-28T08:49:14.397Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3b/e309d731712c1a1866d61b5356a069dd44e5b01e394b6cb49848fa2efbff/zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98", size = 208961, upload-time = "2024-11-28T08:48:29.865Z" }, - { url = "https://files.pythonhosted.org/packages/49/65/78e7cebca6be07c8fc4032bfbb123e500d60efdf7b86727bb8a071992108/zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d", size = 209356, upload-time = "2024-11-28T08:48:33.297Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/627384b745310d082d29e3695db5f5a9188186676912c14b61a78bbc6afe/zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c", size = 264196, upload-time = "2024-11-28T09:18:17.584Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f6/54548df6dc73e30ac6c8a7ff1da73ac9007ba38f866397091d5a82237bd3/zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398", size = 259237, upload-time = "2024-11-28T08:48:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" }, - { url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" }, - { url = "https://files.pythonhosted.org/packages/8c/2c/1f49dc8b4843c4f0848d8e43191aed312bad946a1563d1bf9e46cf2816ee/zope.interface-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bd449c306ba006c65799ea7912adbbfed071089461a19091a228998b82b1fdb", size = 208349, upload-time = "2024-11-28T08:49:28.872Z" }, - { url = "https://files.pythonhosted.org/packages/ed/7d/83ddbfc8424c69579a90fc8edc2b797223da2a8083a94d8dfa0e374c5ed4/zope.interface-7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a19a6cc9c6ce4b1e7e3d319a473cf0ee989cbbe2b39201d7c19e214d2dfb80c7", size = 208799, upload-time = "2024-11-28T08:49:30.616Z" }, - { url = "https://files.pythonhosted.org/packages/36/22/b1abd91854c1be03f5542fe092e6a745096d2eca7704d69432e119100583/zope.interface-7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cd1790b48c16db85d51fbbd12d20949d7339ad84fd971427cf00d990c1f137", size = 254267, upload-time = "2024-11-28T09:18:21.059Z" }, - { url = "https://files.pythonhosted.org/packages/2a/dd/fcd313ee216ad0739ae00e6126bc22a0af62a74f76a9ca668d16cd276222/zope.interface-7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e446f9955195440e787596dccd1411f543743c359eeb26e9b2c02b077b0519", size = 248614, upload-time = "2024-11-28T08:48:41.953Z" }, - { url = "https://files.pythonhosted.org/packages/88/d4/4ba1569b856870527cec4bf22b91fe704b81a3c1a451b2ccf234e9e0666f/zope.interface-7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad9913fd858274db8dd867012ebe544ef18d218f6f7d1e3c3e6d98000f14b75", size = 253800, upload-time = "2024-11-28T08:48:46.637Z" }, - { url = "https://files.pythonhosted.org/packages/69/da/c9cfb384c18bd3a26d9fc6a9b5f32ccea49ae09444f097eaa5ca9814aff9/zope.interface-7.2-cp39-cp39-win_amd64.whl", hash = "sha256:1090c60116b3da3bfdd0c03406e2f14a1ff53e5771aebe33fec1edc0a350175d", size = 211980, upload-time = "2024-11-28T08:50:35.681Z" }, +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/88/3a/7fcf02178b8fad0a51e67e32765cd039ae505d054d744d76b8c2bbcba5ba/zope_interface-8.0.1.tar.gz", hash = "sha256:eba5610d042c3704a48222f7f7c6ab5b243ed26f917e2bc69379456b115e02d1", size = 253746, upload-time = "2025-09-25T05:55:51.285Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/e5/ffef169d17b92c6236b3b18b890c0ce73502f3cbd5b6532ff20d412d94a3/zope_interface-8.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fd7195081b8637eeed8d73e4d183b07199a1dc738fb28b3de6666b1b55662570", size = 207364, upload-time = "2025-09-25T05:58:50.262Z" }, + { url = "https://files.pythonhosted.org/packages/35/b6/87aca626c09af829d3a32011599d6e18864bc8daa0ad3a7e258f3d7f8bcf/zope_interface-8.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f7c4bc4021108847bce763673ce70d0716b08dfc2ba9889e7bad46ac2b3bb924", size = 207901, upload-time = "2025-09-25T05:58:51.74Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c1/eec33cc9f847ebeb0bc6234d7d45fe3fc0a6fe8fc5b5e6be0442bd2c684d/zope_interface-8.0.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:758803806b962f32c87b31bb18c298b022965ba34fe532163831cc39118c24ab", size = 249358, upload-time = "2025-09-25T05:58:16.979Z" }, + { url = "https://files.pythonhosted.org/packages/58/7d/1e3476a1ef0175559bd8492dc7bb921ad0df5b73861d764b1f824ad5484a/zope_interface-8.0.1-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f8e88f35f86bbe8243cad4b2972deef0fdfca0a0723455abbebdc83bbab96b69", size = 254475, upload-time = "2025-09-25T05:58:10.032Z" }, + { url = "https://files.pythonhosted.org/packages/bc/67/ba5ea98ff23f723c5cbe7db7409f2e43c9fe2df1ced67881443c01e64478/zope_interface-8.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7844765695937d9b0d83211220b72e2cf6ac81a08608ad2b58f2c094af498d83", size = 254913, upload-time = "2025-09-25T06:26:22.263Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a7/b1b8b6c13fba955c043cdee409953ee85f652b106493e2e931a84f95c1aa/zope_interface-8.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:64fa7b206dd9669f29d5c1241a768bebe8ab1e8a4b63ee16491f041e058c09d0", size = 211753, upload-time = "2025-09-25T05:59:00.561Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/c10c739bcb9b072090c97c2e08533777497190daa19d190d72b4cce9c7cb/zope_interface-8.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4bd01022d2e1bce4a4a4ed9549edb25393c92e607d7daa6deff843f1f68b479d", size = 207903, upload-time = "2025-09-25T05:58:21.671Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e1/9845ac3697f108d9a1af6912170c59a23732090bbfb35955fe77e5544955/zope_interface-8.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:29be8db8b712d94f1c05e24ea230a879271d787205ba1c9a6100d1d81f06c69a", size = 208345, upload-time = "2025-09-25T05:58:24.217Z" }, + { url = "https://files.pythonhosted.org/packages/f2/49/6573bc8b841cfab18e80c8e8259f1abdbbf716140011370de30231be79ad/zope_interface-8.0.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:51ae1b856565b30455b7879fdf0a56a88763b401d3f814fa9f9542d7410dbd7e", size = 255027, upload-time = "2025-09-25T05:58:19.975Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fd/908b0fd4b1ab6e412dfac9bd2b606f2893ef9ba3dd36d643f5e5b94c57b3/zope_interface-8.0.1-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d2e7596149cb1acd1d4d41b9f8fe2ffc0e9e29e2e91d026311814181d0d9efaf", size = 259800, upload-time = "2025-09-25T05:58:11.487Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/8419a2b4e88410520ed4b7f93bbd25a6d4ae66c4e2b131320f2b90f43077/zope_interface-8.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2737c11c34fb9128816759864752d007ec4f987b571c934c30723ed881a7a4f", size = 260978, upload-time = "2025-09-25T06:26:24.483Z" }, + { url = "https://files.pythonhosted.org/packages/e5/90/caf68152c292f1810e2bd3acd2177badf08a740aa8a348714617d6c9ad0b/zope_interface-8.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf66e4bf731aa7e0ced855bb3670e8cda772f6515a475c6a107bad5cb6604103", size = 212155, upload-time = "2025-09-25T05:59:40.318Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/0f08713ddda834c428ebf97b2a7fd8dea50c0100065a8955924dbd94dae8/zope_interface-8.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:115f27c1cc95ce7a517d960ef381beedb0a7ce9489645e80b9ab3cbf8a78799c", size = 208609, upload-time = "2025-09-25T05:58:53.698Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/d423045f54dc81e0991ec655041e7a0eccf6b2642535839dd364b35f4d7f/zope_interface-8.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af655c573b84e3cb6a4f6fd3fbe04e4dc91c63c6b6f99019b3713ef964e589bc", size = 208797, upload-time = "2025-09-25T05:58:56.258Z" }, + { url = "https://files.pythonhosted.org/packages/c6/43/39d4bb3f7a80ebd261446792493cfa4e198badd47107224f5b6fe1997ad9/zope_interface-8.0.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:23f82ef9b2d5370750cc1bf883c3b94c33d098ce08557922a3fbc7ff3b63dfe1", size = 259242, upload-time = "2025-09-25T05:58:21.602Z" }, + { url = "https://files.pythonhosted.org/packages/da/29/49effcff64ef30731e35520a152a9dfcafec86cf114b4c2aff942e8264ba/zope_interface-8.0.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35a1565d5244997f2e629c5c68715b3d9d9036e8df23c4068b08d9316dcb2822", size = 264696, upload-time = "2025-09-25T05:58:13.351Z" }, + { url = "https://files.pythonhosted.org/packages/c7/39/b947673ec9a258eeaa20208dd2f6127d9fbb3e5071272a674ebe02063a78/zope_interface-8.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:029ea1db7e855a475bf88d9910baab4e94d007a054810e9007ac037a91c67c6f", size = 264229, upload-time = "2025-09-25T06:26:26.226Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ee/eed6efd1fc3788d1bef7a814e0592d8173b7fe601c699b935009df035fc2/zope_interface-8.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0beb3e7f7dc153944076fcaf717a935f68d39efa9fce96ec97bafcc0c2ea6cab", size = 212270, upload-time = "2025-09-25T05:58:53.584Z" }, + { url = "https://files.pythonhosted.org/packages/5f/dc/3c12fca01c910c793d636ffe9c0984e0646abaf804e44552070228ed0ede/zope_interface-8.0.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:c7cc027fc5c61c5d69e5080c30b66382f454f43dc379c463a38e78a9c6bab71a", size = 208992, upload-time = "2025-09-25T05:58:40.712Z" }, + { url = "https://files.pythonhosted.org/packages/46/71/6127b7282a3e380ca927ab2b40778a9c97935a4a57a2656dadc312db5f30/zope_interface-8.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fcf9097ff3003b7662299f1c25145e15260ec2a27f9a9e69461a585d79ca8552", size = 209051, upload-time = "2025-09-25T05:58:42.182Z" }, + { url = "https://files.pythonhosted.org/packages/56/86/4387a9f951ee18b0e41fda77da77d59c33e59f04660578e2bad688703e64/zope_interface-8.0.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6d965347dd1fb9e9a53aa852d4ded46b41ca670d517fd54e733a6b6a4d0561c2", size = 259223, upload-time = "2025-09-25T05:58:23.191Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/ce60a114466abc067c68ed41e2550c655f551468ae17b4b17ea360090146/zope_interface-8.0.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a3b8bb77a4b89427a87d1e9eb969ab05e38e6b4a338a9de10f6df23c33ec3c2", size = 264690, upload-time = "2025-09-25T05:58:15.052Z" }, + { url = "https://files.pythonhosted.org/packages/36/9a/62a9ba3a919594605a07c34eee3068659bbd648e2fa0c4a86d876810b674/zope_interface-8.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:87e6b089002c43231fb9afec89268391bcc7a3b66e76e269ffde19a8112fb8d5", size = 264201, upload-time = "2025-09-25T06:26:27.797Z" }, + { url = "https://files.pythonhosted.org/packages/da/06/8fe88bd7edef60566d21ef5caca1034e10f6b87441ea85de4bbf9ea74768/zope_interface-8.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:64a43f5280aa770cbafd0307cb3d1ff430e2a1001774e8ceb40787abe4bb6658", size = 212273, upload-time = "2025-09-25T06:00:25.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/24/d5c5e7936e014276b7a98a076de4f5dc2587100fea95779c1e36650b8770/zope_interface-8.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84464a9fcf801289fa8b15bfc0829e7855d47fb4a8059555effc6f2d1d9a613", size = 207443, upload-time = "2025-09-25T05:59:34.299Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/565cf6db478ba344b27cfd6828f17da2888cf1beb521bce31142b3041fb5/zope_interface-8.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b915cf7e747b5356d741be79a153aa9107e8923bc93bcd65fc873caf0fb5c50", size = 207928, upload-time = "2025-09-25T05:59:35.524Z" }, + { url = "https://files.pythonhosted.org/packages/65/03/9780355205c3b3f55e9ce700e52846b40d0bab99c078e102c0d7e2f3e022/zope_interface-8.0.1-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:110c73ddf974b369ef3c6e7b0d87d44673cf4914eba3fe8a33bfb21c6c606ad8", size = 248605, upload-time = "2025-09-25T05:58:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/44/09/b10eda92f1373cd8e4e9dd376559414d1759a8b54e98eeef0d81844f0638/zope_interface-8.0.1-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9e9bdca901c1bcc34e438001718512c65b3b8924aabcd732b6e7a7f0cd715f17", size = 253793, upload-time = "2025-09-25T05:58:16.92Z" }, + { url = "https://files.pythonhosted.org/packages/62/37/3529065a2b6b7dc8f287ff3c8d1e8b7d8c4681c069e520bd3c4ac995a95c/zope_interface-8.0.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bbd22d4801ad3e8ec704ba9e3e6a4ac2e875e4d77e363051ccb76153d24c5519", size = 254263, upload-time = "2025-09-25T06:26:29.371Z" }, + { url = "https://files.pythonhosted.org/packages/c3/31/42588bea7ddad3abd2d4987ec3b767bd09394cc091a4918b1cf7b9de07ae/zope_interface-8.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:a0016ca85f93b938824e2f9a43534446e95134a2945b084944786e1ace2020bc", size = 211780, upload-time = "2025-09-25T05:59:50.015Z" }, +] + +[[package]] +name = "zope-interface" +version = "8.1.1" +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/71/c9/5ec8679a04d37c797d343f650c51ad67d178f0001c363e44b6ac5f97a9da/zope_interface-8.1.1.tar.gz", hash = "sha256:51b10e6e8e238d719636a401f44f1e366146912407b58453936b781a19be19ec", size = 254748, upload-time = "2025-11-15T08:32:52.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/ca/77df8f9bcbd8a5e29913c7fef14ff0aadac9448e78dc2606a85d7a23ec6c/zope_interface-8.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c6b12b656c7d7e3d79cad8e2afc4a37eae6b6076e2c209a33345143148e435e", size = 207415, upload-time = "2025-11-15T08:36:36.508Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1f/f1c7828ba3d9b34e65c7e498216fc37ca6e69f1ff1918cca37cd1d895682/zope_interface-8.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:557c0f1363c300db406e9eeaae8ab6d1ba429d4fed60d8ab7dadab5ca66ccd35", size = 207951, upload-time = "2025-11-15T08:36:38.468Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9e/e079035812f06fe1feede1bef753f537fb33d5480d05107f65a51d94e7b3/zope_interface-8.1.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:127b0e4c873752b777721543cf8525b3db5e76b88bd33bab807f03c568e9003f", size = 249409, upload-time = "2025-11-15T08:36:40.148Z" }, + { url = "https://files.pythonhosted.org/packages/c2/09/b7f5a33bd3a17efb31e9e14496e600ab550ab0e38829dcda8a73f017fbfe/zope_interface-8.1.1-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e0892c9d2dd47b45f62d1861bcae8b427fcc49b4a04fff67f12c5c55e56654d7", size = 254527, upload-time = "2025-11-15T08:36:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/2a/90/0eecd1eab6b62d296dff8445f051e4aa6bd91b67d71cfe9ff9d270b64dbe/zope_interface-8.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff8a92dc8c8a2c605074e464984e25b9b5a8ac9b2a0238dd73a0f374df59a77e", size = 254963, upload-time = "2025-11-15T08:36:42.708Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ff/2fe84fadd13e8adb7b2fb542311c27bad15881be26e37f403df3d0279c74/zope_interface-8.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:54627ddf6034aab1f506ba750dd093f67d353be6249467d720e9f278a578efe5", size = 211809, upload-time = "2025-11-15T08:36:44.43Z" }, + { url = "https://files.pythonhosted.org/packages/77/fc/d84bac27332bdefe8c03f7289d932aeb13a5fd6aeedba72b0aa5b18276ff/zope_interface-8.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e8a0fdd5048c1bb733e4693eae9bc4145a19419ea6a1c95299318a93fe9f3d72", size = 207955, upload-time = "2025-11-15T08:36:45.902Z" }, + { url = "https://files.pythonhosted.org/packages/52/02/e1234eb08b10b5cf39e68372586acc7f7bbcd18176f6046433a8f6b8b263/zope_interface-8.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a4cb0ea75a26b606f5bc8524fbce7b7d8628161b6da002c80e6417ce5ec757c0", size = 208398, upload-time = "2025-11-15T08:36:47.016Z" }, + { url = "https://files.pythonhosted.org/packages/3c/be/aabda44d4bc490f9966c2b77fa7822b0407d852cb909b723f2d9e05d2427/zope_interface-8.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c267b00b5a49a12743f5e1d3b4beef45479d696dab090f11fe3faded078a5133", size = 255079, upload-time = "2025-11-15T08:36:48.157Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7f/4fbc7c2d7cb310e5a91b55db3d98e98d12b262014c1fcad9714fe33c2adc/zope_interface-8.1.1-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e25d3e2b9299e7ec54b626573673bdf0d740cf628c22aef0a3afef85b438aa54", size = 259850, upload-time = "2025-11-15T08:36:49.544Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2c/dc573fffe59cdbe8bbbdd2814709bdc71c4870893e7226700bc6a08c5e0c/zope_interface-8.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:63db1241804417aff95ac229c13376c8c12752b83cc06964d62581b493e6551b", size = 261033, upload-time = "2025-11-15T08:36:51.061Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/1ac50e5ee933d9e3902f3400bda399c128a5c46f9f209d16affe3d4facc5/zope_interface-8.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:9639bf4ed07b5277fb231e54109117c30d608254685e48a7104a34618bcbfc83", size = 212215, upload-time = "2025-11-15T08:36:52.553Z" }, + { url = "https://files.pythonhosted.org/packages/08/3d/f5b8dd2512f33bfab4faba71f66f6873603d625212206dd36f12403ae4ca/zope_interface-8.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a16715808408db7252b8c1597ed9008bdad7bf378ed48eb9b0595fad4170e49d", size = 208660, upload-time = "2025-11-15T08:36:53.579Z" }, + { url = "https://files.pythonhosted.org/packages/e5/41/c331adea9b11e05ff9ac4eb7d3032b24c36a3654ae9f2bf4ef2997048211/zope_interface-8.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce6b58752acc3352c4aa0b55bbeae2a941d61537e6afdad2467a624219025aae", size = 208851, upload-time = "2025-11-15T08:36:54.854Z" }, + { url = "https://files.pythonhosted.org/packages/25/00/7a8019c3bb8b119c5f50f0a4869183a4b699ca004a7f87ce98382e6b364c/zope_interface-8.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:807778883d07177713136479de7fd566f9056a13aef63b686f0ab4807c6be259", size = 259292, upload-time = "2025-11-15T08:36:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fc/b70e963bf89345edffdd5d16b61e789fdc09365972b603e13785360fea6f/zope_interface-8.1.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50e5eb3b504a7d63dc25211b9298071d5b10a3eb754d6bf2f8ef06cb49f807ab", size = 264741, upload-time = "2025-11-15T08:36:57.675Z" }, + { url = "https://files.pythonhosted.org/packages/96/fe/7d0b5c0692b283901b34847f2b2f50d805bfff4b31de4021ac9dfb516d2a/zope_interface-8.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eee6f93b2512ec9466cf30c37548fd3ed7bc4436ab29cd5943d7a0b561f14f0f", size = 264281, upload-time = "2025-11-15T08:36:58.968Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2c/a7cebede1cf2757be158bcb151fe533fa951038cfc5007c7597f9f86804b/zope_interface-8.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:80edee6116d569883c58ff8efcecac3b737733d646802036dc337aa839a5f06b", size = 212327, upload-time = "2025-11-15T08:37:00.4Z" }, + { url = "https://files.pythonhosted.org/packages/85/81/3c3b5386ce4fba4612fd82ffb8a90d76bcfea33ca2b6399f21e94d38484f/zope_interface-8.1.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:84f9be6d959640de9da5d14ac1f6a89148b16da766e88db37ed17e936160b0b1", size = 209046, upload-time = "2025-11-15T08:37:01.473Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e3/32b7cb950c4c4326b3760a8e28e5d6f70ad15f852bfd8f9364b58634f74b/zope_interface-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:531fba91dcb97538f70cf4642a19d6574269460274e3f6004bba6fe684449c51", size = 209104, upload-time = "2025-11-15T08:37:02.887Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3d/c4c68e1752a5f5effa2c1f5eaa4fea4399433c9b058fb7000a34bfb1c447/zope_interface-8.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:fc65f5633d5a9583ee8d88d1f5de6b46cd42c62e47757cfe86be36fb7c8c4c9b", size = 259277, upload-time = "2025-11-15T08:37:04.389Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5b/cf4437b174af7591ee29bbad728f620cab5f47bd6e9c02f87d59f31a0dda/zope_interface-8.1.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efef80ddec4d7d99618ef71bc93b88859248075ca2e1ae1c78636654d3d55533", size = 264742, upload-time = "2025-11-15T08:37:05.613Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0e/0cf77356862852d3d3e62db9aadae5419a1a7d89bf963b219745283ab5ca/zope_interface-8.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49aad83525eca3b4747ef51117d302e891f0042b06f32aa1c7023c62642f962b", size = 264252, upload-time = "2025-11-15T08:37:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/2af54aa88b2fa172d12364116cc40d325fedbb1877c3bb031b0da6052855/zope_interface-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:71cf329a21f98cb2bd9077340a589e316ac8a415cac900575a32544b3dffcb98", size = 212330, upload-time = "2025-11-15T08:37:08.14Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f5/44efbd98ba06cb937fce7a69fcd7a78c4ac7aa4e1ad2125536801376d2d0/zope_interface-8.1.1-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:da311e9d253991ca327601f47c4644d72359bac6950fbb22f971b24cd7850f8c", size = 209099, upload-time = "2025-11-15T08:37:09.395Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a19866c09c8485c36a4c6908e1dd3f8820b41c1ee333c291157cf4cf09e7/zope_interface-8.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3fb25fca0442c7fb93c4ee40b42e3e033fef2f648730c4b7ae6d43222a3e8946", size = 209240, upload-time = "2025-11-15T08:37:10.687Z" }, + { url = "https://files.pythonhosted.org/packages/c1/28/0dbf40db772d779a4ac8d006a57ad60936d42ad4769a3d5410dcfb98f6f9/zope_interface-8.1.1-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:bac588d0742b4e35efb7c7df1dacc0397b51ed37a17d4169a38019a1cebacf0a", size = 260919, upload-time = "2025-11-15T08:37:11.838Z" }, + { url = "https://files.pythonhosted.org/packages/72/ae/650cd4c01dd1b32c26c800b2c4d852f044552c34a56fbb74d41f569cee31/zope_interface-8.1.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d1f053d2d5e2b393e619bce1e55954885c2e63969159aa521839e719442db49", size = 264102, upload-time = "2025-11-15T08:37:13.241Z" }, + { url = "https://files.pythonhosted.org/packages/46/f0/f534a2c34c006aa090c593cd70eaf94e259fd0786f934698d81f0534d907/zope_interface-8.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:64a1ad7f4cb17d948c6bdc525a1d60c0e567b2526feb4fa38b38f249961306b8", size = 264276, upload-time = "2025-11-15T08:37:14.369Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a8/d7e9cf03067b767e23908dbab5f6be7735d70cb4818311a248a8c4bb23cc/zope_interface-8.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:169214da1b82b7695d1a36f92d70b11166d66b6b09d03df35d150cc62ac52276", size = 212492, upload-time = "2025-11-15T08:37:15.538Z" }, ] From 5b13ae006a5786f2ae2c3669d39251fc61c638e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 07:41:08 -0600 Subject: [PATCH 048/129] Bump github/codeql-action from 4.31.8 to 4.31.9 in the actions group (#2660) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 55dd016a87..47eafe4303 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -63,6 +63,6 @@ jobs: pip install -e . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 with: category: "/language:${{matrix.language}}" From 6ccaae5772933b62c2fa0530529c0f4845fb59cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 10:23:11 -0500 Subject: [PATCH 049/129] Bump furo from 2025.9.25 to 2025.12.19 (#2661) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- requirements/docs.txt | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/docs.txt b/requirements/docs.txt index 54ebf3625d..e05f27de3a 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -3,4 +3,4 @@ sphinx_rtd_theme>=2,<4 readthedocs-sphinx-search~=0.3 sphinxcontrib-shellcheck>=1,<2 sphinx-autobuild>=2020.9.1 -furo==2025.9.25 +furo==2025.12.19 diff --git a/uv.lock b/uv.lock index bba8df9386..896cf7b8fe 100644 --- a/uv.lock +++ b/uv.lock @@ -844,7 +844,7 @@ wheels = [ [[package]] name = "furo" -version = "2025.9.25" +version = "2025.12.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, @@ -855,9 +855,9 @@ dependencies = [ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-basic-ng" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/29/ff3b83a1ffce74676043ab3e7540d398e0b1ce7660917a00d7c4958b93da/furo-2025.9.25.tar.gz", hash = "sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98", size = 1662007, upload-time = "2025-09-25T21:37:19.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/69/964b55f389c289e16ba2a5dfe587c3c462aac09e24123f09ddf703889584/furo-2025.9.25-py3-none-any.whl", hash = "sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe", size = 340409, upload-time = "2025-09-25T21:37:17.244Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, ] [[package]] @@ -1590,7 +1590,7 @@ requires-dist = [ { name = "certifi", marker = "(os_name == 'nt' and extra == 'ocsp') or (sys_platform == 'darwin' and extra == 'ocsp')", specifier = ">=2023.7.22" }, { name = "cryptography", marker = "extra == 'ocsp'", specifier = ">=42.0.0" }, { name = "dnspython", specifier = ">=2.6.1,<3.0.0" }, - { name = "furo", marker = "extra == 'docs'", specifier = "==2025.9.25" }, + { name = "furo", marker = "extra == 'docs'", specifier = "==2025.12.19" }, { name = "importlib-metadata", marker = "python_full_version < '3.13' and extra == 'test'", specifier = ">=7.0" }, { name = "pykerberos", marker = "os_name != 'nt' and extra == 'gssapi'", specifier = ">=1.2.4" }, { name = "pymongo-auth-aws", marker = "extra == 'aws'", specifier = ">=1.1.0,<2.0.0" }, From 18c1f142b5b3fd179a677b2736332dc17770b7c2 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 23 Dec 2025 06:43:32 -0600 Subject: [PATCH 050/129] PYTHON-5529 Introduce optin setting to await for MinPoolSize population (#2664) --- test/asynchronous/test_client.py | 2 +- test/asynchronous/unified_format.py | 14 ++- test/asynchronous/utils.py | 6 +- test/csot/command-execution.json | 14 ++- test/csot/convenient-transactions.json | 6 +- test/csot/error-transformations.json | 6 +- test/csot/global-timeoutMS.json | 130 ++++++++++++++++------ test/csot/non-tailable-cursors.json | 6 +- test/csot/retryability-timeoutMS.json | 6 +- test/csot/runCursorCommand.json | 6 +- test/csot/sessions-inherit-timeoutMS.json | 6 +- test/test_client.py | 2 +- test/unified_format.py | 14 ++- test/utils.py | 6 +- 14 files changed, 164 insertions(+), 60 deletions(-) diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index 37fb239237..5511765bae 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -2398,7 +2398,7 @@ def test_gevent_timeout_when_creating_connection(self): client = self.async_rs_or_single_client() self.addCleanup(client.close) coll = client.pymongo_test.test - pool = async_get_pool(client) + pool = async_get_pool(client) # type:ignore # Patch the pool to delay the connect method. def delayed_connect(*args, **kwargs): diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index bca259b826..be2372f224 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -329,6 +329,17 @@ async def _create_entity(self, entity_spec, uri=None): kwargs["h"] = uri client = await self.test.async_rs_or_single_client(**kwargs) await client.aconnect() + # Wait for pool to be populated. + if "awaitMinPoolSizeMS" in spec: + pool = await async_get_pool(client) + t0 = time.monotonic() + while True: + if (time.monotonic() - t0) > spec["awaitMinPoolSizeMS"] * 1000: + raise ValueError("Test timed out during awaitMinPoolSize") + async with pool.lock: + if len(pool.conns) + pool.active_sockets >= pool.opts.min_pool_size: + break + await asyncio.sleep(0.1) self[spec["id"]] = client return elif entity_type == "database": @@ -463,7 +474,7 @@ class UnifiedSpecTestMixinV1(AsyncIntegrationTest): a class attribute ``TEST_SPEC``. """ - SCHEMA_VERSION = Version.from_string("1.25") + SCHEMA_VERSION = Version.from_string("1.26") RUN_ON_LOAD_BALANCER = True TEST_SPEC: Any TEST_PATH = "" # This gets filled in by generate_test_classes @@ -1551,7 +1562,6 @@ async def test_case(self): if re.search(fail_pattern, description): test_method = unittest.expectedFailure(test_method) break - setattr(cls, test_name, test_method) diff --git a/test/asynchronous/utils.py b/test/asynchronous/utils.py index 02ba46c71a..9021b06082 100644 --- a/test/asynchronous/utils.py +++ b/test/asynchronous/utils.py @@ -28,25 +28,25 @@ from bson.son import SON from pymongo import AsyncMongoClient +from pymongo.asynchronous.pool import Pool, _CancellationContext, _PoolGeneration from pymongo.errors import ConfigurationError from pymongo.hello import HelloCompat from pymongo.lock import _async_create_lock from pymongo.operations import _Op from pymongo.read_preferences import ReadPreference from pymongo.server_selectors import any_server_selector, writable_server_selector -from pymongo.synchronous.pool import _CancellationContext, _PoolGeneration _IS_SYNC = False -async def async_get_pool(client): +async def async_get_pool(client: AsyncMongoClient) -> Pool: """Get the standalone, primary, or mongos pool.""" topology = await client._get_topology() server = await topology._select_server(writable_server_selector, _Op.TEST) return server.pool -async def async_get_pools(client): +async def async_get_pools(client: AsyncMongoClient) -> list[Pool]: """Get all pools.""" return [ server.pool diff --git a/test/csot/command-execution.json b/test/csot/command-execution.json index aa9c3eb23f..212cd41089 100644 --- a/test/csot/command-execution.json +++ b/test/csot/command-execution.json @@ -1,6 +1,6 @@ { "description": "timeoutMS behaves correctly during command execution", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.4.7", @@ -69,8 +69,10 @@ "appName": "reduceMaxTimeMSTest", "w": 1, "timeoutMS": 500, - "heartbeatFrequencyMS": 500 + "heartbeatFrequencyMS": 500, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "observeEvents": [ "commandStartedEvent" ] @@ -185,8 +187,10 @@ "appName": "rttTooHighTest", "w": 1, "timeoutMS": 10, - "heartbeatFrequencyMS": 500 + "heartbeatFrequencyMS": 500, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "observeEvents": [ "commandStartedEvent" ] @@ -316,8 +320,10 @@ "appName": "reduceMaxTimeMSTest", "w": 1, "timeoutMS": 90, - "heartbeatFrequencyMS": 100000 + "heartbeatFrequencyMS": 100000, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "observeEvents": [ "commandStartedEvent" ] diff --git a/test/csot/convenient-transactions.json b/test/csot/convenient-transactions.json index 3868b3026c..f9d03429db 100644 --- a/test/csot/convenient-transactions.json +++ b/test/csot/convenient-transactions.json @@ -1,6 +1,6 @@ { "description": "timeoutMS behaves correctly for the withTransaction API", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.4", @@ -21,8 +21,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 500 + "timeoutMS": 500, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" diff --git a/test/csot/error-transformations.json b/test/csot/error-transformations.json index 4889e39583..89be49f0a4 100644 --- a/test/csot/error-transformations.json +++ b/test/csot/error-transformations.json @@ -1,6 +1,6 @@ { "description": "MaxTimeMSExpired server errors are transformed into a custom timeout error", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.0", @@ -26,8 +26,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" diff --git a/test/csot/global-timeoutMS.json b/test/csot/global-timeoutMS.json index f1edbe68e3..9d8046d1bf 100644 --- a/test/csot/global-timeoutMS.json +++ b/test/csot/global-timeoutMS.json @@ -1,6 +1,6 @@ { "description": "timeoutMS can be configured on a MongoClient", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.4", @@ -38,8 +38,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -217,8 +219,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -390,8 +394,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -569,8 +575,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -762,8 +770,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -941,8 +951,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -1120,8 +1132,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -1305,8 +1319,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -1484,8 +1500,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -1663,8 +1681,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -1842,8 +1862,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -2021,8 +2043,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -2194,8 +2218,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -2375,8 +2401,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -2554,8 +2582,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -2733,8 +2763,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -2906,8 +2938,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -3079,8 +3113,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -3258,8 +3294,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -3441,8 +3479,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -3628,8 +3668,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -3807,8 +3849,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -3986,8 +4030,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -4171,8 +4217,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -4360,8 +4408,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -4549,8 +4599,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -4728,8 +4780,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -4913,8 +4967,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -5102,8 +5158,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -5297,8 +5355,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -5482,8 +5542,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" @@ -5677,8 +5739,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 250 + "timeoutMS": 250, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" diff --git a/test/csot/non-tailable-cursors.json b/test/csot/non-tailable-cursors.json index 291c6e72aa..58c59cb32d 100644 --- a/test/csot/non-tailable-cursors.json +++ b/test/csot/non-tailable-cursors.json @@ -1,6 +1,6 @@ { "description": "timeoutMS behaves correctly for non-tailable cursors", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.4" @@ -17,8 +17,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 200 + "timeoutMS": 200, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" diff --git a/test/csot/retryability-timeoutMS.json b/test/csot/retryability-timeoutMS.json index 9daad260ef..5a0c9f3605 100644 --- a/test/csot/retryability-timeoutMS.json +++ b/test/csot/retryability-timeoutMS.json @@ -1,6 +1,6 @@ { "description": "timeoutMS behaves correctly for retryable operations", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.0", @@ -26,8 +26,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 100 + "timeoutMS": 100, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent" diff --git a/test/csot/runCursorCommand.json b/test/csot/runCursorCommand.json index 36f774fb5a..e5182e338e 100644 --- a/test/csot/runCursorCommand.json +++ b/test/csot/runCursorCommand.json @@ -1,6 +1,6 @@ { "description": "runCursorCommand", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.4" @@ -16,6 +16,10 @@ { "client": { "id": "commandClient", + "uriOptions": { + "minPoolSize": 1 + }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent", diff --git a/test/csot/sessions-inherit-timeoutMS.json b/test/csot/sessions-inherit-timeoutMS.json index 13ea91c794..dbf163e484 100644 --- a/test/csot/sessions-inherit-timeoutMS.json +++ b/test/csot/sessions-inherit-timeoutMS.json @@ -1,6 +1,6 @@ { "description": "sessions inherit timeoutMS from their parent MongoClient", - "schemaVersion": "1.9", + "schemaVersion": "1.26", "runOnRequirements": [ { "minServerVersion": "4.4", @@ -21,8 +21,10 @@ "client": { "id": "client", "uriOptions": { - "timeoutMS": 500 + "timeoutMS": 500, + "minPoolSize": 1 }, + "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent", diff --git a/test/test_client.py b/test/test_client.py index 2d30eb1bd2..737b3afe60 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -2353,7 +2353,7 @@ def test_gevent_timeout_when_creating_connection(self): client = self.rs_or_single_client() self.addCleanup(client.close) coll = client.pymongo_test.test - pool = get_pool(client) + pool = get_pool(client) # type:ignore # Patch the pool to delay the connect method. def delayed_connect(*args, **kwargs): diff --git a/test/unified_format.py b/test/unified_format.py index 06c6abd004..9e3dc1fed0 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -328,6 +328,17 @@ def _create_entity(self, entity_spec, uri=None): kwargs["h"] = uri client = self.test.rs_or_single_client(**kwargs) client._connect() + # Wait for pool to be populated. + if "awaitMinPoolSizeMS" in spec: + pool = get_pool(client) + t0 = time.monotonic() + while True: + if (time.monotonic() - t0) > spec["awaitMinPoolSizeMS"] * 1000: + raise ValueError("Test timed out during awaitMinPoolSize") + with pool.lock: + if len(pool.conns) + pool.active_sockets >= pool.opts.min_pool_size: + break + time.sleep(0.1) self[spec["id"]] = client return elif entity_type == "database": @@ -462,7 +473,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest): a class attribute ``TEST_SPEC``. """ - SCHEMA_VERSION = Version.from_string("1.25") + SCHEMA_VERSION = Version.from_string("1.26") RUN_ON_LOAD_BALANCER = True TEST_SPEC: Any TEST_PATH = "" # This gets filled in by generate_test_classes @@ -1536,7 +1547,6 @@ def test_case(self): if re.search(fail_pattern, description): test_method = unittest.expectedFailure(test_method) break - setattr(cls, test_name, test_method) diff --git a/test/utils.py b/test/utils.py index bfc606fe83..7b5a4c8214 100644 --- a/test/utils.py +++ b/test/utils.py @@ -34,19 +34,19 @@ from pymongo.operations import _Op from pymongo.read_preferences import ReadPreference from pymongo.server_selectors import any_server_selector, writable_server_selector -from pymongo.synchronous.pool import _CancellationContext, _PoolGeneration +from pymongo.synchronous.pool import Pool, _CancellationContext, _PoolGeneration _IS_SYNC = True -def get_pool(client): +def get_pool(client: MongoClient) -> Pool: """Get the standalone, primary, or mongos pool.""" topology = client._get_topology() server = topology._select_server(writable_server_selector, _Op.TEST) return server.pool -def get_pools(client): +def get_pools(client: MongoClient) -> list[Pool]: """Get all pools.""" return [ server.pool From 130067799cc671d4dcc2f34dafff73c4ef5ddeaf Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:59:06 -0600 Subject: [PATCH 051/129] [Spec Resync] 12-22-2025 (#2663) Co-authored-by: Cloud User Co-authored-by: Steven Silvester --- .evergreen/remove-unimplemented-tests.sh | 4 + .evergreen/spec-patch/PYTHON-5517.patch | 444 +----------------- .../pool-create-min-size-error.json | 2 +- .../unified/minPoolSize-error.json | 5 +- test/load_balancer/sdam-error-handling.json | 4 +- 5 files changed, 11 insertions(+), 448 deletions(-) diff --git a/.evergreen/remove-unimplemented-tests.sh b/.evergreen/remove-unimplemented-tests.sh index 9e049de318..d81713794d 100755 --- a/.evergreen/remove-unimplemented-tests.sh +++ b/.evergreen/remove-unimplemented-tests.sh @@ -7,6 +7,7 @@ rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-application-error.j rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-checkout-error.json # PYTHON-4918 rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-min-pool-size-error.json # PYTHON-4918 rm $PYMONGO/test/client-side-encryption/spec/unified/client-bulkWrite-qe.json # PYTHON-4929 +rm $PYMONGO/test/sessions/snapshot-sessions.json # PYTHON-5559 # Python doesn't implement DRIVERS-3064 rm $PYMONGO/test/collection_management/listCollections-rawdata.json @@ -50,4 +51,7 @@ rm $PYMONGO/test/csot/override-database-timeoutMS.json # PYTHON-2943 - Socks5 Proxy Support rm $PYMONGO/test/uri_options/proxy-options.json +# PYTHON-5517 - Avoid clearing the connection pool when the server connection rate limiter triggers +rm $PYMONGO/test/discovery_and_monitoring/unified/backpressure-*.json + echo "Done removing unimplemented tests" diff --git a/.evergreen/spec-patch/PYTHON-5517.patch b/.evergreen/spec-patch/PYTHON-5517.patch index bc2e194271..adf453fd1f 100644 --- a/.evergreen/spec-patch/PYTHON-5517.patch +++ b/.evergreen/spec-patch/PYTHON-5517.patch @@ -1,447 +1,5 @@ -diff --git a/test/connection_monitoring/pool-create-min-size-error.json b/test/connection_monitoring/pool-create-min-size-error.json -index 1c744b85..5c8ad02d 100644 ---- a/test/connection_monitoring/pool-create-min-size-error.json -+++ b/test/connection_monitoring/pool-create-min-size-error.json -@@ -17,7 +17,7 @@ - "isMaster", - "hello" - ], -- "closeConnection": true, -+ "errorCode": 91, - "appName": "poolCreateMinSizeErrorTest" - } - }, -diff --git a/test/discovery_and_monitoring/unified/backpressure-network-error-fail.json b/test/discovery_and_monitoring/unified/backpressure-network-error-fail.json -new file mode 100644 -index 00000000..f41b7645 ---- /dev/null -+++ b/test/discovery_and_monitoring/unified/backpressure-network-error-fail.json -@@ -0,0 +1,140 @@ -+{ -+ "description": "backpressure-network-error-fail", -+ "schemaVersion": "1.17", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "serverless": "forbid", -+ "topologies": [ -+ "single", -+ "replicaset", -+ "sharded" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "setupClient", -+ "useMultipleMongoses": false -+ } -+ } -+ ], -+ "initialData": [ -+ { -+ "collectionName": "backpressure-network-error-fail", -+ "databaseName": "sdam-tests", -+ "documents": [ -+ { -+ "_id": 1 -+ }, -+ { -+ "_id": 2 -+ } -+ ] -+ } -+ ], -+ "tests": [ -+ { -+ "description": "apply backpressure on network connection errors during connection establishment", -+ "operations": [ -+ { -+ "name": "createEntities", -+ "object": "testRunner", -+ "arguments": { -+ "entities": [ -+ { -+ "client": { -+ "id": "client", -+ "useMultipleMongoses": false, -+ "observeEvents": [ -+ "serverHeartbeatSucceededEvent", -+ "poolClearedEvent" -+ ], -+ "uriOptions": { -+ "retryWrites": false, -+ "heartbeatFrequencyMS": 1000000, -+ "serverMonitoringMode": "poll", -+ "appname": "backpressureNetworkErrorFailTest" -+ } -+ } -+ }, -+ { -+ "database": { -+ "id": "database", -+ "client": "client", -+ "databaseName": "sdam-tests" -+ } -+ }, -+ { -+ "collection": { -+ "id": "collection", -+ "database": "database", -+ "collectionName": "backpressure-network-error-fail" -+ } -+ } -+ ] -+ } -+ }, -+ { -+ "name": "waitForEvent", -+ "object": "testRunner", -+ "arguments": { -+ "client": "client", -+ "event": { -+ "serverHeartbeatSucceededEvent": {} -+ }, -+ "count": 1 -+ } -+ }, -+ { -+ "name": "failPoint", -+ "object": "testRunner", -+ "arguments": { -+ "client": "setupClient", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": "alwaysOn", -+ "data": { -+ "failCommands": [ -+ "isMaster", -+ "hello" -+ ], -+ "appName": "backpressureNetworkErrorFailTest", -+ "closeConnection": true -+ } -+ } -+ } -+ }, -+ { -+ "name": "insertMany", -+ "object": "collection", -+ "arguments": { -+ "documents": [ -+ { -+ "_id": 3 -+ }, -+ { -+ "_id": 4 -+ } -+ ] -+ }, -+ "expectError": { -+ "isError": true, -+ "errorLabelsContain": [ -+ "SystemOverloadedError", -+ "RetryableError" -+ ] -+ } -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client", -+ "eventType": "cmap", -+ "events": [] -+ } -+ ] -+ } -+ ] -+} -diff --git a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail.json b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail.json -new file mode 100644 -index 00000000..a97c7a32 ---- /dev/null -+++ b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail.json -@@ -0,0 +1,143 @@ -+{ -+ "description": "backpressure-network-timeout-error", -+ "schemaVersion": "1.17", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "serverless": "forbid", -+ "topologies": [ -+ "single", -+ "replicaset", -+ "sharded" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "setupClient", -+ "useMultipleMongoses": false -+ } -+ } -+ ], -+ "initialData": [ -+ { -+ "collectionName": "backpressure-network-timeout-error", -+ "databaseName": "sdam-tests", -+ "documents": [ -+ { -+ "_id": 1 -+ }, -+ { -+ "_id": 2 -+ } -+ ] -+ } -+ ], -+ "tests": [ -+ { -+ "description": "apply backpressure on network timeout error during connection establishment", -+ "operations": [ -+ { -+ "name": "createEntities", -+ "object": "testRunner", -+ "arguments": { -+ "entities": [ -+ { -+ "client": { -+ "id": "client", -+ "useMultipleMongoses": false, -+ "observeEvents": [ -+ "serverDescriptionChangedEvent", -+ "poolClearedEvent" -+ ], -+ "uriOptions": { -+ "retryWrites": false, -+ "heartbeatFrequencyMS": 1000000, -+ "appname": "backpressureNetworkTimeoutErrorTest", -+ "serverMonitoringMode": "poll", -+ "connectTimeoutMS": 250, -+ "socketTimeoutMS": 250 -+ } -+ } -+ }, -+ { -+ "database": { -+ "id": "database", -+ "client": "client", -+ "databaseName": "sdam-tests" -+ } -+ }, -+ { -+ "collection": { -+ "id": "collection", -+ "database": "database", -+ "collectionName": "backpressure-network-timeout-error" -+ } -+ } -+ ] -+ } -+ }, -+ { -+ "name": "waitForEvent", -+ "object": "testRunner", -+ "arguments": { -+ "client": "client", -+ "event": { -+ "serverDescriptionChangedEvent": {} -+ }, -+ "count": 1 -+ } -+ }, -+ { -+ "name": "failPoint", -+ "object": "testRunner", -+ "arguments": { -+ "client": "setupClient", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": "alwaysOn", -+ "data": { -+ "failCommands": [ -+ "isMaster", -+ "hello" -+ ], -+ "blockConnection": true, -+ "blockTimeMS": 500, -+ "appName": "backpressureNetworkTimeoutErrorTest" -+ } -+ } -+ } -+ }, -+ { -+ "name": "insertMany", -+ "object": "collection", -+ "arguments": { -+ "documents": [ -+ { -+ "_id": 3 -+ }, -+ { -+ "_id": 4 -+ } -+ ] -+ }, -+ "expectError": { -+ "isError": true, -+ "errorLabelsContain": [ -+ "SystemOverloadedError", -+ "RetryableError" -+ ] -+ } -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client", -+ "eventType": "cmap", -+ "events": [] -+ } -+ ] -+ } -+ ] -+} -diff --git a/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json -new file mode 100644 -index 00000000..35a49c13 ---- /dev/null -+++ b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json -@@ -0,0 +1,106 @@ -+{ -+ "description": "backpressure-server-description-unchanged-on-min-pool-size-population-error", -+ "schemaVersion": "1.17", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "serverless": "forbid", -+ "topologies": [ -+ "single" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "setupClient", -+ "useMultipleMongoses": false -+ } -+ } -+ ], -+ "tests": [ -+ { -+ "description": "the server description is not changed on handshake error during minPoolSize population", -+ "operations": [ -+ { -+ "name": "createEntities", -+ "object": "testRunner", -+ "arguments": { -+ "entities": [ -+ { -+ "client": { -+ "id": "client", -+ "observeEvents": [ -+ "serverDescriptionChangedEvent", -+ "connectionClosedEvent" -+ ], -+ "uriOptions": { -+ "appname": "authErrorTest", -+ "minPoolSize": 5, -+ "maxConnecting": 1, -+ "serverMonitoringMode": "poll", -+ "heartbeatFrequencyMS": 1000000 -+ } -+ } -+ } -+ ] -+ } -+ }, -+ { -+ "name": "failPoint", -+ "object": "testRunner", -+ "arguments": { -+ "client": "setupClient", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": { -+ "skip": 1 -+ }, -+ "data": { -+ "failCommands": [ -+ "hello", -+ "isMaster" -+ ], -+ "appName": "authErrorTest", -+ "closeConnection": true -+ } -+ } -+ } -+ }, -+ { -+ "name": "waitForEvent", -+ "object": "testRunner", -+ "arguments": { -+ "client": "client", -+ "event": { -+ "serverDescriptionChangedEvent": {} -+ }, -+ "count": 1 -+ } -+ }, -+ { -+ "name": "waitForEvent", -+ "object": "testRunner", -+ "arguments": { -+ "client": "client", -+ "event": { -+ "connectionClosedEvent": {} -+ }, -+ "count": 1 -+ } -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client", -+ "eventType": "sdam", -+ "events": [ -+ { -+ "serverDescriptionChangedEvent": {} -+ } -+ ] -+ } -+ ] -+ } -+ ] -+} -diff --git a/test/load_balancer/sdam-error-handling.json b/test/load_balancer/sdam-error-handling.json -index 5892dcac..41bc90be 100644 ---- a/test/load_balancer/sdam-error-handling.json -+++ b/test/load_balancer/sdam-error-handling.json -@@ -282,7 +282,7 @@ - "isMaster", - "hello" - ], -- "closeConnection": true, -+ "errorCode": 11600, - "appName": "lbSDAMErrorTestClient" - } - } -@@ -297,7 +297,7 @@ - } - }, - "expectError": { -- "isClientError": true -+ "isError": true - } - } - ], diff --git a/test/discovery_and_monitoring/errors/error_handling_handshake.json b/test/discovery_and_monitoring/errors/error_handling_handshake.json -index 56ca7d11..bf83f46f 100644 +index 56ca7d113..bf83f46f6 100644 --- a/test/discovery_and_monitoring/errors/error_handling_handshake.json +++ b/test/discovery_and_monitoring/errors/error_handling_handshake.json @@ -97,14 +97,22 @@ diff --git a/test/connection_monitoring/pool-create-min-size-error.json b/test/connection_monitoring/pool-create-min-size-error.json index 1c744b850c..5c8ad02dbd 100644 --- a/test/connection_monitoring/pool-create-min-size-error.json +++ b/test/connection_monitoring/pool-create-min-size-error.json @@ -17,7 +17,7 @@ "isMaster", "hello" ], - "closeConnection": true, + "errorCode": 91, "appName": "poolCreateMinSizeErrorTest" } }, diff --git a/test/discovery_and_monitoring/unified/minPoolSize-error.json b/test/discovery_and_monitoring/unified/minPoolSize-error.json index bd9e9fcdec..0ce2bdd6c1 100644 --- a/test/discovery_and_monitoring/unified/minPoolSize-error.json +++ b/test/discovery_and_monitoring/unified/minPoolSize-error.json @@ -27,7 +27,7 @@ ], "tests": [ { - "description": "Network error on minPoolSize background creation", + "description": "Server error on minPoolSize background creation", "operations": [ { "name": "failPoint", @@ -45,7 +45,7 @@ "isMaster" ], "appName": "SDAMminPoolSizeError", - "closeConnection": true + "errorCode": 91 } } } @@ -68,6 +68,7 @@ "heartbeatFrequencyMS": 10000, "appname": "SDAMminPoolSizeError", "minPoolSize": 10, + "serverMonitoringMode": "poll", "serverSelectionTimeoutMS": 1000 } } diff --git a/test/load_balancer/sdam-error-handling.json b/test/load_balancer/sdam-error-handling.json index 5892dcacd6..41bc90be7f 100644 --- a/test/load_balancer/sdam-error-handling.json +++ b/test/load_balancer/sdam-error-handling.json @@ -282,7 +282,7 @@ "isMaster", "hello" ], - "closeConnection": true, + "errorCode": 11600, "appName": "lbSDAMErrorTestClient" } } @@ -297,7 +297,7 @@ } }, "expectError": { - "isClientError": true + "isError": true } } ], From 10dd20405ba19c643a1b305ff84f742b19c40daf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:20:52 -0500 Subject: [PATCH 052/129] Update coverage[toml] requirement from <=7.10.6,>=5 to >=5,<=7.10.7 (#2662) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester Co-authored-by: Casey Clements --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 37b6d6db23..12af7c6f63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ pip = ["pip>=20.2"] gevent = ["gevent>=21.12"] coverage = [ "pytest-cov>=4.0.0", - "coverage[toml]>=5,<=7.10.6" + "coverage[toml]>=5,<=7.10.7" ] mockupdb = [ "mockupdb@git+https://github.com/mongodb-labs/mongo-mockup-db@master" diff --git a/uv.lock b/uv.lock index 896cf7b8fe..0ede4d7962 100644 --- a/uv.lock +++ b/uv.lock @@ -1613,7 +1613,7 @@ provides-extras = ["aws", "docs", "encryption", "gssapi", "ocsp", "snappy", "tes [package.metadata.requires-dev] coverage = [ - { name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.6" }, + { name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.7" }, { name = "pytest-cov", specifier = ">=4.0.0" }, ] dev = [] From e9658b2406232b6bd6ac1d961b368142f8819479 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 26 Dec 2025 16:46:28 -0500 Subject: [PATCH 053/129] Add 4.15.5 release date to changelog (#2666) --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index a2a8f6c211..a9f76383c8 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -18,7 +18,7 @@ PyMongo 4.16 brings a number of changes including: and :class:`~pymongo.synchronous.collection.Collection` to include ``None``. - Added support for NumPy 1D-arrays in :class:`bson.binary.BinaryVector`. -Changes in Version 4.15.5 (2025/XX/XX) +Changes in Version 4.15.5 (2025/12/02) -------------------------------------- Version 4.15.5 is a bug fix release. From 2f263d4d3f11eb4a2b32ace467fd2767c371f4d7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 29 Dec 2025 09:09:56 -0600 Subject: [PATCH 054/129] PYTHON-5680 Fix handling of expectedDocuments in Unified Test Runner (#2665) --- test/asynchronous/unified_format.py | 2 +- test/unified_format.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index be2372f224..6ce8f852cf 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -1458,7 +1458,7 @@ async def verify_outcome(self, spec): read_concern=ReadConcern(level="local"), ) - if expected_documents: + if expected_documents is not None: sorted_expected_documents = sorted(expected_documents, key=lambda doc: doc["_id"]) actual_documents = await coll.find({}, sort=[("_id", ASCENDING)]).to_list() self.assertListEqual(sorted_expected_documents, actual_documents) diff --git a/test/unified_format.py b/test/unified_format.py index 9e3dc1fed0..9aee287256 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -1445,7 +1445,7 @@ def verify_outcome(self, spec): read_concern=ReadConcern(level="local"), ) - if expected_documents: + if expected_documents is not None: sorted_expected_documents = sorted(expected_documents, key=lambda doc: doc["_id"]) actual_documents = coll.find({}, sort=[("_id", ASCENDING)]).to_list() self.assertListEqual(sorted_expected_documents, actual_documents) From 0cd9763423b85cd37779ea934bf7b1f8be8bbf8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 09:16:13 -0600 Subject: [PATCH 055/129] Bump zizmorcore/zizmor-action from cb3d8e846e148d1111d90b03375b9c03deceda37 to 706c51b5bce7adb027de71ab36d865f5d3fcc7b7 in the actions group (#2667) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 40c32b0957..2980210d77 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@cb3d8e846e148d1111d90b03375b9c03deceda37 + uses: zizmorcore/zizmor-action@706c51b5bce7adb027de71ab36d865f5d3fcc7b7 From fdb1f7ea4abe128ae4e59e3d063fd95d7daaa964 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark" Date: Mon, 29 Dec 2025 17:16:34 -0500 Subject: [PATCH 056/129] PYTHON-5677 Prevent ClientEncryption from loading crypt shared library (#2659) Co-authored-by: Kevin Albertson --- doc/changelog.rst | 3 +++ pymongo/asynchronous/encryption.py | 5 ++++- pymongo/synchronous/encryption.py | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index a9f76383c8..4261f1c024 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -17,6 +17,9 @@ PyMongo 4.16 brings a number of changes including: - Fixed return type annotation for ``find_one_and_*`` methods on :class:`~pymongo.asynchronous.collection.AsyncCollection` and :class:`~pymongo.synchronous.collection.Collection` to include ``None``. - Added support for NumPy 1D-arrays in :class:`bson.binary.BinaryVector`. +- Prevented :class:`~pymongo.encryption.ClientEncryption` from loading the crypt + shared library to fix "MongoCryptError: An existing crypt_shared library is + loaded by the application" unless the linked library search path is set. Changes in Version 4.15.5 (2025/12/02) -------------------------------------- diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index 4dfd36aa49..c7779a3e31 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -717,7 +717,10 @@ def __init__( self._encryption = AsyncExplicitEncrypter( self._io_callbacks, _create_mongocrypt_options( - kms_providers=kms_providers, schema_map=None, key_expiration_ms=key_expiration_ms + kms_providers=kms_providers, + schema_map=None, + key_expiration_ms=key_expiration_ms, + bypass_encryption=True, # Don't load crypt_shared ), ) # Use the same key vault collection as the callback. diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index 2d666b9763..051e6e7a5d 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -710,7 +710,10 @@ def __init__( self._encryption = ExplicitEncrypter( self._io_callbacks, _create_mongocrypt_options( - kms_providers=kms_providers, schema_map=None, key_expiration_ms=key_expiration_ms + kms_providers=kms_providers, + schema_map=None, + key_expiration_ms=key_expiration_ms, + bypass_encryption=True, # Don't load crypt_shared ), ) # Use the same key vault collection as the callback. From 6585d9cb51d5c9b2d85eec4f49b8196612d9e8ee Mon Sep 17 00:00:00 2001 From: Rin Date: Tue, 30 Dec 2025 23:41:37 +0700 Subject: [PATCH 057/129] PYTHON-2442: Refactor: use _asdict() in _options_dict() (#2670) Co-authored-by: Steven Silvester --- bson/codec_options.py | 18 +----------------- bson/json_util.py | 15 +-------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/bson/codec_options.py b/bson/codec_options.py index add5416a5b..569ac55026 100644 --- a/bson/codec_options.py +++ b/bson/codec_options.py @@ -273,9 +273,6 @@ def with_options(self, **kwargs: Any) -> CodecOptions[Any]: def _arguments_repr(self) -> str: ... - def _options_dict(self) -> dict[Any, Any]: - ... - # NamedTuple API @classmethod def _make(cls, obj: Iterable[Any]) -> CodecOptions[_DocumentType]: @@ -466,19 +463,6 @@ def _arguments_repr(self) -> str: ) ) - def _options_dict(self) -> dict[str, Any]: - """Dictionary of the arguments used to create this object.""" - # TODO: PYTHON-2442 use _asdict() instead - return { - "document_class": self.document_class, - "tz_aware": self.tz_aware, - "uuid_representation": self.uuid_representation, - "unicode_decode_error_handler": self.unicode_decode_error_handler, - "tzinfo": self.tzinfo, - "type_registry": self.type_registry, - "datetime_conversion": self.datetime_conversion, - } - def __repr__(self) -> str: return f"{self.__class__.__name__}({self._arguments_repr()})" @@ -494,7 +478,7 @@ def with_options(self, **kwargs: Any) -> CodecOptions: .. versionadded:: 3.5 """ - opts = self._options_dict() + opts = self._asdict() opts.update(kwargs) return CodecOptions(**opts) diff --git a/bson/json_util.py b/bson/json_util.py index 8151226a26..0b59c406dd 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -382,19 +382,6 @@ def _arguments_repr(self) -> str: ) ) - def _options_dict(self) -> dict[Any, Any]: - # TODO: PYTHON-2442 use _asdict() instead - options_dict = super()._options_dict() - options_dict.update( - { - "strict_number_long": self.strict_number_long, - "datetime_representation": self.datetime_representation, - "strict_uuid": self.strict_uuid, - "json_mode": self.json_mode, - } - ) - return options_dict - def with_options(self, **kwargs: Any) -> JSONOptions: """ Make a copy of this JSONOptions, overriding some options:: @@ -408,7 +395,7 @@ def with_options(self, **kwargs: Any) -> JSONOptions: .. versionadded:: 3.12 """ - opts = self._options_dict() + opts = self._asdict() for opt in ("strict_number_long", "datetime_representation", "strict_uuid", "json_mode"): opts[opt] = kwargs.get(opt, getattr(self, opt)) opts.update(kwargs) From 1be94d262dd0281b032b6f9e56d381cac3a7a6d9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 5 Jan 2026 17:04:05 -0600 Subject: [PATCH 058/129] PYTHON-5685 Fix unified spec sync metadata for csot and sessions tests (#2669) --- .evergreen/remove-unimplemented-tests.sh | 1 - .evergreen/spec-patch/PYTHON-5529.patch | 587 ----------------------- 2 files changed, 588 deletions(-) delete mode 100644 .evergreen/spec-patch/PYTHON-5529.patch diff --git a/.evergreen/remove-unimplemented-tests.sh b/.evergreen/remove-unimplemented-tests.sh index d81713794d..794319679b 100755 --- a/.evergreen/remove-unimplemented-tests.sh +++ b/.evergreen/remove-unimplemented-tests.sh @@ -7,7 +7,6 @@ rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-application-error.j rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-checkout-error.json # PYTHON-4918 rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-min-pool-size-error.json # PYTHON-4918 rm $PYMONGO/test/client-side-encryption/spec/unified/client-bulkWrite-qe.json # PYTHON-4929 -rm $PYMONGO/test/sessions/snapshot-sessions.json # PYTHON-5559 # Python doesn't implement DRIVERS-3064 rm $PYMONGO/test/collection_management/listCollections-rawdata.json diff --git a/.evergreen/spec-patch/PYTHON-5529.patch b/.evergreen/spec-patch/PYTHON-5529.patch deleted file mode 100644 index a97602e055..0000000000 --- a/.evergreen/spec-patch/PYTHON-5529.patch +++ /dev/null @@ -1,587 +0,0 @@ -diff --git a/test/csot/command-execution.json b/test/csot/command-execution.json -index aa9c3eb2..212cd410 100644 ---- a/test/csot/command-execution.json -+++ b/test/csot/command-execution.json -@@ -1,6 +1,6 @@ - { - "description": "timeoutMS behaves correctly during command execution", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.4.7", -@@ -69,8 +69,10 @@ - "appName": "reduceMaxTimeMSTest", - "w": 1, - "timeoutMS": 500, -- "heartbeatFrequencyMS": 500 -+ "heartbeatFrequencyMS": 500, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "observeEvents": [ - "commandStartedEvent" - ] -@@ -185,8 +187,10 @@ - "appName": "rttTooHighTest", - "w": 1, - "timeoutMS": 10, -- "heartbeatFrequencyMS": 500 -+ "heartbeatFrequencyMS": 500, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "observeEvents": [ - "commandStartedEvent" - ] -@@ -316,8 +320,10 @@ - "appName": "reduceMaxTimeMSTest", - "w": 1, - "timeoutMS": 90, -- "heartbeatFrequencyMS": 100000 -+ "heartbeatFrequencyMS": 100000, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "observeEvents": [ - "commandStartedEvent" - ] -diff --git a/test/csot/convenient-transactions.json b/test/csot/convenient-transactions.json -index 3868b302..f9d03429 100644 ---- a/test/csot/convenient-transactions.json -+++ b/test/csot/convenient-transactions.json -@@ -1,6 +1,6 @@ - { - "description": "timeoutMS behaves correctly for the withTransaction API", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.4", -@@ -21,8 +21,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 500 -+ "timeoutMS": 500, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -diff --git a/test/csot/error-transformations.json b/test/csot/error-transformations.json -index 4889e395..89be49f0 100644 ---- a/test/csot/error-transformations.json -+++ b/test/csot/error-transformations.json -@@ -1,6 +1,6 @@ - { - "description": "MaxTimeMSExpired server errors are transformed into a custom timeout error", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.0", -@@ -26,8 +26,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -diff --git a/test/csot/global-timeoutMS.json b/test/csot/global-timeoutMS.json -index f1edbe68..9d8046d1 100644 ---- a/test/csot/global-timeoutMS.json -+++ b/test/csot/global-timeoutMS.json -@@ -1,6 +1,6 @@ - { - "description": "timeoutMS can be configured on a MongoClient", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.4", -@@ -38,8 +38,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -217,8 +219,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -390,8 +394,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -569,8 +575,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -762,8 +770,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -941,8 +951,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -1120,8 +1132,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -1305,8 +1319,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -1484,8 +1500,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -1663,8 +1681,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -1842,8 +1862,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -2021,8 +2043,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -2194,8 +2218,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -2375,8 +2401,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -2554,8 +2582,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -2733,8 +2763,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -2906,8 +2938,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -3079,8 +3113,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -3258,8 +3294,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -3441,8 +3479,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -3628,8 +3668,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -3807,8 +3849,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -3986,8 +4030,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -4171,8 +4217,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -4360,8 +4408,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -4549,8 +4599,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -4728,8 +4780,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -4913,8 +4967,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -5102,8 +5158,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -5297,8 +5355,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -5482,8 +5542,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -@@ -5677,8 +5739,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 250 -+ "timeoutMS": 250, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -diff --git a/test/csot/non-tailable-cursors.json b/test/csot/non-tailable-cursors.json -index 291c6e72..58c59cb3 100644 ---- a/test/csot/non-tailable-cursors.json -+++ b/test/csot/non-tailable-cursors.json -@@ -1,6 +1,6 @@ - { - "description": "timeoutMS behaves correctly for non-tailable cursors", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.4" -@@ -17,8 +17,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 200 -+ "timeoutMS": 200, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -diff --git a/test/csot/retryability-timeoutMS.json b/test/csot/retryability-timeoutMS.json -index 9daad260..5a0c9f36 100644 ---- a/test/csot/retryability-timeoutMS.json -+++ b/test/csot/retryability-timeoutMS.json -@@ -1,6 +1,6 @@ - { - "description": "timeoutMS behaves correctly for retryable operations", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.0", -@@ -26,8 +26,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 100 -+ "timeoutMS": 100, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent" -diff --git a/test/csot/runCursorCommand.json b/test/csot/runCursorCommand.json -index 36f774fb..e5182e33 100644 ---- a/test/csot/runCursorCommand.json -+++ b/test/csot/runCursorCommand.json -@@ -1,6 +1,6 @@ - { - "description": "runCursorCommand", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.4" -@@ -16,6 +16,10 @@ - { - "client": { - "id": "commandClient", -+ "uriOptions": { -+ "minPoolSize": 1 -+ }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent", -diff --git a/test/csot/sessions-inherit-timeoutMS.json b/test/csot/sessions-inherit-timeoutMS.json -index 13ea91c7..dbf163e4 100644 ---- a/test/csot/sessions-inherit-timeoutMS.json -+++ b/test/csot/sessions-inherit-timeoutMS.json -@@ -1,6 +1,6 @@ - { - "description": "sessions inherit timeoutMS from their parent MongoClient", -- "schemaVersion": "1.9", -+ "schemaVersion": "1.26", - "runOnRequirements": [ - { - "minServerVersion": "4.4", -@@ -21,8 +21,10 @@ - "client": { - "id": "client", - "uriOptions": { -- "timeoutMS": 500 -+ "timeoutMS": 500, -+ "minPoolSize": 1 - }, -+ "awaitMinPoolSizeMS": 10000, - "useMultipleMongoses": false, - "observeEvents": [ - "commandStartedEvent", From 32901018ca14d9acb05004a88d65b6ab0b29c877 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark" Date: Wed, 7 Jan 2026 12:03:02 -0500 Subject: [PATCH 059/129] Prepare 4.16.0 release (#2672) --- doc/changelog.rst | 2 +- pymongo/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 4261f1c024..571ce3b63e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -Changes in Version 4.16.0 (XXXX/XX/XX) +Changes in Version 4.16.0 (2026/01/07) -------------------------------------- PyMongo 4.16 brings a number of changes including: diff --git a/pymongo/_version.py b/pymongo/_version.py index 22a0463fdb..d135408fb9 100644 --- a/pymongo/_version.py +++ b/pymongo/_version.py @@ -18,7 +18,7 @@ import re from typing import List, Tuple, Union -__version__ = "4.16.0.dev1" +__version__ = "4.16.0" def get_version_tuple(version: str) -> Tuple[Union[int, str], ...]: From cb01da6a5048a7ae6a536a3e3cceea39c4991558 Mon Sep 17 00:00:00 2001 From: "mongodb-dbx-release-bot[bot]" <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:10:24 +0000 Subject: [PATCH 060/129] BUMP 4.17.0.dev0 Signed-off-by: mongodb-dbx-release-bot[bot] <167856002+mongodb-dbx-release-bot[bot]@users.noreply.github.com> --- pymongo/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/_version.py b/pymongo/_version.py index d135408fb9..9e4363eed5 100644 --- a/pymongo/_version.py +++ b/pymongo/_version.py @@ -18,7 +18,7 @@ import re from typing import List, Tuple, Union -__version__ = "4.16.0" +__version__ = "4.17.0.dev0" def get_version_tuple(version: str) -> Tuple[Union[int, str], ...]: From b88415b8e8dd9c72158e44f9b7979534990d5ed8 Mon Sep 17 00:00:00 2001 From: Rin Date: Fri, 9 Jan 2026 21:23:00 +0700 Subject: [PATCH 061/129] refactor(ci): replace shell=True and awk pipes with native Python (#2671) --- .evergreen/scripts/resync-all-specs.py | 47 +++++++++++++++++++------- doc/conf.py | 6 ++-- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index 8e58e56da2..1996d5d634 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -6,7 +6,6 @@ import subprocess from argparse import Namespace from subprocess import CalledProcessError -from typing import Optional def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: @@ -32,14 +31,27 @@ def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: def apply_patches(errored): print("Beginning to apply patches") - subprocess.run(["bash", "./.evergreen/remove-unimplemented-tests.sh"], check=True) # noqa: S603, S607 + subprocess.run( + ["bash", "./.evergreen/remove-unimplemented-tests.sh"], # noqa: S603, S607 + check=True, + ) try: - subprocess.run( - ["git apply -R --allow-empty --whitespace=fix ./.evergreen/spec-patch/*"], # noqa: S607 - shell=True, # noqa: S602 - check=True, - stderr=subprocess.PIPE, - ) + # Avoid shell=True by passing arguments as a list. + # Note: glob expansion doesn't work in shell=False, so we use a list of files. + patches = [str(p) for p in pathlib.Path("./.evergreen/spec-patch/").glob("*")] + if patches: + subprocess.run( + [ # noqa: S603, S607 + "git", + "apply", + "-R", + "--allow-empty", + "--whitespace=fix", + *patches, + ], + check=True, + stderr=subprocess.PIPE, + ) except CalledProcessError as exc: errored["applying patches"] = exc.stderr @@ -73,17 +85,24 @@ def check_new_spec_directories(directory: pathlib.Path) -> list[str]: return list(spec_set - test_set) -def write_summary(errored: dict[str, str], new: list[str], filename: Optional[str]) -> None: +def write_summary(errored: dict[str, str], new: list[str], filename: str | None) -> None: """Generate the PR description""" pr_body = "" + # Avoid shell=True and complex pipes by using Python to process git output process = subprocess.run( - ["git diff --name-only | awk -F'/' '{print $2}' | sort | uniq"], # noqa: S607 - shell=True, # noqa: S602 + ["git", "diff", "--name-only"], # noqa: S603, S607 capture_output=True, text=True, check=True, ) - succeeded = process.stdout.strip().split() + changed_files = process.stdout.strip().splitlines() + succeeded_set = set() + for f in changed_files: + parts = f.split("/") + if len(parts) > 1: + succeeded_set.add(parts[1]) + succeeded = sorted(succeeded_set) + if len(succeeded) > 0: pr_body += "The following specs were changed:\n -" pr_body += "\n -".join(succeeded) @@ -120,7 +139,9 @@ def main(args: Namespace): description="Python Script to resync all specs and generate summary for PR." ) parser.add_argument( - "--filename", help="Name of file for the summary to be written into.", default=None + "--filename", + help="Name of file for the summary to be written into.", + default=None, ) args = parser.parse_args() main(args) diff --git a/doc/conf.py b/doc/conf.py index 063429cd98..6d648ca503 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -88,6 +88,8 @@ linkcheck_ignore = [ "https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-monitoring.md#requesting-an-immediate-check", "https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/transactions-convenient-api.md#handling-errors-inside-the-callback", + "https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.md", + "https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.md", "https://github.com/mongodb/libmongocrypt/blob/master/bindings/python/README.rst#installing-from-source", r"https://wiki.centos.org/[\w/]*", r"https://sourceforge.net/", @@ -186,8 +188,8 @@ ("index", "PyMongo.tex", "PyMongo Documentation", "Michael Dirolf", "manual"), ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. +# The name of an image file (relative to this directory) to place at the top +# of the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, From 12b3859903430c55ac18ebdb16c80a3c289c00a6 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 20 Jan 2026 12:24:55 -0500 Subject: [PATCH 062/129] PYTHON-5697 - Migrate 8.0+ tests to Windows 2022 (#2681) --- .evergreen/generated_configs/variants.yml | 18 +++++++++--------- .evergreen/scripts/generate_config_utils.py | 9 +++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 1b0c3fc734..1d8275d148 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -82,7 +82,7 @@ buildvariants: - name: .auth-aws !.auth-aws-ecs display_name: Auth AWS Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small tags: [] - name: auth-aws-macos tasks: @@ -199,7 +199,7 @@ buildvariants: - name: .test-non-standard !.pypy display_name: Encryption Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small batchtime: 1440 expansions: TEST_NAME: encryption @@ -231,7 +231,7 @@ buildvariants: - name: .test-non-standard !.pypy display_name: Encryption crypt_shared Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small batchtime: 1440 expansions: TEST_NAME: encryption @@ -273,7 +273,7 @@ buildvariants: - name: .test-standard-auth !.pypy .auth display_name: Auth Enterprise Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small expansions: TEST_NAME: enterprise_auth AUTH: auth @@ -384,7 +384,7 @@ buildvariants: - name: .ocsp-rsa !.ocsp-staple .4.4 display_name: OCSP Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small batchtime: 10080 - name: ocsp-macos tasks: @@ -423,7 +423,7 @@ buildvariants: - name: "!.auth_oidc_remote .auth_oidc" display_name: Auth OIDC Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small batchtime: 1440 # Perf tests @@ -462,7 +462,7 @@ buildvariants: - name: .test-standard !.pypy .async .replica_set-noauth-ssl display_name: PyOpenSSL Win64 run_on: - - rhel87-small + - windows-2022-latest-small batchtime: 1440 expansions: SUB_TEST_NAME: pyopenssl @@ -606,7 +606,7 @@ buildvariants: - name: .test-standard !.pypy display_name: "* Test Win64" run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small tags: [standard-non-linux] - name: test-win32 tasks: @@ -655,7 +655,7 @@ buildvariants: - name: .test-numpy display_name: Test Numpy Win64 run_on: - - windows-64-vsMulti-small + - windows-2022-latest-small tags: [binary, vector] - name: test-numpy-win32 tasks: diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 573afa36ea..1ed2e8f811 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -59,6 +59,7 @@ class Host: # Hosts with toolchains. HOSTS["rhel8"] = Host("rhel8", "rhel87-small", "RHEL8", dict()) HOSTS["win64"] = Host("win64", "windows-64-vsMulti-small", "Win64", dict()) +HOSTS["win-latest"] = Host("win-latest", "windows-2022-latest-small", "WinLatest", dict()) HOSTS["win32"] = Host("win32", "windows-64-vsMulti-small", "Win32", dict()) HOSTS["macos"] = Host("macos", "macos-14", "macOS", dict()) HOSTS["macos-arm64"] = Host("macos-arm64", "macos-14-arm64", "macOS Arm64", dict()) @@ -139,6 +140,14 @@ def create_variant( expansions = expansions and expansions.copy() or dict() if version: expansions["VERSION"] = version + # 8.0+ Windows builds must run on win-latest + if ( + "win64" in display_name.lower() + or "win32" in display_name.lower() + and version + and version >= "8.0" + ): + kwargs["run_on"] = HOSTS["win-latest"].run_on return create_variant_generic( tasks, display_name, version=version, host=host, expansions=expansions, **kwargs ) From db28d14b6d59a66ef07467bc468aa9abaa22e273 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:21:36 -0500 Subject: [PATCH 063/129] [Spec Resync] 01-19-2026 (#2680) Co-authored-by: Cloud User --- test/handshake/unified/metadata-not-propagated.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/handshake/unified/metadata-not-propagated.json b/test/handshake/unified/metadata-not-propagated.json index 500b579b89..7f70dd1488 100644 --- a/test/handshake/unified/metadata-not-propagated.json +++ b/test/handshake/unified/metadata-not-propagated.json @@ -10,6 +10,7 @@ { "client": { "id": "client", + "useMultipleMongoses": false, "observeEvents": [ "commandSucceededEvent", "commandFailedEvent", From 1e7477b9dfb49603ce26426df8efad4afa98f97b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:17:15 -0500 Subject: [PATCH 064/129] Bump pyright from 1.1.407 to 1.1.408 (#2675) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Casey Clements Co-authored-by: Casey Clements --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 12af7c6f63..65cbeca8b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ mockupdb = [ perf = ["simplejson>=3.17.0"] typing = [ "mypy==1.19.1", - "pyright==1.1.407", + "pyright==1.1.408", "typing_extensions>=3.7.4.2", "pip>=20.2" ] diff --git a/uv.lock b/uv.lock index 0ede4d7962..7d0ff9fb0f 100644 --- a/uv.lock +++ b/uv.lock @@ -1624,7 +1624,7 @@ pip = [{ name = "pip", specifier = ">=20.2" }] typing = [ { name = "mypy", specifier = "==1.19.1" }, { name = "pip", specifier = ">=20.2" }, - { name = "pyright", specifier = "==1.1.407" }, + { name = "pyright", specifier = "==1.1.408" }, { name = "typing-extensions", specifier = ">=3.7.4.2" }, ] @@ -1674,15 +1674,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.407" +version = "1.1.408" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" }, ] [[package]] From a426ad91d7a44e861c430a6b70c8cb5dcdc73b23 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Fri, 23 Jan 2026 14:53:30 -0500 Subject: [PATCH 065/129] PYTHON-5692 - [Infrastructure] Improve dependabot version updates (#2682) --- .github/dependabot.yml | 2 ++ .github/workflows/zizmor.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5bf500ba12..b810b64fc1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,8 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 groups: actions: patterns: diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 2980210d77..26f75fa792 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@706c51b5bce7adb027de71ab36d865f5d3fcc7b7 + uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1 From db6dad95beae6ad2b9b8b60b00d37a67810b743e Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 26 Jan 2026 07:51:26 -0500 Subject: [PATCH 066/129] PYTHON-5605 - Drop usage of Ubuntu 20 (#2683) --- .evergreen/generated_configs/variants.yml | 6 +++--- .evergreen/scripts/generate_config.py | 2 +- .evergreen/scripts/generate_config_utils.py | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 1d8275d148..9ddf4d634d 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -70,12 +70,12 @@ buildvariants: tags: [pr] # Aws auth tests - - name: auth-aws-ubuntu-20 + - name: auth-aws-rhel8 tasks: - name: .auth-aws - display_name: Auth AWS Ubuntu-20 + display_name: Auth AWS RHEL8 run_on: - - ubuntu2004-small + - rhel87-small tags: [] - name: auth-aws-win64 tasks: diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 05afc16db1..7d25e6c861 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -481,7 +481,7 @@ def create_perf_variants(): def create_aws_auth_variants(): variants = [] - for host_name in ["ubuntu20", "win64", "macos"]: + for host_name in ["rhel8", "win64", "macos"]: expansions = dict() tasks = [".auth-aws"] tags = [] diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 1ed2e8f811..b876d8a789 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -63,7 +63,6 @@ class Host: HOSTS["win32"] = Host("win32", "windows-64-vsMulti-small", "Win32", dict()) HOSTS["macos"] = Host("macos", "macos-14", "macOS", dict()) HOSTS["macos-arm64"] = Host("macos-arm64", "macos-14-arm64", "macOS Arm64", dict()) -HOSTS["ubuntu20"] = Host("ubuntu20", "ubuntu2004-small", "Ubuntu-20", dict()) HOSTS["ubuntu22"] = Host("ubuntu22", "ubuntu2204-small", "Ubuntu-22", dict()) HOSTS["perf"] = Host("perf", "rhel90-dbx-perf-large", "", dict()) HOSTS["debian11"] = Host("debian11", "debian11-small", "Debian11", dict()) From a89c5e3a89fa7e621f03fc9173067256a5fdc300 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:36:51 -0600 Subject: [PATCH 067/129] PYTHON-5699 & PYTHON-5698 [Spec Resync] 01-26-2026 (#2685) Co-authored-by: Cloud User --- test/command_logging/command.json | 328 ++++++++++++++++++ test/csot/sessions-inherit-timeoutMS.json | 5 +- ...sessions-override-operation-timeoutMS.json | 5 +- test/csot/sessions-override-timeoutMS.json | 5 +- 4 files changed, 337 insertions(+), 6 deletions(-) diff --git a/test/command_logging/command.json b/test/command_logging/command.json index d2970df692..57e18b365c 100644 --- a/test/command_logging/command.json +++ b/test/command_logging/command.json @@ -23,6 +23,12 @@ "database": "database", "collectionName": "logging-tests-collection" } + }, + { + "session": { + "id": "session", + "client": "client" + } } ], "initialData": [ @@ -210,6 +216,328 @@ ] } ] + }, + { + "description": "A successful commitTransaction command", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "operations": [ + { + "name": "startTransaction", + "object": "session" + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "name": "commitTransaction", + "object": "session" + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "insert", + "command": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "databaseName": "logging-tests", + "commandName": "insert", + "reply": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "admin", + "commandName": "commitTransaction", + "command": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "databaseName": "admin", + "commandName": "commitTransaction", + "reply": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + } + ] + } + ] + }, + { + "description": "A successful abortTransaction command", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "operations": [ + { + "name": "startTransaction", + "object": "session" + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "abortTransaction", + "object": "session" + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "insert", + "command": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "databaseName": "logging-tests", + "commandName": "insert", + "reply": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "admin", + "commandName": "abortTransaction", + "command": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "databaseName": "admin", + "commandName": "abortTransaction", + "reply": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + } + ] + } + ] } ] } diff --git a/test/csot/sessions-inherit-timeoutMS.json b/test/csot/sessions-inherit-timeoutMS.json index dbf163e484..50feabf609 100644 --- a/test/csot/sessions-inherit-timeoutMS.json +++ b/test/csot/sessions-inherit-timeoutMS.json @@ -244,11 +244,12 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 1 + "times": 2 }, "data": { "failCommands": [ - "insert" + "insert", + "abortTransaction" ], "blockConnection": true, "blockTimeMS": 600 diff --git a/test/csot/sessions-override-operation-timeoutMS.json b/test/csot/sessions-override-operation-timeoutMS.json index 441c698328..78e873f948 100644 --- a/test/csot/sessions-override-operation-timeoutMS.json +++ b/test/csot/sessions-override-operation-timeoutMS.json @@ -245,11 +245,12 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 1 + "times": 2 }, "data": { "failCommands": [ - "insert" + "insert", + "abortTransaction" ], "blockConnection": true, "blockTimeMS": 600 diff --git a/test/csot/sessions-override-timeoutMS.json b/test/csot/sessions-override-timeoutMS.json index d90152e909..c4a446c531 100644 --- a/test/csot/sessions-override-timeoutMS.json +++ b/test/csot/sessions-override-timeoutMS.json @@ -242,11 +242,12 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 1 + "times": 2 }, "data": { "failCommands": [ - "insert" + "insert", + "abortTransaction" ], "blockConnection": true, "blockTimeMS": 600 From 896f139ddcd3c3eb6cb482f8f0c528bbc35e00e7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 27 Jan 2026 10:49:44 -0600 Subject: [PATCH 068/129] PYTHON-5703 Use Ubuntu24 for AWS Auth tests (#2686) --- .evergreen/generated_configs/tasks.yml | 18 ++++++++++--- .evergreen/generated_configs/variants.yml | 11 ++++++-- .evergreen/scripts/generate_config.py | 29 +++++++++++++++++---- .evergreen/scripts/generate_config_utils.py | 1 + 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index f69afcdb9e..60ee6ed135 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -102,7 +102,7 @@ tasks: AWS_ROLE_SESSION_NAME: test TOOLCHAIN_VERSION: "3.14" tags: [auth-aws, auth-aws-web-identity] - - name: test-auth-aws-latest-ecs-python3.10 + - name: test-auth-aws-latest-regular-python3.10-min-deps commands: - func: run server vars: @@ -112,9 +112,21 @@ tasks: - func: run tests vars: TEST_NAME: auth_aws - SUB_TEST_NAME: ecs + SUB_TEST_NAME: regular TOOLCHAIN_VERSION: "3.10" - tags: [auth-aws, auth-aws-ecs] + TEST_MIN_DEPS: "1" + tags: [auth-aws, auth-aws-regular] + - name: test-auth-aws-ecs + commands: + - func: assume ec2 role + - func: run server + vars: + VERSION: "8.0" + - func: run tests + vars: + TEST_NAME: auth_aws + SUB_TEST_NAME: ecs + tags: [auth-aws-ecs] # Backport pr tests - name: backport-pr diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 9ddf4d634d..e4890af2b1 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -79,18 +79,25 @@ buildvariants: tags: [] - name: auth-aws-win64 tasks: - - name: .auth-aws !.auth-aws-ecs + - name: .auth-aws display_name: Auth AWS Win64 run_on: - windows-2022-latest-small tags: [] - name: auth-aws-macos tasks: - - name: .auth-aws !.auth-aws-web-identity !.auth-aws-ecs !.auth-aws-ec2 + - name: .auth-aws !.auth-aws-web-identity !.auth-aws-ec2 display_name: Auth AWS macOS run_on: - macos-14 tags: [pr] + - name: auth-aws-ecs-macos + tasks: + - name: .auth-aws-ecs + display_name: Auth AWS ECS macOS + run_on: + - ubuntu2404-small + tags: [pr] # Aws lambda tests - name: faas-lambda diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 7d25e6c861..984db0d039 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -486,10 +486,10 @@ def create_aws_auth_variants(): tasks = [".auth-aws"] tags = [] if host_name == "macos": - tasks = [".auth-aws !.auth-aws-web-identity !.auth-aws-ecs !.auth-aws-ec2"] + tasks = [".auth-aws !.auth-aws-web-identity !.auth-aws-ec2"] tags = ["pr"] elif host_name == "win64": - tasks = [".auth-aws !.auth-aws-ecs"] + tasks = [".auth-aws"] host = HOSTS[host_name] variant = create_variant( tasks, @@ -499,6 +499,16 @@ def create_aws_auth_variants(): expansions=expansions, ) variants.append(variant) + + # The ECS test must be run on Ubuntu 24 to match the Fargate Config. + variant = create_variant( + [".auth-aws-ecs"], + get_variant_name("Auth AWS ECS", host), + host=HOSTS["ubuntu24"], + tags=tags, + expansions=expansions, + ) + variants.append(variant) return variants @@ -788,19 +798,18 @@ def create_aws_tasks(): "env-creds", "session-creds", "web-identity", - "ecs", ] + assume_func = FunctionCall(func="assume ec2 role") for version, test_type, python in zip_cycle(get_versions_from("4.4"), aws_test_types, CPYTHONS): base_name = f"test-auth-aws-{version}" base_tags = ["auth-aws"] server_vars = dict(AUTH_AWS="1", VERSION=version) server_func = FunctionCall(func="run server", vars=server_vars) - assume_func = FunctionCall(func="assume ec2 role") tags = [*base_tags, f"auth-aws-{test_type}"] if "t" in python: tags.append("free-threaded") test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME=test_type, TOOLCHAIN_VERSION=python) - if python == ALL_PYTHONS[0] and test_type != "ecs": + if python == ALL_PYTHONS[0]: test_vars["TEST_MIN_DEPS"] = "1" name = get_task_name(f"{base_name}-{test_type}", **test_vars) test_func = FunctionCall(func="run tests", vars=test_vars) @@ -822,6 +831,16 @@ def create_aws_tasks(): funcs = [server_func, assume_func, test_func] tasks.append(EvgTask(name=name, tags=tags, commands=funcs)) + # Add the ECS task. This will run on Ubuntu 24 to match the + # Fargate environment. + tags = ["auth-aws-ecs"] + test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME="ecs") + name = get_task_name("test-auth-aws-ecs", **test_vars) + test_func = FunctionCall(func="run tests", vars=test_vars) + server_func = FunctionCall(func="run server", vars=dict(VERSION="8.0")) + funcs = [assume_func, server_func, test_func] + tasks.append(EvgTask(name=name, tags=tags, commands=funcs)) + return tasks diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index b876d8a789..ee5da4bcd6 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -64,6 +64,7 @@ class Host: HOSTS["macos"] = Host("macos", "macos-14", "macOS", dict()) HOSTS["macos-arm64"] = Host("macos-arm64", "macos-14-arm64", "macOS Arm64", dict()) HOSTS["ubuntu22"] = Host("ubuntu22", "ubuntu2204-small", "Ubuntu-22", dict()) +HOSTS["ubuntu24"] = Host("ubuntu24", "ubuntu2404-small", "Ubuntu-24", dict()) HOSTS["perf"] = Host("perf", "rhel90-dbx-perf-large", "", dict()) HOSTS["debian11"] = Host("debian11", "debian11-small", "Debian11", dict()) DEFAULT_HOST = HOSTS["rhel8"] From fa56b563ddb333c49e4e487646ad4e39d72f23d7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 27 Jan 2026 12:04:51 -0600 Subject: [PATCH 069/129] PYTHON-5704 Skip free-threading for enterprise auth builds (#2687) --- .evergreen/generated_configs/variants.yml | 2 +- .evergreen/scripts/generate_config.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index e4890af2b1..42a6776092 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -277,7 +277,7 @@ buildvariants: AUTH: auth - name: auth-enterprise-win64 tasks: - - name: .test-standard-auth !.pypy .auth + - name: .test-standard-auth !.pypy .auth !.free-threaded display_name: Auth Enterprise Win64 run_on: - windows-2022-latest-small diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 984db0d039..04579c521f 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -223,7 +223,8 @@ def create_enterprise_auth_variants(): if host == "macos": tasks = [".test-standard-auth !.pypy .auth !.free-threaded"] if host == "win64": - tasks = [".test-standard-auth !.pypy .auth"] + # https://jira.mongodb.org/browse/PYTHON-5704 + tasks = [".test-standard-auth !.pypy .auth !.free-threaded"] variant = create_variant(tasks, display_name, host=host, expansions=expansions) variants.append(variant) return variants From 4c86d86bf1c78b7708b4bfee7a5d1ce5354c572f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:36:28 -0600 Subject: [PATCH 070/129] Bump astral-sh/setup-uv from 7.1.6 to 7.2.0 in the actions group across 1 directory (#2684) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .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 3d32a852a2..086e22faec 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -87,7 +87,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: "3.10" @@ -112,7 +112,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: "3.10" @@ -131,7 +131,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: "3.10" @@ -153,7 +153,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -174,7 +174,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: enable-cache: true python-version: "3.10" @@ -264,7 +264,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7 + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 with: python-version: "3.9" - id: setup-mongodb From 182d8e2ea08c674e8b1e5d0d7a17a4ca551b4e32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:35:46 -0600 Subject: [PATCH 071/129] Bump peter-evans/create-pull-request from 8.0.0 to 8.1.0 in the actions group (#2692) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .github/workflows/sbom.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index c5d055bdcb..69a07c8be2 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -74,7 +74,7 @@ jobs: if-no-files-found: error - name: Create Pull Request - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore: Update SBOM after dependency changes' From 543c4e532c4c0fd129b06210e78b5bf754b80b82 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 2 Feb 2026 08:47:26 -0500 Subject: [PATCH 072/129] PYTHON-1357 - Refactor Cursor and CommandCursor (#2691) --- .../pymongo/asynchronous/command_cursor.rst | 1 + doc/api/pymongo/asynchronous/cursor.rst | 2 + doc/api/pymongo/command_cursor.rst | 1 + doc/api/pymongo/cursor.rst | 1 + pymongo/asynchronous/client_session.py | 2 +- pymongo/asynchronous/command_cursor.py | 138 +-------------- pymongo/asynchronous/cursor.py | 167 +----------------- pymongo/asynchronous/cursor_base.py | 122 +++++++++++++ pymongo/asynchronous/mongo_client.py | 2 +- pymongo/cursor_shared.py | 99 ++++++++++- pymongo/synchronous/client_session.py | 2 +- pymongo/synchronous/command_cursor.py | 138 +-------------- pymongo/synchronous/cursor.py | 167 +----------------- pymongo/synchronous/cursor_base.py | 122 +++++++++++++ pymongo/synchronous/mongo_client.py | 2 +- tools/synchro.py | 1 + 16 files changed, 380 insertions(+), 587 deletions(-) create mode 100644 pymongo/asynchronous/cursor_base.py create mode 100644 pymongo/synchronous/cursor_base.py diff --git a/doc/api/pymongo/asynchronous/command_cursor.rst b/doc/api/pymongo/asynchronous/command_cursor.rst index 1f94c6e525..73488d1dab 100644 --- a/doc/api/pymongo/asynchronous/command_cursor.rst +++ b/doc/api/pymongo/asynchronous/command_cursor.rst @@ -5,3 +5,4 @@ .. automodule:: pymongo.asynchronous.command_cursor :synopsis: Tools for iterating over MongoDB command results :members: + :inherited-members: diff --git a/doc/api/pymongo/asynchronous/cursor.rst b/doc/api/pymongo/asynchronous/cursor.rst index f511734de4..635805c2af 100644 --- a/doc/api/pymongo/asynchronous/cursor.rst +++ b/doc/api/pymongo/asynchronous/cursor.rst @@ -7,6 +7,8 @@ .. autoclass:: pymongo.asynchronous.cursor.AsyncCursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, session=None, allow_disk_use=None) :members: + :inherited-members: + .. describe:: c[index] diff --git a/doc/api/pymongo/command_cursor.rst b/doc/api/pymongo/command_cursor.rst index 2d0597a00f..b6cc78905a 100644 --- a/doc/api/pymongo/command_cursor.rst +++ b/doc/api/pymongo/command_cursor.rst @@ -4,3 +4,4 @@ .. automodule:: pymongo.command_cursor :synopsis: Tools for iterating over MongoDB command results :members: + :inherited-members: diff --git a/doc/api/pymongo/cursor.rst b/doc/api/pymongo/cursor.rst index 513f051abb..2eb14c9cb7 100644 --- a/doc/api/pymongo/cursor.rst +++ b/doc/api/pymongo/cursor.rst @@ -17,6 +17,7 @@ .. autoclass:: pymongo.cursor.Cursor(collection, filter=None, projection=None, skip=0, limit=0, no_cursor_timeout=False, cursor_type=CursorType.NON_TAILABLE, sort=None, allow_partial_results=False, oplog_replay=False, batch_size=0, collation=None, hint=None, max_scan=None, max_time_ms=None, max=None, min=None, return_key=False, show_record_id=False, snapshot=False, comment=None, session=None, allow_disk_use=None) :members: + :inherited-members: .. describe:: c[index] diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index 6ab3b39983..a12ca1f11b 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -157,7 +157,7 @@ from bson.int64 import Int64 from bson.timestamp import Timestamp from pymongo import _csot -from pymongo.asynchronous.cursor import _ConnectionManager +from pymongo.asynchronous.cursor_base import _ConnectionManager from pymongo.errors import ( ConfigurationError, ConnectionFailure, diff --git a/pymongo/asynchronous/command_cursor.py b/pymongo/asynchronous/command_cursor.py index e18b3a330e..5a59c67a15 100644 --- a/pymongo/asynchronous/command_cursor.py +++ b/pymongo/asynchronous/command_cursor.py @@ -20,7 +20,6 @@ TYPE_CHECKING, Any, AsyncIterator, - Generic, Mapping, NoReturn, Optional, @@ -29,17 +28,10 @@ ) from bson import CodecOptions, _convert_raw_document_lists_to_streams -from pymongo import _csot -from pymongo.asynchronous.cursor import _ConnectionManager +from pymongo.asynchronous.cursor_base import _AsyncCursorBase, _ConnectionManager from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure -from pymongo.message import ( - _CursorAddress, - _GetMore, - _OpMsg, - _OpReply, - _RawBatchGetMore, -) +from pymongo.message import _GetMore, _OpMsg, _OpReply, _RawBatchGetMore from pymongo.response import PinnedResponse from pymongo.typings import _Address, _DocumentOut, _DocumentType @@ -51,7 +43,7 @@ _IS_SYNC = False -class AsyncCommandCursor(Generic[_DocumentType]): +class AsyncCommandCursor(_AsyncCursorBase[_DocumentType]): """An asynchronous cursor / iterator over command cursors.""" _getmore_class = _GetMore @@ -98,8 +90,8 @@ def __init__( f"max_await_time_ms must be an integer or None, not {type(max_await_time_ms)}" ) - def __del__(self) -> None: - self._die_no_lock() + def _get_namespace(self) -> str: + return self._ns def batch_size(self, batch_size: int) -> AsyncCommandCursor[_DocumentType]: """Limits the number of documents returned in one batch. Each batch @@ -161,94 +153,12 @@ def _unpack_response( ) -> Sequence[_DocumentOut]: return response.unpack_response(cursor_id, codec_options, user_fields, legacy_response) - @property - def alive(self) -> bool: - """Does this cursor have the potential to return more data? - - Even if :attr:`alive` is ``True``, :meth:`next` can raise - :exc:`StopIteration`. Best to use a for loop:: - - async for doc in collection.aggregate(pipeline): - print(doc) - - .. note:: :attr:`alive` can be True while iterating a cursor from - a failed server. In this case :attr:`alive` will return False after - :meth:`next` fails to retrieve the next batch of results from the - server. - """ - return bool(len(self._data) or (not self._killed)) - - @property - def cursor_id(self) -> int: - """Returns the id of the cursor.""" - return self._id - - @property - def address(self) -> Optional[_Address]: - """The (host, port) of the server used, or None. - - .. versionadded:: 3.0 - """ - return self._address - - @property - def session(self) -> Optional[AsyncClientSession]: - """The cursor's :class:`~pymongo.asynchronous.client_session.AsyncClientSession`, or None. - - .. versionadded:: 3.6 - """ - if self._session and not self._session._implicit: - return self._session - return None - - def _prepare_to_die(self) -> tuple[int, Optional[_CursorAddress]]: - already_killed = self._killed - self._killed = True - if self._id and not already_killed: - cursor_id = self._id - assert self._address is not None - address = _CursorAddress(self._address, self._ns) - else: - # Skip killCursors. - cursor_id = 0 - address = None - return cursor_id, address - - def _die_no_lock(self) -> None: - """Closes this cursor without acquiring a lock.""" - cursor_id, address = self._prepare_to_die() - self._collection.database.client._cleanup_cursor_no_lock( - cursor_id, address, self._sock_mgr, self._session - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - - async def _die_lock(self) -> None: - """Closes this cursor.""" - cursor_id, address = self._prepare_to_die() - await self._collection.database.client._cleanup_cursor_lock( - cursor_id, - address, - self._sock_mgr, - self._session, - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - def _end_session(self) -> None: if self._session and self._session._implicit: self._session._attached_to_cursor = False self._session._end_implicit_session() self._session = None - async def close(self) -> None: - """Explicitly close / kill this cursor.""" - await self._die_lock() - async def _send_message(self, operation: _GetMore) -> None: """Send a getmore message and handle the response.""" client = self._collection.database.client @@ -330,6 +240,9 @@ async def _refresh(self) -> int: def __aiter__(self) -> AsyncIterator[_DocumentType]: return self + async def __aenter__(self) -> AsyncCommandCursor[_DocumentType]: + return self + async def next(self) -> _DocumentType: """Advance the cursor.""" # Block until a document is returnable. @@ -385,41 +298,6 @@ async def try_next(self) -> Optional[_DocumentType]: """ return await self._try_next(get_more_allowed=True) - async def __aenter__(self) -> AsyncCommandCursor[_DocumentType]: - return self - - async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - await self.close() - - @_csot.apply - async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: - """Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``. - - To use:: - - >>> await cursor.to_list() - - Or, so read at most n items from the cursor:: - - >>> await cursor.to_list(n) - - If the cursor is empty or has no more results, an empty list will be returned. - - .. versionadded:: 4.9 - """ - res: list[_DocumentType] = [] - remaining = length - if isinstance(length, int) and length < 1: - raise ValueError("to_list() length must be greater than 0") - while self.alive: - if not await self._next_batch(res, remaining): - break - if length is not None: - remaining = length - len(res) - if remaining == 0: - break - return res - class AsyncRawBatchCommandCursor(AsyncCommandCursor[_DocumentType]): _getmore_class = _RawBatchGetMore diff --git a/pymongo/asynchronous/cursor.py b/pymongo/asynchronous/cursor.py index 67494192fe..a60c082ade 100644 --- a/pymongo/asynchronous/cursor.py +++ b/pymongo/asynchronous/cursor.py @@ -21,7 +21,6 @@ from typing import ( TYPE_CHECKING, Any, - Generic, Iterable, List, Mapping, @@ -36,7 +35,8 @@ from bson import RE_TYPE, _convert_raw_document_lists_to_streams from bson.code import Code from bson.son import SON -from pymongo import _csot, helpers_shared +from pymongo import helpers_shared +from pymongo.asynchronous.cursor_base import _AsyncCursorBase, _ConnectionManager from pymongo.asynchronous.helpers import anext from pymongo.collation import validate_collation_or_none from pymongo.common import ( @@ -45,9 +45,7 @@ ) from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS, _QUERY_OPTIONS, CursorType, _Hint, _Sort from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure -from pymongo.lock import _async_create_lock from pymongo.message import ( - _CursorAddress, _GetMore, _OpMsg, _OpReply, @@ -65,31 +63,12 @@ from bson.codec_options import CodecOptions from pymongo.asynchronous.client_session import AsyncClientSession from pymongo.asynchronous.collection import AsyncCollection - from pymongo.asynchronous.pool import AsyncConnection from pymongo.read_preferences import _ServerMode _IS_SYNC = False -class _ConnectionManager: - """Used with exhaust cursors to ensure the connection is returned.""" - - def __init__(self, conn: AsyncConnection, more_to_come: bool): - self.conn: Optional[AsyncConnection] = conn - self.more_to_come = more_to_come - self._lock = _async_create_lock() - - def update_exhaust(self, more_to_come: bool) -> None: - self.more_to_come = more_to_come - - async def close(self) -> None: - """Return this instance's connection to the connection pool.""" - if self.conn: - await self.conn.unpin() - self.conn = None - - -class AsyncCursor(Generic[_DocumentType]): +class AsyncCursor(_AsyncCursorBase[_DocumentType]): _query_class = _Query _getmore_class = _GetMore @@ -266,8 +245,8 @@ def retrieved(self) -> int: """The number of documents retrieved so far.""" return self._retrieved - def __del__(self) -> None: - self._die_no_lock() + def _get_namespace(self) -> str: + return f"{self._dbname}.{self._collname}" def clone(self) -> AsyncCursor[_DocumentType]: """Get a clone of this cursor. @@ -899,55 +878,6 @@ def _get_read_preference(self) -> _ServerMode: self._read_preference = self._collection._read_preference_for(self.session) return self._read_preference - @property - def alive(self) -> bool: - """Does this cursor have the potential to return more data? - - This is mostly useful with `tailable cursors - `_ - since they will stop iterating even though they *may* return more - results in the future. - - With regular cursors, simply use an asynchronous for loop instead of :attr:`alive`:: - - async for doc in collection.find(): - print(doc) - - .. note:: Even if :attr:`alive` is True, :meth:`next` can raise - :exc:`StopIteration`. :attr:`alive` can also be True while iterating - a cursor from a failed server. In this case :attr:`alive` will - return False after :meth:`next` fails to retrieve the next batch - of results from the server. - """ - return bool(len(self._data) or (not self._killed)) - - @property - def cursor_id(self) -> Optional[int]: - """Returns the id of the cursor - - .. versionadded:: 2.2 - """ - return self._id - - @property - def address(self) -> Optional[tuple[str, Any]]: - """The (host, port) of the server used, or None. - - .. versionchanged:: 3.0 - Renamed from "conn_id". - """ - return self._address - - @property - def session(self) -> Optional[AsyncClientSession]: - """The cursor's :class:`~pymongo.asynchronous.client_session.AsyncClientSession`, or None. - - .. versionadded:: 3.6 - """ - if self._session and not self._session._implicit: - return self._session - return None - def __copy__(self) -> AsyncCursor[_DocumentType]: """Support function for `copy.copy()`. @@ -1011,59 +941,6 @@ def _deepcopy( y[key] = value # type:ignore[index] return y - def _prepare_to_die(self, already_killed: bool) -> tuple[int, Optional[_CursorAddress]]: - self._killed = True - if self._id and not already_killed: - cursor_id = self._id - assert self._address is not None - address = _CursorAddress(self._address, f"{self._dbname}.{self._collname}") - else: - # Skip killCursors. - cursor_id = 0 - address = None - return cursor_id, address - - def _die_no_lock(self) -> None: - """Closes this cursor without acquiring a lock.""" - try: - already_killed = self._killed - except AttributeError: - # ___init__ did not run to completion (or at all). - return - - cursor_id, address = self._prepare_to_die(already_killed) - self._collection.database.client._cleanup_cursor_no_lock( - cursor_id, address, self._sock_mgr, self._session - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - - async def _die_lock(self) -> None: - """Closes this cursor.""" - try: - already_killed = self._killed - except AttributeError: - # ___init__ did not run to completion (or at all). - return - - cursor_id, address = self._prepare_to_die(already_killed) - await self._collection.database.client._cleanup_cursor_lock( - cursor_id, - address, - self._sock_mgr, - self._session, - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - - async def close(self) -> None: - """Explicitly close / kill this cursor.""" - await self._die_lock() - async def distinct(self, key: str) -> list[Any]: """Get a list of distinct values for `key` among all documents in the result set of this query. @@ -1296,40 +1173,8 @@ def __aiter__(self) -> AsyncCursor[_DocumentType]: async def __aenter__(self) -> AsyncCursor[_DocumentType]: return self - async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - await self.close() - - @_csot.apply - async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: - """Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``. - To use:: - - >>> await cursor.to_list() - - Or, to read at most n items from the cursor:: - - >>> await cursor.to_list(n) - - If the cursor is empty or has no more results, an empty list will be returned. - - .. versionadded:: 4.9 - """ - res: list[_DocumentType] = [] - remaining = length - if isinstance(length, int) and length < 1: - raise ValueError("to_list() length must be greater than 0") - while self.alive: - if not await self._next_batch(res, remaining): - break - if length is not None: - remaining = length - len(res) - if remaining == 0: - break - return res - - -class AsyncRawBatchCursor(AsyncCursor, Generic[_DocumentType]): # type: ignore[type-arg] +class AsyncRawBatchCursor(AsyncCursor[_DocumentType]): """An asynchronous cursor / iterator over raw batches of BSON data from a query result.""" _query_class = _RawBatchQuery diff --git a/pymongo/asynchronous/cursor_base.py b/pymongo/asynchronous/cursor_base.py new file mode 100644 index 0000000000..1e293a1812 --- /dev/null +++ b/pymongo/asynchronous/cursor_base.py @@ -0,0 +1,122 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you +# may not use this file except in compliance with the License. You +# may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Asynchronous cursor base extending the shared agnostic cursor base.""" +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Any, Optional + +from pymongo import _csot +from pymongo.cursor_shared import _AgnosticCursorBase +from pymongo.lock import _async_create_lock +from pymongo.typings import _DocumentType + +if TYPE_CHECKING: + from pymongo.asynchronous.client_session import AsyncClientSession + from pymongo.asynchronous.pool import AsyncConnection + +_IS_SYNC = False + + +class _ConnectionManager: + """Used with exhaust cursors to ensure the connection is returned.""" + + def __init__(self, conn: AsyncConnection, more_to_come: bool): + self.conn: Optional[AsyncConnection] = conn + self.more_to_come = more_to_come + self._lock = _async_create_lock() + + def update_exhaust(self, more_to_come: bool) -> None: + self.more_to_come = more_to_come + + async def close(self) -> None: + """Return this instance's connection to the connection pool.""" + if self.conn: + await self.conn.unpin() + self.conn = None + + +class _AsyncCursorBase(_AgnosticCursorBase[_DocumentType]): + """Asynchronous cursor base class.""" + + @property + def session(self) -> Optional[AsyncClientSession]: + """The cursor's :class:`~pymongo.asynchronous.client_session.AsyncClientSession`, or None. + + .. versionadded:: 3.6 + """ + if self._session and not self._session._implicit: + return self._session + return None + + @abstractmethod + async def _next_batch(self, result: list, total: Optional[int] = None) -> bool: # type: ignore[type-arg] + ... + + async def _die_lock(self) -> None: + """Closes this cursor.""" + try: + already_killed = self._killed + except AttributeError: + # ___init__ did not run to completion (or at all). + return + + cursor_id, address = self._prepare_to_die(already_killed) + await self._collection.database.client._cleanup_cursor_lock( + cursor_id, + address, + self._sock_mgr, + self._session, + ) + if self._session and self._session._implicit: + self._session._attached_to_cursor = False + self._session = None + self._sock_mgr = None + + async def close(self) -> None: + """Explicitly close / kill this cursor.""" + await self._die_lock() + + async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + await self.close() + + @_csot.apply + async def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: + """Converts the contents of this cursor to a list more efficiently than ``[doc async for doc in cursor]``. + + To use:: + + >>> await cursor.to_list() + + Or, to read at most n items from the cursor:: + + >>> await cursor.to_list(n) + + If the cursor is empty or has no more results, an empty list will be returned. + + .. versionadded:: 4.9 + """ + res: list[_DocumentType] = [] + remaining = length + if isinstance(length, int) and length < 1: + raise ValueError("to_list() length must be greater than 0") + while self.alive: + if not await self._next_batch(res, remaining): + break + if length is not None: + remaining = length - len(res) + if remaining == 0: + break + return res diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index bc51b7d8cc..4f3c43f23c 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -139,7 +139,7 @@ from bson.objectid import ObjectId from pymongo.asynchronous.bulk import _AsyncBulk from pymongo.asynchronous.client_session import AsyncClientSession, _ServerSession - from pymongo.asynchronous.cursor import _ConnectionManager + from pymongo.asynchronous.cursor_base import _ConnectionManager from pymongo.asynchronous.encryption import _Encrypter from pymongo.asynchronous.pool import AsyncConnection from pymongo.asynchronous.server import Server diff --git a/pymongo/cursor_shared.py b/pymongo/cursor_shared.py index de6126c4fb..e5b76a404f 100644 --- a/pymongo/cursor_shared.py +++ b/pymongo/cursor_shared.py @@ -16,7 +16,104 @@ """Constants and types shared across all cursor classes.""" from __future__ import annotations -from typing import Any, Mapping, Sequence, Tuple, Union +from abc import ABC, abstractmethod +from typing import Any, Generic, Mapping, Optional, Sequence, Tuple, Union + +from pymongo.message import _CursorAddress +from pymongo.typings import _Address, _DocumentType + + +class _AgnosticCursorBase(Generic[_DocumentType], ABC): + """ + Shared IO-agnostic cursor base used by both async and sync cursor classes. + All IO-specific behavior is implemented in subclasses. + """ + + # These are all typed more accurately in subclasses. + _collection: Any + _id: Optional[int] + _data: Any + _address: Optional[_Address] + _sock_mgr: Any + _session: Optional[Any] + _killed: bool + + @abstractmethod + def _get_namespace(self) -> str: + """Return the full namespace (dbname.collname) for this cursor.""" + ... + + def __del__(self) -> None: + self._die_no_lock() + + @property + def alive(self) -> bool: + """Does this cursor have the potential to return more data? + + This is mostly useful with `tailable cursors + `_ + since they will stop iterating even though they *may* return more + results in the future. + + With regular cursors, simply use an asynchronous for loop instead of :attr:`alive`:: + + async for doc in collection.find(): + print(doc) + + .. note:: Even if :attr:`alive` is True, :meth:`next` can raise + :exc:`StopIteration`. :attr:`alive` can also be True while iterating + a cursor from a failed server. In this case :attr:`alive` will + return False after :meth:`next` fails to retrieve the next batch + of results from the server. + """ + return bool(len(self._data) or (not self._killed)) + + @property + def cursor_id(self) -> Optional[int]: + """Returns the id of the cursor. + + .. versionadded:: 2.2 + """ + return self._id + + @property + def address(self) -> Optional[_Address]: + """The (host, port) of the server used, or None. + + .. versionchanged:: 3.0 + Renamed from "conn_id". + """ + return self._address + + def _prepare_to_die(self, already_killed: bool) -> tuple[int, Optional[_CursorAddress]]: + self._killed = True + if self._id and not already_killed: + cursor_id = self._id + assert self._address is not None + address = _CursorAddress(self._address, self._get_namespace()) + else: + # Skip killCursors. + cursor_id = 0 + address = None + return cursor_id, address + + def _die_no_lock(self) -> None: + """Closes this cursor without acquiring a lock.""" + try: + already_killed = self._killed + except AttributeError: + # ___init__ did not run to completion (or at all). + return + + cursor_id, address = self._prepare_to_die(already_killed) + self._collection.database.client._cleanup_cursor_no_lock( + cursor_id, address, self._sock_mgr, self._session + ) + if self._session and self._session._implicit: + self._session._attached_to_cursor = False + self._session = None + self._sock_mgr = None + # These errors mean that the server has already killed the cursor so there is # no need to send killCursors. diff --git a/pymongo/synchronous/client_session.py b/pymongo/synchronous/client_session.py index 9b547dc946..8755e57261 100644 --- a/pymongo/synchronous/client_session.py +++ b/pymongo/synchronous/client_session.py @@ -168,7 +168,7 @@ from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference, _ServerMode from pymongo.server_type import SERVER_TYPE -from pymongo.synchronous.cursor import _ConnectionManager +from pymongo.synchronous.cursor_base import _ConnectionManager from pymongo.write_concern import WriteConcern if TYPE_CHECKING: diff --git a/pymongo/synchronous/command_cursor.py b/pymongo/synchronous/command_cursor.py index a09a67efc9..34f60c6540 100644 --- a/pymongo/synchronous/command_cursor.py +++ b/pymongo/synchronous/command_cursor.py @@ -19,7 +19,6 @@ from typing import ( TYPE_CHECKING, Any, - Generic, Iterator, Mapping, NoReturn, @@ -29,18 +28,11 @@ ) from bson import CodecOptions, _convert_raw_document_lists_to_streams -from pymongo import _csot from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure -from pymongo.message import ( - _CursorAddress, - _GetMore, - _OpMsg, - _OpReply, - _RawBatchGetMore, -) +from pymongo.message import _GetMore, _OpMsg, _OpReply, _RawBatchGetMore from pymongo.response import PinnedResponse -from pymongo.synchronous.cursor import _ConnectionManager +from pymongo.synchronous.cursor_base import _ConnectionManager, _CursorBase from pymongo.typings import _Address, _DocumentOut, _DocumentType if TYPE_CHECKING: @@ -51,7 +43,7 @@ _IS_SYNC = True -class CommandCursor(Generic[_DocumentType]): +class CommandCursor(_CursorBase[_DocumentType]): """A cursor / iterator over command cursors.""" _getmore_class = _GetMore @@ -98,8 +90,8 @@ def __init__( f"max_await_time_ms must be an integer or None, not {type(max_await_time_ms)}" ) - def __del__(self) -> None: - self._die_no_lock() + def _get_namespace(self) -> str: + return self._ns def batch_size(self, batch_size: int) -> CommandCursor[_DocumentType]: """Limits the number of documents returned in one batch. Each batch @@ -161,94 +153,12 @@ def _unpack_response( ) -> Sequence[_DocumentOut]: return response.unpack_response(cursor_id, codec_options, user_fields, legacy_response) - @property - def alive(self) -> bool: - """Does this cursor have the potential to return more data? - - Even if :attr:`alive` is ``True``, :meth:`next` can raise - :exc:`StopIteration`. Best to use a for loop:: - - for doc in collection.aggregate(pipeline): - print(doc) - - .. note:: :attr:`alive` can be True while iterating a cursor from - a failed server. In this case :attr:`alive` will return False after - :meth:`next` fails to retrieve the next batch of results from the - server. - """ - return bool(len(self._data) or (not self._killed)) - - @property - def cursor_id(self) -> int: - """Returns the id of the cursor.""" - return self._id - - @property - def address(self) -> Optional[_Address]: - """The (host, port) of the server used, or None. - - .. versionadded:: 3.0 - """ - return self._address - - @property - def session(self) -> Optional[ClientSession]: - """The cursor's :class:`~pymongo.client_session.ClientSession`, or None. - - .. versionadded:: 3.6 - """ - if self._session and not self._session._implicit: - return self._session - return None - - def _prepare_to_die(self) -> tuple[int, Optional[_CursorAddress]]: - already_killed = self._killed - self._killed = True - if self._id and not already_killed: - cursor_id = self._id - assert self._address is not None - address = _CursorAddress(self._address, self._ns) - else: - # Skip killCursors. - cursor_id = 0 - address = None - return cursor_id, address - - def _die_no_lock(self) -> None: - """Closes this cursor without acquiring a lock.""" - cursor_id, address = self._prepare_to_die() - self._collection.database.client._cleanup_cursor_no_lock( - cursor_id, address, self._sock_mgr, self._session - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - - def _die_lock(self) -> None: - """Closes this cursor.""" - cursor_id, address = self._prepare_to_die() - self._collection.database.client._cleanup_cursor_lock( - cursor_id, - address, - self._sock_mgr, - self._session, - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - def _end_session(self) -> None: if self._session and self._session._implicit: self._session._attached_to_cursor = False self._session._end_implicit_session() self._session = None - def close(self) -> None: - """Explicitly close / kill this cursor.""" - self._die_lock() - def _send_message(self, operation: _GetMore) -> None: """Send a getmore message and handle the response.""" client = self._collection.database.client @@ -330,6 +240,9 @@ def _refresh(self) -> int: def __iter__(self) -> Iterator[_DocumentType]: return self + def __enter__(self) -> CommandCursor[_DocumentType]: + return self + def next(self) -> _DocumentType: """Advance the cursor.""" # Block until a document is returnable. @@ -385,41 +298,6 @@ def try_next(self) -> Optional[_DocumentType]: """ return self._try_next(get_more_allowed=True) - def __enter__(self) -> CommandCursor[_DocumentType]: - return self - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self.close() - - @_csot.apply - def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: - """Converts the contents of this cursor to a list more efficiently than ``[doc for doc in cursor]``. - - To use:: - - >>> cursor.to_list() - - Or, so read at most n items from the cursor:: - - >>> cursor.to_list(n) - - If the cursor is empty or has no more results, an empty list will be returned. - - .. versionadded:: 4.9 - """ - res: list[_DocumentType] = [] - remaining = length - if isinstance(length, int) and length < 1: - raise ValueError("to_list() length must be greater than 0") - while self.alive: - if not self._next_batch(res, remaining): - break - if length is not None: - remaining = length - len(res) - if remaining == 0: - break - return res - class RawBatchCommandCursor(CommandCursor[_DocumentType]): _getmore_class = _RawBatchGetMore diff --git a/pymongo/synchronous/cursor.py b/pymongo/synchronous/cursor.py index d1e9731d99..5a721d8e06 100644 --- a/pymongo/synchronous/cursor.py +++ b/pymongo/synchronous/cursor.py @@ -21,7 +21,6 @@ from typing import ( TYPE_CHECKING, Any, - Generic, Iterable, List, Mapping, @@ -36,7 +35,7 @@ from bson import RE_TYPE, _convert_raw_document_lists_to_streams from bson.code import Code from bson.son import SON -from pymongo import _csot, helpers_shared +from pymongo import helpers_shared from pymongo.collation import validate_collation_or_none from pymongo.common import ( validate_is_document_type, @@ -44,9 +43,7 @@ ) from pymongo.cursor_shared import _CURSOR_CLOSED_ERRORS, _QUERY_OPTIONS, CursorType, _Hint, _Sort from pymongo.errors import ConnectionFailure, InvalidOperation, OperationFailure -from pymongo.lock import _create_lock from pymongo.message import ( - _CursorAddress, _GetMore, _OpMsg, _OpReply, @@ -55,6 +52,7 @@ _RawBatchQuery, ) from pymongo.response import PinnedResponse +from pymongo.synchronous.cursor_base import _ConnectionManager, _CursorBase from pymongo.synchronous.helpers import next from pymongo.typings import _Address, _CollationIn, _DocumentOut, _DocumentType from pymongo.write_concern import validate_boolean @@ -66,30 +64,11 @@ from pymongo.read_preferences import _ServerMode from pymongo.synchronous.client_session import ClientSession from pymongo.synchronous.collection import Collection - from pymongo.synchronous.pool import Connection _IS_SYNC = True -class _ConnectionManager: - """Used with exhaust cursors to ensure the connection is returned.""" - - def __init__(self, conn: Connection, more_to_come: bool): - self.conn: Optional[Connection] = conn - self.more_to_come = more_to_come - self._lock = _create_lock() - - def update_exhaust(self, more_to_come: bool) -> None: - self.more_to_come = more_to_come - - def close(self) -> None: - """Return this instance's connection to the connection pool.""" - if self.conn: - self.conn.unpin() - self.conn = None - - -class Cursor(Generic[_DocumentType]): +class Cursor(_CursorBase[_DocumentType]): _query_class = _Query _getmore_class = _GetMore @@ -266,8 +245,8 @@ def retrieved(self) -> int: """The number of documents retrieved so far.""" return self._retrieved - def __del__(self) -> None: - self._die_no_lock() + def _get_namespace(self) -> str: + return f"{self._dbname}.{self._collname}" def clone(self) -> Cursor[_DocumentType]: """Get a clone of this cursor. @@ -897,55 +876,6 @@ def _get_read_preference(self) -> _ServerMode: self._read_preference = self._collection._read_preference_for(self.session) return self._read_preference - @property - def alive(self) -> bool: - """Does this cursor have the potential to return more data? - - This is mostly useful with `tailable cursors - `_ - since they will stop iterating even though they *may* return more - results in the future. - - With regular cursors, simply use a for loop instead of :attr:`alive`:: - - for doc in collection.find(): - print(doc) - - .. note:: Even if :attr:`alive` is True, :meth:`next` can raise - :exc:`StopIteration`. :attr:`alive` can also be True while iterating - a cursor from a failed server. In this case :attr:`alive` will - return False after :meth:`next` fails to retrieve the next batch - of results from the server. - """ - return bool(len(self._data) or (not self._killed)) - - @property - def cursor_id(self) -> Optional[int]: - """Returns the id of the cursor - - .. versionadded:: 2.2 - """ - return self._id - - @property - def address(self) -> Optional[tuple[str, Any]]: - """The (host, port) of the server used, or None. - - .. versionchanged:: 3.0 - Renamed from "conn_id". - """ - return self._address - - @property - def session(self) -> Optional[ClientSession]: - """The cursor's :class:`~pymongo.client_session.ClientSession`, or None. - - .. versionadded:: 3.6 - """ - if self._session and not self._session._implicit: - return self._session - return None - def __copy__(self) -> Cursor[_DocumentType]: """Support function for `copy.copy()`. @@ -1009,59 +939,6 @@ def _deepcopy( y[key] = value # type:ignore[index] return y - def _prepare_to_die(self, already_killed: bool) -> tuple[int, Optional[_CursorAddress]]: - self._killed = True - if self._id and not already_killed: - cursor_id = self._id - assert self._address is not None - address = _CursorAddress(self._address, f"{self._dbname}.{self._collname}") - else: - # Skip killCursors. - cursor_id = 0 - address = None - return cursor_id, address - - def _die_no_lock(self) -> None: - """Closes this cursor without acquiring a lock.""" - try: - already_killed = self._killed - except AttributeError: - # ___init__ did not run to completion (or at all). - return - - cursor_id, address = self._prepare_to_die(already_killed) - self._collection.database.client._cleanup_cursor_no_lock( - cursor_id, address, self._sock_mgr, self._session - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - - def _die_lock(self) -> None: - """Closes this cursor.""" - try: - already_killed = self._killed - except AttributeError: - # ___init__ did not run to completion (or at all). - return - - cursor_id, address = self._prepare_to_die(already_killed) - self._collection.database.client._cleanup_cursor_lock( - cursor_id, - address, - self._sock_mgr, - self._session, - ) - if self._session and self._session._implicit: - self._session._attached_to_cursor = False - self._session = None - self._sock_mgr = None - - def close(self) -> None: - """Explicitly close / kill this cursor.""" - self._die_lock() - def distinct(self, key: str) -> list[Any]: """Get a list of distinct values for `key` among all documents in the result set of this query. @@ -1294,40 +1171,8 @@ def __iter__(self) -> Cursor[_DocumentType]: def __enter__(self) -> Cursor[_DocumentType]: return self - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self.close() - - @_csot.apply - def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: - """Converts the contents of this cursor to a list more efficiently than ``[doc for doc in cursor]``. - To use:: - - >>> cursor.to_list() - - Or, to read at most n items from the cursor:: - - >>> cursor.to_list(n) - - If the cursor is empty or has no more results, an empty list will be returned. - - .. versionadded:: 4.9 - """ - res: list[_DocumentType] = [] - remaining = length - if isinstance(length, int) and length < 1: - raise ValueError("to_list() length must be greater than 0") - while self.alive: - if not self._next_batch(res, remaining): - break - if length is not None: - remaining = length - len(res) - if remaining == 0: - break - return res - - -class RawBatchCursor(Cursor, Generic[_DocumentType]): # type: ignore[type-arg] +class RawBatchCursor(Cursor[_DocumentType]): """A cursor / iterator over raw batches of BSON data from a query result.""" _query_class = _RawBatchQuery diff --git a/pymongo/synchronous/cursor_base.py b/pymongo/synchronous/cursor_base.py new file mode 100644 index 0000000000..52ada613c7 --- /dev/null +++ b/pymongo/synchronous/cursor_base.py @@ -0,0 +1,122 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you +# may not use this file except in compliance with the License. You +# may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. + +"""Synchronous cursor base extending the shared agnostic cursor base.""" +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Any, Optional + +from pymongo import _csot +from pymongo.cursor_shared import _AgnosticCursorBase +from pymongo.lock import _create_lock +from pymongo.typings import _DocumentType + +if TYPE_CHECKING: + from pymongo.synchronous.client_session import ClientSession + from pymongo.synchronous.pool import Connection + +_IS_SYNC = True + + +class _ConnectionManager: + """Used with exhaust cursors to ensure the connection is returned.""" + + def __init__(self, conn: Connection, more_to_come: bool): + self.conn: Optional[Connection] = conn + self.more_to_come = more_to_come + self._lock = _create_lock() + + def update_exhaust(self, more_to_come: bool) -> None: + self.more_to_come = more_to_come + + def close(self) -> None: + """Return this instance's connection to the connection pool.""" + if self.conn: + self.conn.unpin() + self.conn = None + + +class _CursorBase(_AgnosticCursorBase[_DocumentType]): + """Synchronous cursor base class.""" + + @property + def session(self) -> Optional[ClientSession]: + """The cursor's :class:`~pymongo.client_session.ClientSession`, or None. + + .. versionadded:: 3.6 + """ + if self._session and not self._session._implicit: + return self._session + return None + + @abstractmethod + def _next_batch(self, result: list, total: Optional[int] = None) -> bool: # type: ignore[type-arg] + ... + + def _die_lock(self) -> None: + """Closes this cursor.""" + try: + already_killed = self._killed + except AttributeError: + # ___init__ did not run to completion (or at all). + return + + cursor_id, address = self._prepare_to_die(already_killed) + self._collection.database.client._cleanup_cursor_lock( + cursor_id, + address, + self._sock_mgr, + self._session, + ) + if self._session and self._session._implicit: + self._session._attached_to_cursor = False + self._session = None + self._sock_mgr = None + + def close(self) -> None: + """Explicitly close / kill this cursor.""" + self._die_lock() + + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + self.close() + + @_csot.apply + def to_list(self, length: Optional[int] = None) -> list[_DocumentType]: + """Converts the contents of this cursor to a list more efficiently than ``[doc for doc in cursor]``. + + To use:: + + >>> cursor.to_list() + + Or, to read at most n items from the cursor:: + + >>> cursor.to_list(n) + + If the cursor is empty or has no more results, an empty list will be returned. + + .. versionadded:: 4.9 + """ + res: list[_DocumentType] = [] + remaining = length + if isinstance(length, int) and length < 1: + raise ValueError("to_list() length must be greater than 0") + while self.alive: + if not self._next_batch(res, remaining): + break + if length is not None: + remaining = length - len(res) + if remaining == 0: + break + return res diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 139fe9c50d..cd0d19141f 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -141,7 +141,7 @@ from pymongo.server_selectors import Selection from pymongo.synchronous.bulk import _Bulk from pymongo.synchronous.client_session import ClientSession, _ServerSession - from pymongo.synchronous.cursor import _ConnectionManager + from pymongo.synchronous.cursor_base import _ConnectionManager from pymongo.synchronous.encryption import _Encrypter from pymongo.synchronous.pool import Connection from pymongo.synchronous.server import Server diff --git a/tools/synchro.py b/tools/synchro.py index 1444b22994..5735d0052a 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -30,6 +30,7 @@ replacements = { "AsyncCollection": "Collection", "AsyncDatabase": "Database", + "_AsyncCursorBase": "_CursorBase", "AsyncCursor": "Cursor", "AsyncMongoClient": "MongoClient", "AsyncCommandCursor": "CommandCursor", From e077ebd9268814bae19b67ca72e24e66f916f4df Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:44:16 -0500 Subject: [PATCH 073/129] [Spec Resync] 02-02-2026 (#2694) Co-authored-by: Cloud User --- .../read/DeprioritizedNearestStateChange.json | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestStateChange.json diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestStateChange.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestStateChange.json new file mode 100644 index 0000000000..f1560bc117 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestStateChange.json @@ -0,0 +1,78 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 25, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 50, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 25, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 25, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} From afc884d786160481cb171557cf95582b33b1002f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 5 Feb 2026 13:52:10 -0600 Subject: [PATCH 074/129] PYTHON-5467 Add codecov integration (#2690) --- .evergreen/config.yml | 1 + .evergreen/generated_configs/functions.yml | 18 ++++++++++ .evergreen/generated_configs/variants.yml | 3 ++ .evergreen/scripts/generate_config.py | 22 ++++++++++-- .evergreen/scripts/upload-codecov.sh | 42 ++++++++++++++++++++++ .github/workflows/test-python.yml | 29 +++++++++++++++ .gitignore | 1 + pyproject.toml | 6 +++- 8 files changed, 119 insertions(+), 3 deletions(-) create mode 100755 .evergreen/scripts/upload-codecov.sh diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 91fa442775..1af19857c1 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -38,6 +38,7 @@ post: # Disabled, causing timeouts # - func: "upload working dir" - func: "teardown system" + - func: "upload codecov" - func: "upload coverage" - func: "upload mo artifacts" - func: "upload test results" diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index bd983abb3e..6fcda5e985 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -252,6 +252,24 @@ functions: - TOOLCHAIN_VERSION type: test + # Upload coverage codecov + upload codecov: + - command: subprocess.exec + params: + binary: bash + args: + - .evergreen/scripts/upload-codecov.sh + working_dir: src + include_expansions_in_env: + - CODECOV_TOKEN + - build_variant + - task_name + - github_commit + - github_pr_number + - github_pr_head_branch + - github_author + type: test + # Upload coverage upload coverage: - command: ec2.assume_role diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 42a6776092..edca050240 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -367,6 +367,9 @@ buildvariants: display_name: No C Ext RHEL8 run_on: - rhel87-small + expansions: + COVERAGE: "1" + NO_EXT: "1" # No server tests - name: no-server-rhel8 diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 04579c521f..405125021f 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -318,10 +318,10 @@ def create_green_framework_variants(): def create_no_c_ext_variants(): host = DEFAULT_HOST tasks = [".test-standard"] - expansions = dict() + expansions = dict(COVERAGE="1") handle_c_ext(C_EXTS[0], expansions) display_name = get_variant_name("No C Ext", host) - return [create_variant(tasks, display_name, host=host)] + return [create_variant(tasks, display_name, host=host, expansions=expansions)] def create_mod_wsgi_variants(): @@ -1077,6 +1077,24 @@ def create_upload_coverage_func(): return "upload coverage", [get_assume_role(), cmd] +def create_upload_coverage_codecov_func(): + # Upload the coverage xml report to codecov. + include_expansions = [ + "CODECOV_TOKEN", + "build_variant", + "task_name", + "github_commit", + "github_pr_number", + "github_pr_head_branch", + "github_author", + ] + args = [ + ".evergreen/scripts/upload-codecov.sh", + ] + upload_cmd = get_subprocess_exec(include_expansions_in_env=include_expansions, args=args) + return "upload codecov", [upload_cmd] + + def create_download_and_merge_coverage_func(): include_expansions = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] args = [ diff --git a/.evergreen/scripts/upload-codecov.sh b/.evergreen/scripts/upload-codecov.sh new file mode 100755 index 0000000000..a7fdb03711 --- /dev/null +++ b/.evergreen/scripts/upload-codecov.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# shellcheck disable=SC2154 +# Upload a coverate report to codecov. +set -eu + +HERE=$(dirname ${BASH_SOURCE:-$0}) +ROOT=$(dirname "$(dirname $HERE)") + +pushd $ROOT > /dev/null +export FNAME=coverage.xml + +if [ -z "${github_pr_number:-}" ]; then + echo "This is not a PR, not running codecov" + exit 0 +fi + +if [ ! -f ".coverage" ]; then + echo "There are no XML test results, not running codecov" + exit 0 +fi + +echo "Uploading..." +printf 'pr: %s\n' "$github_pr_number" +printf 'sha: %s\n' "$github_commit" +printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" +printf 'flag: %s-%s\n' "$build_variant" "$task_name" +printf 'file: %s\n' "$FNAME" +uv tool run --with "coverage[toml]" coverage xml +uv tool run --from codecov-cli codecovcli upload-process \ + --report-type coverage \ + --disable-search \ + --fail-on-error \ + --git-service github \ + --token ${CODECOV_TOKEN} \ + --pr ${github_pr_number} \ + --sha ${github_commit} \ + --branch "${github_author}:${github_pr_head_branch}" \ + --flag "${build_variant}-${task_name}" \ + --file $FNAME +echo "Uploading...done." + +popd > /dev/null diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 086e22faec..5c0bbe08eb 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -79,6 +79,35 @@ jobs: - name: Run tests run: uv run --extra test pytest -v + coverage: + # This enables a coverage report for a given PR, which will be augmented by + # the combined codecov report uploaded in Evergreen. + runs-on: ubuntu-latest + + name: Coverage + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - name: Install uv + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + with: + enable-cache: true + python-version: "3.10" + - id: setup-mongodb + uses: mongodb-labs/drivers-evergreen-tools@master + with: + version: "8.0" + - name: Install just + run: uv tool install rust-just + - name: Setup tests + run: COVERAGE=1 just setup-tests + - name: Run tests + run: just run-tests + - name: Generate xml report + run: uv tool run --with "coverage[toml]" coverage xml + - name: Upload test results to Codecov + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 doctest: runs-on: ubuntu-latest name: DocTest diff --git a/.gitignore b/.gitignore index 74ed0bbb70..cb4940a55e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ test/lambda/*.json # test results and logs xunit-results/ +coverage.xml server.log diff --git a/pyproject.toml b/pyproject.toml index 65cbeca8b4..acc9fa5b0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -239,7 +239,11 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?)|dummy.*)$" [tool.coverage.run] branch = true -source = ["pymongo", "bson", "gridfs" ] +include = [ + "pymongo/*", + "bson/*", + "gridfs/*" +] relative_files = true [tool.coverage.report] From d5e1777732e71b75a7860ba05a44e76de10b39d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 09:01:06 -0500 Subject: [PATCH 075/129] Bump astral-sh/setup-uv from 7.2.0 to 7.2.1 in the actions group (#2700) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test-python.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 5c0bbe08eb..3f8090d35e 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -90,7 +90,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -116,7 +116,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -141,7 +141,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -160,7 +160,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -182,7 +182,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -203,7 +203,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -293,7 +293,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: python-version: "3.9" - id: setup-mongodb From f28ab12db0bba5bbfa0006fda09bacb2aa6be838 Mon Sep 17 00:00:00 2001 From: Casey Clements Date: Fri, 6 Feb 2026 09:08:00 -0500 Subject: [PATCH 076/129] PYTHON-XXXX Fixed typo in Running Tests Locally section. (#2698) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eaf05111de..eb1c35fc8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,7 +197,7 @@ the pages will re-render and the browser will automatically refresh. version of Python, set `UV_PYTHON` before running `just install`. - 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-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/evergreen/run-orchestration.sh -h` + [`run-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/.evergreen/run-orchestration.sh -h` for a full list of options. - Run `just test` or `pytest` to run all of the tests. - Append `test/.py::::` to run From b1a0a1f10483a2c55614848479627d58472fa788 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 6 Feb 2026 10:29:37 -0600 Subject: [PATCH 077/129] PYTHON-5467 Fix codecov upload (#2701) --- .github/workflows/test-python.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 3f8090d35e..388f68bbe5 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -108,6 +108,8 @@ jobs: run: uv tool run --with "coverage[toml]" coverage xml - name: Upload test results to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} doctest: runs-on: ubuntu-latest name: DocTest From fdb6a3291fb9b37e113ddf70d862c35969cae139 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2026 13:55:08 -0600 Subject: [PATCH 078/129] PYTHON-5467 Fix codecov upload on Evergreen (#2702) --- .evergreen/generated_configs/functions.yml | 2 + .evergreen/scripts/generate_config.py | 2 + .evergreen/scripts/upload-codecov.sh | 47 ++++++++++++++-------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index 6fcda5e985..58bffbf922 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -268,6 +268,8 @@ functions: - github_pr_number - github_pr_head_branch - github_author + - is_patch + - branch_name type: test # Upload coverage diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 405125021f..3375b9e14e 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -1087,6 +1087,8 @@ def create_upload_coverage_codecov_func(): "github_pr_number", "github_pr_head_branch", "github_author", + "is_patch", + "branch_name", ] args = [ ".evergreen/scripts/upload-codecov.sh", diff --git a/.evergreen/scripts/upload-codecov.sh b/.evergreen/scripts/upload-codecov.sh index a7fdb03711..75bd9d9e21 100755 --- a/.evergreen/scripts/upload-codecov.sh +++ b/.evergreen/scripts/upload-codecov.sh @@ -9,34 +9,49 @@ ROOT=$(dirname "$(dirname $HERE)") pushd $ROOT > /dev/null export FNAME=coverage.xml -if [ -z "${github_pr_number:-}" ]; then - echo "This is not a PR, not running codecov" +if [ -n "${is_patch:-}" ]; then + echo "This is a patch build, not running codecov" exit 0 fi if [ ! -f ".coverage" ]; then - echo "There are no XML test results, not running codecov" + echo "There are no coverage results, not running codecov" exit 0 fi echo "Uploading..." -printf 'pr: %s\n' "$github_pr_number" printf 'sha: %s\n' "$github_commit" -printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" printf 'flag: %s-%s\n' "$build_variant" "$task_name" printf 'file: %s\n' "$FNAME" uv tool run --with "coverage[toml]" coverage xml -uv tool run --from codecov-cli codecovcli upload-process \ - --report-type coverage \ - --disable-search \ - --fail-on-error \ - --git-service github \ - --token ${CODECOV_TOKEN} \ - --pr ${github_pr_number} \ - --sha ${github_commit} \ - --branch "${github_author}:${github_pr_head_branch}" \ - --flag "${build_variant}-${task_name}" \ - --file $FNAME + +codecov_args=( + upload-process + --report-type coverage + --disable-search + --fail-on-error + --git-service github + --token "${CODECOV_TOKEN}" + --sha "${github_commit}" + --flag "${build_variant}-${task_name}" + --file "${FNAME}" +) + +if [ -n "${github_pr_number:-}" ]; then + printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" + printf 'pr: %s\n' "$github_pr_number" + + uv tool run --from codecov-cli codecovcli \ + "${codecov_args[@]}" \ + --pr "${github_pr_number}" \ + --branch "${github_author}:${github_pr_head_branch}" +else + printf 'branch: %s\n' "$branch_name" + + uv tool run --from codecov-cli codecovcli \ + "${codecov_args[@]}" \ + --branch "${branch_name}" +fi echo "Uploading...done." popd > /dev/null From 0441761872fcf4d6060b558b4cdd4a261377bcbf Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2026 14:51:30 -0600 Subject: [PATCH 079/129] PYTHON-5715 Add appName to OIDC test failpoints (#2697) --- test/asynchronous/test_auth_oidc.py | 4 ++-- test/auth/unified/mongodb-oidc-no-retry.json | 18 ++++++++++++------ test/test_auth_oidc.py | 6 ++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/test/asynchronous/test_auth_oidc.py b/test/asynchronous/test_auth_oidc.py index ff604f55ae..3567d7706b 100644 --- a/test/asynchronous/test_auth_oidc.py +++ b/test/asynchronous/test_auth_oidc.py @@ -104,7 +104,7 @@ def get_token(self, username=None): @asynccontextmanager async def fail_point(self, command_args): - cmd_on = SON([("configureFailPoint", "failCommand")]) + cmd_on = dict(configureFailPoint="failCommand", appName="auth_oidc") cmd_on.update(command_args) client = AsyncMongoClient(self.uri_admin) await client.admin.command(cmd_on) @@ -112,7 +112,7 @@ async def fail_point(self, command_args): yield finally: await client.admin.command( - "configureFailPoint", cmd_on["configureFailPoint"], mode="off" + "configureFailPoint", cmd_on["configureFailPoint"], mode="off", appName="auth_oidc" ) await client.close() diff --git a/test/auth/unified/mongodb-oidc-no-retry.json b/test/auth/unified/mongodb-oidc-no-retry.json index 0a8658455e..b32ada172a 100644 --- a/test/auth/unified/mongodb-oidc-no-retry.json +++ b/test/auth/unified/mongodb-oidc-no-retry.json @@ -25,7 +25,8 @@ "$$placeholder": 1 }, "retryReads": false, - "retryWrites": false + "retryWrites": false, + "appName": "mongodb-oidc-no-retry" }, "observeEvents": [ "commandStartedEvent", @@ -147,7 +148,8 @@ "failCommands": [ "find" ], - "errorCode": 391 + "errorCode": 391, + "appName": "mongodb-oidc-no-retry" } } } @@ -212,7 +214,8 @@ "failCommands": [ "insert" ], - "errorCode": 391 + "errorCode": 391, + "appName": "mongodb-oidc-no-retry" } } } @@ -289,7 +292,8 @@ "failCommands": [ "insert" ], - "closeConnection": true + "closeConnection": true, + "appName": "mongodb-oidc-no-retry" } } } @@ -321,7 +325,8 @@ "failCommands": [ "saslStart" ], - "errorCode": 18 + "errorCode": 18, + "appName": "mongodb-oidc-no-retry" } } } @@ -398,7 +403,8 @@ "failCommands": [ "saslStart" ], - "errorCode": 18 + "errorCode": 18, + "appName": "mongodb-oidc-no-retry" } } } diff --git a/test/test_auth_oidc.py b/test/test_auth_oidc.py index 1defe82006..e88e067b2c 100644 --- a/test/test_auth_oidc.py +++ b/test/test_auth_oidc.py @@ -104,14 +104,16 @@ def get_token(self, username=None): @contextmanager def fail_point(self, command_args): - cmd_on = SON([("configureFailPoint", "failCommand")]) + cmd_on = dict(configureFailPoint="failCommand", appName="auth_oidc") cmd_on.update(command_args) client = MongoClient(self.uri_admin) client.admin.command(cmd_on) try: yield finally: - client.admin.command("configureFailPoint", cmd_on["configureFailPoint"], mode="off") + client.admin.command( + "configureFailPoint", cmd_on["configureFailPoint"], mode="off", appName="auth_oidc" + ) client.close() From 36676384bd62782be3e752b735d7e7dfe87d5748 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2026 19:39:05 -0600 Subject: [PATCH 080/129] PYTHON-5705 Improve fallback for PyOpenSSL windows system certs loading (#2688) --- pymongo/pyopenssl_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/pyopenssl_context.py b/pymongo/pyopenssl_context.py index 67456d5537..941d91cd1b 100644 --- a/pymongo/pyopenssl_context.py +++ b/pymongo/pyopenssl_context.py @@ -357,7 +357,7 @@ def load_default_certs(self) -> None: try: for storename in ("CA", "ROOT"): self._load_wincerts(storename) - except PermissionError: + except Exception: # Fall back to certifi self._load_certifi() elif _sys.platform == "darwin": From b60d266ad772b7a6b872fbef1aeebb722d78a855 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 17 Feb 2026 12:23:34 -0600 Subject: [PATCH 081/129] PYTHON-3898 Add coverage to all variants (#2705) --- .codecov.yml | 4 + .evergreen/generated_configs/functions.yml | 3 +- .evergreen/generated_configs/tasks.yml | 139 +++++++++------------ .evergreen/generated_configs/variants.yml | 3 +- .evergreen/scripts/generate_config.py | 40 ++++-- .evergreen/scripts/run_tests.py | 12 ++ .evergreen/scripts/setup_tests.py | 4 +- .evergreen/scripts/upload-codecov.sh | 18 +-- .evergreen/scripts/utils.py | 1 + justfile | 5 +- pyproject.toml | 1 - test/asynchronous/test_ssl.py | 17 +-- test/test_ssl.py | 17 +-- uv.lock | 21 +--- 14 files changed, 129 insertions(+), 156 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000000..5cbc2d3f5f --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,4 @@ +# do not notify until at least 100 builds have been uploaded from the CI pipeline +# you can also set after_n_builds on comments independently +comment: + after_n_builds: 100 diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index 58bffbf922..9fbc1f8500 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -250,6 +250,7 @@ functions: working_dir: src include_expansions_in_env: - TOOLCHAIN_VERSION + - COVERAGE type: test # Upload coverage codecov @@ -268,7 +269,7 @@ functions: - github_pr_number - github_pr_head_branch - github_author - - is_patch + - requester - branch_name type: test diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index 60ee6ed135..6a460ce486 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -75,7 +75,7 @@ tasks: SUB_TEST_NAME: session-creds TOOLCHAIN_VERSION: 3.14t tags: [auth-aws, auth-aws-session-creds, free-threaded] - - name: test-auth-aws-rapid-web-identity-python3.14 + - name: test-auth-aws-rapid-web-identity-python3.14-cov commands: - func: run server vars: @@ -87,7 +87,8 @@ tasks: TEST_NAME: auth_aws SUB_TEST_NAME: web-identity TOOLCHAIN_VERSION: "3.14" - tags: [auth-aws, auth-aws-web-identity] + COVERAGE: "1" + tags: [auth-aws, auth-aws-web-identity, pr] - name: test-auth-aws-rapid-web-identity-session-name-python3.14 commands: - func: run server @@ -904,7 +905,7 @@ tasks: - ocsp-ecdsa - rapid - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-latest-python3.14 + - name: test-ocsp-ecdsa-valid-cert-server-staples-latest-python3.14-cov commands: - func: run tests vars: @@ -913,11 +914,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.14" VERSION: latest + COVERAGE: "1" tags: - ocsp - ocsp-ecdsa - latest - ocsp-staple + - pr - name: test-ocsp-ecdsa-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests @@ -1928,7 +1931,7 @@ tasks: - ocsp-rsa - rapid - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-latest-python3.14 + - name: test-ocsp-rsa-valid-cert-server-staples-latest-python3.14-cov commands: - func: run tests vars: @@ -1937,11 +1940,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.14" VERSION: latest + COVERAGE: "1" tags: - ocsp - ocsp-rsa - latest - ocsp-staple + - pr - name: test-ocsp-rsa-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests @@ -2615,20 +2620,18 @@ tasks: - replica_set-auth-nossl - async - free-threaded - - name: test-server-version-python3.13-sync-auth-nossl-replica-set-cov + - name: test-server-version-python3.13-sync-auth-nossl-replica-set commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: @@ -2636,20 +2639,18 @@ tasks: - python-3.13 - replica_set-auth-nossl - sync - - name: test-server-version-python3.12-async-auth-ssl-replica-set-cov + - name: test-server-version-python3.12-async-auth-ssl-replica-set commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -2657,20 +2658,18 @@ tasks: - python-3.12 - replica_set-auth-ssl - async - - name: test-server-version-python3.11-sync-auth-ssl-replica-set-cov + - name: test-server-version-python3.11-sync-auth-ssl-replica-set commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: @@ -2743,20 +2742,18 @@ tasks: - python-pypy3.11 - replica_set-noauth-ssl - async - - name: test-server-version-python3.14-sync-noauth-ssl-replica-set-cov + - name: test-server-version-python3.14-sync-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: @@ -2764,20 +2761,18 @@ tasks: - python-3.14 - replica_set-noauth-ssl - sync - - name: test-server-version-python3.14-async-auth-nossl-sharded-cluster-cov + - name: test-server-version-python3.14-async-auth-nossl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -2829,20 +2824,18 @@ tasks: - sharded_cluster-auth-ssl - async - pr - - name: test-server-version-python3.11-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.11-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: @@ -2850,20 +2843,18 @@ tasks: - python-3.11 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.12-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.12-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -2871,20 +2862,18 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.13-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.13-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: @@ -2892,20 +2881,18 @@ tasks: - python-3.13 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.14-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.14-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -2976,20 +2963,18 @@ tasks: - sharded_cluster-auth-ssl - sync - pr - - name: test-server-version-python3.11-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.11-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: @@ -2997,20 +2982,18 @@ tasks: - python-3.11 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.12-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.12-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: @@ -3018,20 +3001,18 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.13-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.13-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: @@ -3039,20 +3020,18 @@ tasks: - python-3.13 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.14-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.14-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: @@ -3099,20 +3078,18 @@ tasks: - python-pypy3.11 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.12-async-noauth-nossl-sharded-cluster-cov + - name: test-server-version-python3.12-async-noauth-nossl-sharded-cluster commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -3120,20 +3097,18 @@ tasks: - python-3.12 - sharded_cluster-noauth-nossl - async - - name: test-server-version-python3.11-sync-noauth-nossl-sharded-cluster-cov + - name: test-server-version-python3.11-sync-noauth-nossl-sharded-cluster commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: @@ -3141,7 +3116,7 @@ tasks: - python-3.11 - sharded_cluster-noauth-nossl - sync - - name: test-server-version-python3.10-async-noauth-ssl-sharded-cluster-min-deps-cov + - name: test-server-version-python3.10-async-noauth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -3149,14 +3124,12 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster TEST_MIN_DEPS: "1" - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: sharded_cluster TEST_MIN_DEPS: "1" - COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: @@ -3183,20 +3156,18 @@ tasks: - python-pypy3.11 - sharded_cluster-noauth-ssl - sync - - name: test-server-version-python3.13-async-auth-nossl-standalone-cov + - name: test-server-version-python3.13-async-auth-nossl-standalone commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: @@ -3204,20 +3175,18 @@ tasks: - python-3.13 - standalone-auth-nossl - async - - name: test-server-version-python3.12-sync-auth-nossl-standalone-cov + - name: test-server-version-python3.12-sync-auth-nossl-standalone commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: @@ -3225,20 +3194,18 @@ tasks: - python-3.12 - standalone-auth-nossl - sync - - name: test-server-version-python3.11-async-auth-ssl-standalone-cov + - name: test-server-version-python3.11-async-auth-ssl-standalone commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: @@ -3246,7 +3213,7 @@ tasks: - python-3.11 - standalone-auth-ssl - async - - name: test-server-version-python3.10-sync-auth-ssl-standalone-min-deps-cov + - name: test-server-version-python3.10-sync-auth-ssl-standalone-min-deps commands: - func: run server vars: @@ -3254,14 +3221,12 @@ tasks: SSL: ssl TOPOLOGY: standalone TEST_MIN_DEPS: "1" - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: standalone TEST_MIN_DEPS: "1" - COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: @@ -3293,18 +3258,20 @@ tasks: - standalone-noauth-nossl - async - pr - - name: test-server-version-pypy3.11-sync-noauth-nossl-standalone + - name: test-server-version-pypy3.11-sync-noauth-nossl-standalone-cov commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone + COVERAGE: "1" TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: @@ -3313,20 +3280,18 @@ tasks: - standalone-noauth-nossl - sync - pr - - name: test-server-version-python3.14-async-noauth-ssl-standalone-cov + - name: test-server-version-python3.14-async-noauth-ssl-standalone commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -4082,7 +4047,7 @@ tasks: - standalone-noauth-nossl - async - pypy - - name: test-standard-latest-python3.12-async-noauth-ssl-replica-set + - name: test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov commands: - func: run server vars: @@ -4090,12 +4055,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -4128,7 +4095,7 @@ tasks: - replica_set-noauth-ssl - async - pypy - - name: test-standard-latest-python3.13-async-auth-ssl-sharded-cluster + - name: test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -4136,12 +4103,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: @@ -4151,7 +4120,7 @@ tasks: - sharded_cluster-auth-ssl - async - pr - - name: test-standard-latest-python3.11-async-noauth-nossl-standalone + - name: test-standard-latest-python3.11-async-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4159,12 +4128,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: @@ -4174,7 +4145,7 @@ tasks: - standalone-noauth-nossl - async - pr - - name: test-standard-latest-python3.14-async-noauth-nossl-standalone + - name: test-standard-latest-python3.14-async-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4182,12 +4153,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -4829,7 +4802,7 @@ tasks: - python-3.13 - standalone-noauth-nossl - noauth - - name: test-non-standard-latest-python3.14t-noauth-ssl-replica-set + - name: test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov commands: - func: run server vars: @@ -4837,12 +4810,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard @@ -4874,7 +4849,7 @@ tasks: - replica_set-noauth-ssl - noauth - pypy - - name: test-non-standard-latest-python3.14-auth-ssl-sharded-cluster + - name: test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -4882,12 +4857,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard @@ -4896,7 +4873,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pr - - name: test-non-standard-latest-python3.13-noauth-nossl-standalone + - name: test-non-standard-latest-python3.13-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4904,12 +4881,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard @@ -5007,7 +4986,7 @@ tasks: - pypy # Test numpy tests - - name: test-numpy-python3.10 + - name: test-numpy-python3.10-python3.10 commands: - func: test numpy vars: @@ -5017,16 +4996,18 @@ tasks: - vector - python-3.10 - test-numpy - - name: test-numpy-python3.14 + - name: test-numpy-python3.14-python3.14-cov commands: - func: test numpy vars: TOOLCHAIN_VERSION: "3.14" + COVERAGE: "1" tags: - binary - vector - python-3.14 - test-numpy + - pr # Test standard auth tests - name: test-standard-auth-v4.2-python3.10-auth-ssl-sharded-cluster-min-deps @@ -5290,7 +5271,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster + - name: test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -5298,12 +5279,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" tags: - test-standard-auth diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index edca050240..4c9116d628 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -368,7 +368,6 @@ buildvariants: run_on: - rhel87-small expansions: - COVERAGE: "1" NO_EXT: "1" # No server tests @@ -420,6 +419,8 @@ buildvariants: run_on: - ubuntu2204-small batchtime: 1440 + expansions: + COVERAGE: "1" tags: [pr] - name: auth-oidc-macos tasks: diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 3375b9e14e..b4760eab97 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -318,7 +318,7 @@ def create_green_framework_variants(): def create_no_c_ext_variants(): host = DEFAULT_HOST tasks = [".test-standard"] - expansions = dict(COVERAGE="1") + expansions = dict() handle_c_ext(C_EXTS[0], expansions) display_name = get_variant_name("No C Ext", host) return [create_variant(tasks, display_name, host=host, expansions=expansions)] @@ -344,8 +344,12 @@ def create_test_numpy_tasks(): tasks = [] for python in MIN_MAX_PYTHON: tags = ["binary", "vector", f"python-{python}", "test-numpy"] - task_name = get_task_name("test-numpy", python=python) - test_func = FunctionCall(func="test numpy", vars=dict(TOOLCHAIN_VERSION=python)) + vars = dict(TOOLCHAIN_VERSION=python) + if python == MIN_MAX_PYTHON[-1]: + tags.append("pr") + vars["COVERAGE"] = "1" + task_name = get_task_name("test-numpy", python=python, **vars) + test_func = FunctionCall(func="test numpy", vars=vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) return tasks @@ -397,6 +401,7 @@ def create_oidc_auth_variants(): tags=["pr"], host=host, batchtime=BATCHTIME_DAY, + expansions=dict(COVERAGE="1"), ) ) return variants @@ -596,7 +601,7 @@ def create_server_version_tasks(): expansions["TEST_MIN_DEPS"] = "1" if "t" in python: tags.append("free-threaded") - if python not in PYPYS and "t" not in python: + if "pr" in tags: expansions["COVERAGE"] = "1" name = get_task_name( "test-server-version", @@ -661,6 +666,8 @@ def create_test_non_standard_tasks(): expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) if python == ALL_PYTHONS[0]: expansions["TEST_MIN_DEPS"] = "1" + elif pr: + expansions["COVERAGE"] = "1" name = get_task_name("test-non-standard", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -703,6 +710,8 @@ def create_test_standard_auth_tasks(): expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) if python == ALL_PYTHONS[0]: expansions["TEST_MIN_DEPS"] = "1" + elif pr: + expansions["COVERAGE"] = "1" name = get_task_name("test-standard-auth", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -741,6 +750,8 @@ def create_standard_tasks(): expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) if python == ALL_PYTHONS[0]: expansions["TEST_MIN_DEPS"] = "1" + elif pr: + expansions["COVERAGE"] = "1" name = get_task_name("test-standard", python=python, sync=sync, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -810,8 +821,11 @@ def create_aws_tasks(): if "t" in python: tags.append("free-threaded") test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME=test_type, TOOLCHAIN_VERSION=python) - if python == ALL_PYTHONS[0]: + if python == MIN_MAX_PYTHON[0]: test_vars["TEST_MIN_DEPS"] = "1" + elif python == MIN_MAX_PYTHON[-1]: + tags.append("pr") + test_vars["COVERAGE"] = "1" name = get_task_name(f"{base_name}-{test_type}", **test_vars) test_func = FunctionCall(func="run tests", vars=test_vars) funcs = [server_func, assume_func, test_func] @@ -849,11 +863,11 @@ def create_oidc_tasks(): tasks = [] for sub_test in ["default", "azure", "gcp", "eks", "aks", "gke"]: vars = dict(TEST_NAME="auth_oidc", SUB_TEST_NAME=sub_test) - test_func = FunctionCall(func="run tests", vars=vars) - task_name = f"test-auth-oidc-{sub_test}" tags = ["auth_oidc"] if sub_test != "default": tags.append("auth_oidc_remote") + test_func = FunctionCall(func="run tests", vars=vars) + task_name = get_task_name(f"test-auth-oidc-{sub_test}", **vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) return tasks @@ -903,14 +917,14 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name): ) if python == ALL_PYTHONS[0]: vars["TEST_MIN_DEPS"] = "1" - test_func = FunctionCall(func="run tests", vars=vars) - tags = ["ocsp", f"ocsp-{algo}", version] if "disableStapling" not in variant: tags.append("ocsp-staple") - if algo == "valid-cert-server-staples" and version == "latest": + if base_task_name == "valid-cert-server-staples" and version == "latest": tags.append("pr") - + if "TEST_MIN_DEPS" not in vars: + vars["COVERAGE"] = "1" + test_func = FunctionCall(func="run tests", vars=vars) task_name = get_task_name(f"test-ocsp-{algo}-{base_task_name}", **vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) @@ -1087,7 +1101,7 @@ def create_upload_coverage_codecov_func(): "github_pr_number", "github_pr_head_branch", "github_author", - "is_patch", + "requester", "branch_name", ] args = [ @@ -1230,7 +1244,7 @@ def create_run_tests_func(): def create_test_numpy_func(): - includes = ["TOOLCHAIN_VERSION"] + includes = ["TOOLCHAIN_VERSION", "COVERAGE"] test_cmd = get_subprocess_exec( include_expansions_in_env=includes, args=[".evergreen/just.sh", "test-numpy"] ) diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py index 9c8101c5b1..fcf5fe76cd 100644 --- a/.evergreen/scripts/run_tests.py +++ b/.evergreen/scripts/run_tests.py @@ -4,7 +4,9 @@ import logging import os import platform +import shlex import shutil +import subprocess import sys from datetime import datetime from pathlib import Path @@ -202,6 +204,16 @@ def run() -> None: if os.environ.get("DEBUG_LOG"): TEST_ARGS.extend(f"-o log_cli_level={logging.DEBUG}".split()) + if os.environ.get("COVERAGE"): + binary = sys.executable.replace(os.sep, "/") + cmd = f"{binary} -m coverage run -m pytest {' '.join(TEST_ARGS)} {' '.join(sys.argv[1:])}" + result = subprocess.run(shlex.split(cmd), check=False) # noqa: S603 + cmd = f"{binary} -m coverage report" + subprocess.run(shlex.split(cmd), check=False) # noqa: S603 + if result.returncode != 0: + print(result.stderr) + sys.exit(result.returncode) + # Run local tests. ret = pytest.main(TEST_ARGS + sys.argv[1:]) if ret != 0: diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 939423ffcc..f3d86973b4 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -431,6 +431,9 @@ def handle_test_env() -> None: # We do not want the default client_context to be initialized. write_env("DISABLE_CONTEXT") + if test_name == "numpy": + UV_ARGS.append("--with numpy") + if test_name == "perf": data_dir = ROOT / "specifications/source/benchmarking/data" if not data_dir.exists(): @@ -458,7 +461,6 @@ def handle_test_env() -> None: # Keep in sync with combine-coverage.sh. # coverage >=5 is needed for relative_files=true. UV_ARGS.append("--group coverage") - TEST_ARGS = f"{TEST_ARGS} --cov" write_env("COVERAGE") if opts.green_framework: diff --git a/.evergreen/scripts/upload-codecov.sh b/.evergreen/scripts/upload-codecov.sh index 75bd9d9e21..5c1d84c55c 100755 --- a/.evergreen/scripts/upload-codecov.sh +++ b/.evergreen/scripts/upload-codecov.sh @@ -8,18 +8,20 @@ ROOT=$(dirname "$(dirname $HERE)") pushd $ROOT > /dev/null export FNAME=coverage.xml - -if [ -n "${is_patch:-}" ]; then - echo "This is a patch build, not running codecov" - exit 0 -fi +REQUESTER=${requester:-} if [ ! -f ".coverage" ]; then echo "There are no coverage results, not running codecov" exit 0 fi -echo "Uploading..." +if [[ "${REQUESTER}" == "github_pr" || "${REQUESTER}" == "commit" ]]; then + echo "Uploading codecov for $REQUESTER..." +else + echo "Error: requester must be 'github_pr' or 'commit', got '${REQUESTER}'" >&2 + exit 1 +fi + printf 'sha: %s\n' "$github_commit" printf 'flag: %s-%s\n' "$build_variant" "$task_name" printf 'file: %s\n' "$FNAME" @@ -40,18 +42,16 @@ codecov_args=( if [ -n "${github_pr_number:-}" ]; then printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" printf 'pr: %s\n' "$github_pr_number" - uv tool run --from codecov-cli codecovcli \ "${codecov_args[@]}" \ --pr "${github_pr_number}" \ --branch "${github_author}:${github_pr_head_branch}" else printf 'branch: %s\n' "$branch_name" - uv tool run --from codecov-cli codecovcli \ "${codecov_args[@]}" \ --branch "${branch_name}" fi -echo "Uploading...done." +echo "Uploading codecov for $REQUESTER... done." popd > /dev/null diff --git a/.evergreen/scripts/utils.py b/.evergreen/scripts/utils.py index 2bc9c720d2..02238645ce 100644 --- a/.evergreen/scripts/utils.py +++ b/.evergreen/scripts/utils.py @@ -44,6 +44,7 @@ class Distro: "mockupdb": "mockupdb", "ocsp": "ocsp", "perf": "perf", + "numpy": "", } # Tests that require a sub test suite. diff --git a/justfile b/justfile index 082b6ea170..82b1ac91dc 100644 --- a/justfile +++ b/justfile @@ -60,8 +60,9 @@ test *args="-v --durations=5 --maxfail=10": && resync uv run --extra test python -m pytest {{args}} [group('test')] -test-numpy: && resync - uv run --extra test --with numpy python -m pytest test/test_bson.py +test-numpy *args="": && resync + just setup-tests numpy {{args}} + just run-tests test/test_bson.py [group('test')] run-tests *args: && resync diff --git a/pyproject.toml b/pyproject.toml index acc9fa5b0d..9b3287834a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,6 @@ dev = [] pip = ["pip>=20.2"] gevent = ["gevent>=21.12"] coverage = [ - "pytest-cov>=4.0.0", "coverage[toml]>=5,<=7.10.7" ] mockupdb = [ diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index 0ce3e8bbac..7fe57e8503 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -48,19 +48,11 @@ _HAVE_PYOPENSSL = False try: - # All of these must be available to use PyOpenSSL - import OpenSSL - import requests - import service_identity - - # Ensure service_identity>=18.1 is installed - from service_identity.pyopenssl import verify_ip_address - - from pymongo.ocsp_support import _load_trusted_ca_certs + from pymongo import pyopenssl_context _HAVE_PYOPENSSL = True except ImportError: - _load_trusted_ca_certs = None # type: ignore + pass if HAVE_SSL: @@ -136,11 +128,6 @@ def test_config_ssl(self): def test_use_pyopenssl_when_available(self): self.assertTrue(HAVE_PYSSL) - @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") - def test_load_trusted_ca_certs(self): - trusted_ca_certs = _load_trusted_ca_certs(CA_BUNDLE_PEM) - self.assertEqual(2, len(trusted_ca_certs)) - class TestSSL(AsyncIntegrationTest): saved_port: int diff --git a/test/test_ssl.py b/test/test_ssl.py index b1e9a65eb5..77bb086ecb 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -48,19 +48,11 @@ _HAVE_PYOPENSSL = False try: - # All of these must be available to use PyOpenSSL - import OpenSSL - import requests - import service_identity - - # Ensure service_identity>=18.1 is installed - from service_identity.pyopenssl import verify_ip_address - - from pymongo.ocsp_support import _load_trusted_ca_certs + from pymongo import pyopenssl_context _HAVE_PYOPENSSL = True except ImportError: - _load_trusted_ca_certs = None # type: ignore + pass if HAVE_SSL: @@ -136,11 +128,6 @@ def test_config_ssl(self): def test_use_pyopenssl_when_available(self): self.assertTrue(HAVE_PYSSL) - @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") - def test_load_trusted_ca_certs(self): - trusted_ca_certs = _load_trusted_ca_certs(CA_BUNDLE_PEM) - self.assertEqual(2, len(trusted_ca_certs)) - class TestSSL(IntegrationTest): saved_port: int diff --git a/uv.lock b/uv.lock index 7d0ff9fb0f..78d0cc213f 100644 --- a/uv.lock +++ b/uv.lock @@ -1562,7 +1562,6 @@ zstd = [ [package.dev-dependencies] coverage = [ { name = "coverage", extra = ["toml"] }, - { name = "pytest-cov" }, ] gevent = [ { name = "gevent" }, @@ -1612,10 +1611,7 @@ requires-dist = [ provides-extras = ["aws", "docs", "encryption", "gssapi", "ocsp", "snappy", "test", "zstd"] [package.metadata.requires-dev] -coverage = [ - { name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.7" }, - { name = "pytest-cov", specifier = ">=4.0.0" }, -] +coverage = [{ name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.7" }] dev = [] gevent = [{ name = "gevent", specifier = ">=21.12" }] mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mockup-db?rev=master" }] @@ -1763,21 +1759,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] -[[package]] -name = "pytest-cov" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" From 6923641626216a58fb3a8132ab04cbd7ae260c2f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 18 Feb 2026 14:42:00 -0600 Subject: [PATCH 082/129] PYTHON-5729 Pin setuptools when using older gevent (#2708) --- .evergreen/run-tests.sh | 1 + .evergreen/scripts/setup_tests.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 095b7938dc..0785bcf01d 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -38,6 +38,7 @@ trap "cleanup_tests" SIGINT ERR # Start the test runner. echo "Running tests with UV_PYTHON=${UV_PYTHON:-}..." +echo "UV_ARGS=${UV_ARGS}" uv run ${UV_ARGS} --reinstall-package pymongo .evergreen/scripts/run_tests.py "$@" echo "Running tests with UV_PYTHON=${UV_PYTHON:-}... done." diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index f3d86973b4..cdecb259a1 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -466,6 +466,9 @@ def handle_test_env() -> None: if opts.green_framework: framework = opts.green_framework or os.environ["GREEN_FRAMEWORK"] UV_ARGS.append(f"--group {framework}") + if framework == "gevent" and opts.test_min_deps: + # PYTHON-5729. This can be removed when the min supported gevent is moved to 25.9.1. + UV_ARGS.append('--with "setuptools==81.0"') else: TEST_ARGS = f"-v --durations=5 {TEST_ARGS}" From cbd82e75e7e236f8e2fd087c6f6df894c859574d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:26:49 -0600 Subject: [PATCH 083/129] Bump the actions group with 2 updates (#2711) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test-python.yml | 18 +++++++++--------- .github/workflows/zizmor.yml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 388f68bbe5..e8fb9b9185 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -90,7 +90,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -118,7 +118,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -143,7 +143,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -162,7 +162,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -184,7 +184,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -205,7 +205,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -295,7 +295,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: python-version: "3.9" - id: setup-mongodb diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 26f75fa792..c64a9e32ed 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1 + uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 From edd0e0698f827f4d274703590d43da2e3f98fbc0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 20 Feb 2026 11:56:30 -0600 Subject: [PATCH 084/129] PYTHON-5708 Temporarily skip some BSON encryption tests (#2709) --- test/asynchronous/test_encryption.py | 4 ++++ test/test_encryption.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index b1dbc73f39..9650f7043f 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -876,6 +876,8 @@ async def test_views_are_prohibited(self): class TestCorpus(AsyncEncryptionIntegrationTest): + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @async_client_context.require_version_max(6, 99) @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") async def asyncSetUp(self): await super().asyncSetUp() @@ -1052,6 +1054,8 @@ class TestBsonSizeBatches(AsyncEncryptionIntegrationTest): client_encrypted: AsyncMongoClient listener: OvertCommandListener + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @async_client_context.require_version_max(6, 99) async def asyncSetUp(self): await super().asyncSetUp() db = async_client_context.client.db diff --git a/test/test_encryption.py b/test/test_encryption.py index 88d37cfa0d..af9f2e3df7 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -872,6 +872,8 @@ def test_views_are_prohibited(self): class TestCorpus(EncryptionIntegrationTest): + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @client_context.require_version_max(6, 99) @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") def setUp(self): super().setUp() @@ -1048,6 +1050,8 @@ class TestBsonSizeBatches(EncryptionIntegrationTest): client_encrypted: MongoClient listener: OvertCommandListener + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @client_context.require_version_max(6, 99) def setUp(self): super().setUp() db = client_context.client.db From 908102d776ee5608f79df76098f6287d6edb6952 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 20 Feb 2026 13:02:52 -0600 Subject: [PATCH 085/129] PYTHON-5732 Use mongodb-runner in Evergreen Tests (#2703) --- .evergreen/scripts/run_server.py | 4 ++-- .evergreen/scripts/setup_tests.py | 3 ++- .evergreen/scripts/stop-server.sh | 4 ++-- .evergreen/scripts/utils.py | 2 +- CONTRIBUTING.md | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.evergreen/scripts/run_server.py b/.evergreen/scripts/run_server.py index a35fbb57a8..9757eb3a4f 100644 --- a/.evergreen/scripts/run_server.py +++ b/.evergreen/scripts/run_server.py @@ -12,7 +12,7 @@ def set_env(name: str, value: Any = "1") -> None: def start_server(): opts, extra_opts = get_test_options( - "Run a MongoDB server. All given flags will be passed to run-orchestration.sh in DRIVERS_TOOLS.", + "Run a MongoDB server. All given flags will be passed to run-mongodb.sh in DRIVERS_TOOLS.", require_sub_test_name=False, allow_extra_opts=True, ) @@ -51,7 +51,7 @@ def start_server(): elif opts.quiet: extra_opts.append("-q") - cmd = ["bash", f"{DRIVERS_TOOLS}/.evergreen/run-orchestration.sh", *extra_opts] + cmd = ["bash", f"{DRIVERS_TOOLS}/.evergreen/run-mongodb.sh", "start", *extra_opts] run_command(cmd, cwd=DRIVERS_TOOLS) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index cdecb259a1..009de9f1d1 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -324,7 +324,8 @@ def handle_test_env() -> None: version = os.environ.get("VERSION", "latest") cmd = [ "bash", - f"{DRIVERS_TOOLS}/.evergreen/run-orchestration.sh", + f"{DRIVERS_TOOLS}/.evergreen/run-mongodb.sh", + "start", "--ssl", "--version", version, diff --git a/.evergreen/scripts/stop-server.sh b/.evergreen/scripts/stop-server.sh index 7599387f5f..045a655cbd 100755 --- a/.evergreen/scripts/stop-server.sh +++ b/.evergreen/scripts/stop-server.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Stop a server that was started using run-orchestration.sh in DRIVERS_TOOLS. +# Stop a server that was started using run-mongodb.sh in DRIVERS_TOOLS. set -eu HERE=$(dirname ${BASH_SOURCE:-$0}) @@ -11,4 +11,4 @@ if [ -f $HERE/env.sh ]; then source $HERE/env.sh fi -bash ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh +bash ${DRIVERS_TOOLS}/.evergreen/run-mongodb.sh stop diff --git a/.evergreen/scripts/utils.py b/.evergreen/scripts/utils.py index 02238645ce..914cd9ac60 100644 --- a/.evergreen/scripts/utils.py +++ b/.evergreen/scripts/utils.py @@ -52,7 +52,7 @@ class Distro: EXTRA_TESTS = ["mod_wsgi", "aws_lambda", "doctest"] -# Tests that do not use run-orchestration directly. +# Tests that do not use run-mongodb directly. NO_RUN_ORCHESTRATION = [ "auth_oidc", "atlas_connect", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb1c35fc8b..a4eafeddcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,7 +197,7 @@ the pages will re-render and the browser will automatically refresh. version of Python, set `UV_PYTHON` before running `just install`. - 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-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/.evergreen/run-orchestration.sh -h` + [`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` for a full list of options. - Run `just test` or `pytest` to run all of the tests. - Append `test/.py::::` to run @@ -396,7 +396,7 @@ To run any of the test suites with minimum supported dependencies, pass `--test- - If adding new tests files that should only be run for that test suite, add a pytest marker to the file and add to the list of pytest markers in `pyproject.toml`. Then add the test suite to the `TEST_SUITE_MAP` in `.evergreen/scripts/utils.py`. If for some reason it is not a pytest-runnable test, add it to the list of `EXTRA_TESTS` instead. -- If the test uses Atlas or otherwise doesn't use `run-orchestration.sh`, add it to the `NO_RUN_ORCHESTRATION` list in +- If the test uses Atlas or otherwise doesn't use `run-mongodb.sh`, add it to the `NO_RUN_ORCHESTRATION` list in `.evergreen/scripts/utils.py`. - If there is something special required to run the local server or there is an extra flag that should always be set like `AUTH`, add that logic to `.evergreen/scripts/run_server.py`. From 84814b2a7275c7f6b8b5221e1ce071b358823e06 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 23 Feb 2026 13:18:24 -0500 Subject: [PATCH 086/129] PYTHON-5731 - Server selection deprioritization only for overload errors on replica sets (#2710) --- pymongo/asynchronous/mongo_client.py | 6 +- pymongo/synchronous/mongo_client.py | 6 +- test/asynchronous/test_retryable_reads.py | 78 +++++++++++++++++++++++ test/test_retryable_reads.py | 78 +++++++++++++++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 4f3c43f23c..7fa0983908 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -2825,7 +2825,11 @@ async def run(self) -> T: if self._last_error is None: self._last_error = exc - if self._server is not None: + if ( + self._server is not None + and self._client.topology_description.topology_type_name == "Sharded" + or exc.has_error_label("SystemOverloadedError") + ): self._deprioritized_servers.append(self._server) def _is_not_eligible_for_retry(self) -> bool: diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index cd0d19141f..badbeac09d 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -2815,7 +2815,11 @@ def run(self) -> T: if self._last_error is None: self._last_error = exc - if self._server is not None: + if ( + self._server is not None + and self._client.topology_description.topology_type_name == "Sharded" + or exc.has_error_label("SystemOverloadedError") + ): self._deprioritized_servers.append(self._server) def _is_not_eligible_for_retry(self) -> bool: diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 47ac91b0f5..6adfaaae17 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -261,6 +261,84 @@ async def test_retryable_reads_are_retried_on_the_same_implicit_session(self): self.assertEqual(command_docs[0]["lsid"], command_docs[1]["lsid"]) self.assertIsNot(command_docs[0], command_docs[1]) + @async_client_context.require_replica_set + @async_client_context.require_secondaries_count(1) + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) + async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = await self.async_rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + await async_set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + await client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on different servers. + assert listener.failed_events[0].connection_id != listener.succeeded_events[0].connection_id + + @async_client_context.require_replica_set + @async_client_context.require_secondaries_count(1) + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) + async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_same_replicaset_server( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = await self.async_rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError error label. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError"], + "errorCode": 6, + }, + } + await async_set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + await client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index c9f72ae547..18cd669f1c 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -259,6 +259,84 @@ def test_retryable_reads_are_retried_on_the_same_implicit_session(self): self.assertEqual(command_docs[0]["lsid"], command_docs[1]["lsid"]) self.assertIsNot(command_docs[0], command_docs[1]) + @client_context.require_replica_set + @client_context.require_secondaries_count(1) + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) + def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = self.rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on different servers. + assert listener.failed_events[0].connection_id != listener.succeeded_events[0].connection_id + + @client_context.require_replica_set + @client_context.require_secondaries_count(1) + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) + def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_same_replicaset_server( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = self.rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError error label. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError"], + "errorCode": 6, + }, + } + set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() From 469a32a9ddb6915312f94d28d308751d16fe2380 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 2 Mar 2026 10:06:47 -0800 Subject: [PATCH 087/129] PYTHON-5737 - BSON encoding/decoding performance improvements (#2715) --- bson/_cbsonmodule.c | 203 +++++++++++++++++++++++++++++++++++--------- bson/_cbsonmodule.h | 1 + 2 files changed, 162 insertions(+), 42 deletions(-) diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c index 7d184641c5..034490f558 100644 --- a/bson/_cbsonmodule.c +++ b/bson/_cbsonmodule.c @@ -356,7 +356,8 @@ static PyObject* datetime_ms_from_millis(PyObject* self, long long millis){ if (!(ll_millis = PyLong_FromLongLong(millis))){ return NULL; } - dt = PyObject_CallFunctionObjArgs(state->DatetimeMS, ll_millis, NULL); + PyObject* args[1] = {ll_millis}; + dt = PyObject_Vectorcall(state->DatetimeMS, args, 1, NULL); Py_DECREF(ll_millis); return dt; } @@ -401,7 +402,9 @@ static PyObject* decode_datetime(PyObject* self, long long millis, const codec_o int64_t min_millis_offset = 0; int64_t max_millis_offset = 0; if (options->tz_aware && options->tzinfo && options->tzinfo != Py_None) { - PyObject* utcoffset = PyObject_CallMethodObjArgs(options->tzinfo, state->_utcoffset_str, state->min_datetime, NULL); + PyObject* utcoffset_args[2] = {options->tzinfo, state->min_datetime}; + PyObject* utcoffset = PyObject_VectorcallMethod( + state->_utcoffset_str, utcoffset_args, 2, NULL); if (utcoffset == NULL) { return 0; } @@ -420,7 +423,9 @@ static PyObject* decode_datetime(PyObject* self, long long millis, const codec_o (PyDateTime_DELTA_GET_MICROSECONDS(utcoffset) / 1000); } Py_DECREF(utcoffset); - utcoffset = PyObject_CallMethodObjArgs(options->tzinfo, state->_utcoffset_str, state->max_datetime, NULL); + utcoffset_args[1] = state->max_datetime; + utcoffset = PyObject_VectorcallMethod( + state->_utcoffset_str, utcoffset_args, 2, NULL); if (utcoffset == NULL) { return 0; } @@ -481,7 +486,9 @@ static PyObject* decode_datetime(PyObject* self, long long millis, const codec_o /* convert to local time */ if (options->tzinfo != Py_None) { - PyObject* temp = PyObject_CallMethodObjArgs(value, state->_astimezone_str, options->tzinfo, NULL); + PyObject* astimezone_args[2] = {value, options->tzinfo}; + PyObject* temp = PyObject_VectorcallMethod( + state->_astimezone_str, astimezone_args, 2, NULL); Py_DECREF(value); value = temp; } @@ -688,7 +695,8 @@ static int _load_python_objects(PyObject* module) { return 1; } - compiled = PyObject_CallFunction(re_compile, "O", empty_string); + PyObject* compile_args[1] = {empty_string}; + compiled = PyObject_Vectorcall(re_compile, compile_args, 1, NULL); Py_DECREF(re_compile); if (compiled == NULL) { state->REType = NULL; @@ -711,13 +719,19 @@ static long _type_marker(PyObject* object, PyObject* _type_marker_str) { PyObject* type_marker = NULL; long type = 0; - if (PyObject_HasAttr(object, _type_marker_str)) { - type_marker = PyObject_GetAttr(object, _type_marker_str); - if (type_marker == NULL) { + #if PY_VERSION_HEX >= 0x030D0000 + // 3.13 + if (PyObject_GetOptionalAttr(object, _type_marker_str, &type_marker) == -1) { return -1; } - } - + # else + if (PyObject_HasAttr(object, _type_marker_str)) { + type_marker = PyObject_GetAttr(object, _type_marker_str); + if (type_marker == NULL) { + return -1; + } + } + #endif /* * Python objects with broken __getattr__ implementations could return * arbitrary types for a call to PyObject_GetAttrString. For example @@ -814,6 +828,7 @@ int convert_codec_options(PyObject* self, PyObject* options_obj, codec_options_t } options->is_raw_bson = (101 == type_marker); + options->is_dict_class = (options->document_class == (PyObject*)&PyDict_Type); options->options_obj = options_obj; Py_INCREF(options->options_obj); @@ -1013,10 +1028,20 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, } /* * Use _type_marker attribute instead of PyObject_IsInstance for better perf. + * + * Skip _type_marker lookup for common built-in types + * that we know don't have a _type_marker attribute. This avoids the overhead + * of PyObject_HasAttr/PyObject_GetAttr calls for the most common cases. */ - type = _type_marker(value, state->_type_marker_str); - if (type < 0) { - return 0; + if (PyUnicode_CheckExact(value) || PyLong_CheckExact(value) || PyFloat_CheckExact(value) || + PyBool_Check(value) || PyDict_CheckExact(value) || PyList_CheckExact(value) || + PyTuple_CheckExact(value) || PyBytes_CheckExact(value) || value == Py_None) { + type = 0; + } else { + type = _type_marker(value, state->_type_marker_str); + if (type < 0) { + return 0; + } } switch (type) { @@ -1227,7 +1252,9 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, case 100: { /* DBRef */ - PyObject* as_doc = PyObject_CallMethodObjArgs(value, state->_as_doc_str, NULL); + PyObject* as_doc_args[1] = {value}; + PyObject* as_doc = PyObject_VectorcallMethod( + state->_as_doc_str, as_doc_args, 1, NULL); if (!as_doc) { return 0; } @@ -1383,7 +1410,9 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, return write_unicode(buffer, value); } else if (PyDateTime_Check(value)) { long long millis; - PyObject* utcoffset = PyObject_CallMethodObjArgs(value, state->_utcoffset_str , NULL); + PyObject* utcoffset_args[1] = {value}; + PyObject* utcoffset = PyObject_VectorcallMethod( + state->_utcoffset_str, utcoffset_args, 1, NULL); if (utcoffset == NULL) return 0; if (utcoffset != Py_None) { @@ -1422,7 +1451,9 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, if (!(uuid_rep_obj = PyLong_FromLong(options->uuid_rep))) { return 0; } - binary_value = PyObject_CallMethodObjArgs(state->Binary, state->_from_uuid_str, value, uuid_rep_obj, NULL); + PyObject* from_uuid_args[3] = {state->Binary, value, uuid_rep_obj}; + binary_value = PyObject_VectorcallMethod( + state->_from_uuid_str, from_uuid_args, 3, NULL); Py_DECREF(uuid_rep_obj); if (binary_value == NULL) { @@ -1452,7 +1483,8 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, if (converter != NULL) { /* Transform types that have a registered converter. * A new reference is created upon transformation. */ - new_value = PyObject_CallFunctionObjArgs(converter, value, NULL); + PyObject* converter_args[1] = {value}; + new_value = PyObject_Vectorcall(converter, converter_args, 1, NULL); if (new_value == NULL) { return 0; } @@ -1466,8 +1498,9 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, /* Try the fallback encoder if one is provided and we have not already * attempted to use the fallback encoder. */ if (!in_fallback_call && options->type_registry.has_fallback_encoder) { - new_value = PyObject_CallFunctionObjArgs( - options->type_registry.fallback_encoder, value, NULL); + PyObject* fallback_args[1] = {value}; + new_value = PyObject_Vectorcall( + options->type_registry.fallback_encoder, fallback_args, 1, NULL); if (new_value == NULL) { // propagate any exception raised by the callback return 0; @@ -1668,7 +1701,8 @@ void handle_invalid_doc_error(PyObject* dict) { goto cleanup; } // Add doc to the error instance as a property. - new_evalue = PyObject_CallFunctionObjArgs(InvalidDocument, new_msg, dict, NULL); + PyObject* exc_args[2] = {new_msg, dict}; + new_evalue = PyObject_Vectorcall(InvalidDocument, exc_args, 2, NULL); Py_DECREF(evalue); Py_DECREF(etype); etype = InvalidDocument; @@ -1944,7 +1978,8 @@ static PyObject *_dbref_hook(PyObject* self, PyObject* value) { PyMapping_DelItem(value, state->_dollar_db_str); } - ret = PyObject_CallFunctionObjArgs(state->DBRef, ref, id, database, value, NULL); + PyObject* dbref_args[4] = {ref, id, database, value}; + ret = PyObject_Vectorcall(state->DBRef, dbref_args, 4, NULL); Py_DECREF(value); } else { ret = value; @@ -2160,7 +2195,13 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, goto uuiderror; } - binary_value = PyObject_CallFunction(state->Binary, "(Oi)", data, subtype); + PyObject* subtype_obj = PyLong_FromLong(subtype); + if (!subtype_obj) { + goto uuiderror; + } + PyObject* binary_args[2] = {data, subtype_obj}; + binary_value = PyObject_Vectorcall(state->Binary, binary_args, 2, NULL); + Py_DECREF(subtype_obj); if (binary_value == NULL) { goto uuiderror; } @@ -2175,7 +2216,9 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, if (!uuid_rep_obj) { goto uuiderror; } - value = PyObject_CallMethodObjArgs(binary_value, state->_as_uuid_str, uuid_rep_obj, NULL); + PyObject* as_uuid_args[2] = {binary_value, uuid_rep_obj}; + value = PyObject_VectorcallMethod( + state->_as_uuid_str, as_uuid_args, 2, NULL); Py_DECREF(uuid_rep_obj); } @@ -2194,7 +2237,8 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, Py_DECREF(data); goto invalid; } - value = PyObject_CallFunctionObjArgs(state->Binary, data, st, NULL); + PyObject* binary_args[2] = {data, st}; + value = PyObject_Vectorcall(state->Binary, binary_args, 2, NULL); Py_DECREF(st); Py_DECREF(data); if (!value) { @@ -2215,7 +2259,13 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, if (max < 12) { goto invalid; } - value = PyObject_CallFunction(state->ObjectId, "y#", buffer + *position, (Py_ssize_t)12); + PyObject* oid_bytes = PyBytes_FromStringAndSize(buffer + *position, 12); + if (!oid_bytes) { + goto invalid; + } + PyObject* oid_args[1] = {oid_bytes}; + value = PyObject_Vectorcall(state->ObjectId, oid_args, 1, NULL); + Py_DECREF(oid_bytes); *position += 12; break; } @@ -2294,7 +2344,14 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } *position += (unsigned)flags_length + 1; - value = PyObject_CallFunction(state->Regex, "Oi", pattern, flags); + PyObject* flags_obj = PyLong_FromLong(flags); + if (!flags_obj) { + Py_DECREF(pattern); + goto invalid; + } + PyObject* regex_args[2] = {pattern, flags_obj}; + value = PyObject_Vectorcall(state->Regex, regex_args, 2, NULL); + Py_DECREF(flags_obj); Py_DECREF(pattern); break; } @@ -2327,13 +2384,21 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } *position += coll_length; - id = PyObject_CallFunction(state->ObjectId, "y#", buffer + *position, (Py_ssize_t)12); + PyObject* oid_bytes = PyBytes_FromStringAndSize(buffer + *position, 12); + if (!oid_bytes) { + Py_DECREF(collection); + goto invalid; + } + PyObject* oid_args[1] = {oid_bytes}; + id = PyObject_Vectorcall(state->ObjectId, oid_args, 1, NULL); + Py_DECREF(oid_bytes); if (!id) { Py_DECREF(collection); goto invalid; } *position += 12; - value = PyObject_CallFunctionObjArgs(state->DBRef, collection, id, NULL); + PyObject* dbref_args[2] = {collection, id}; + value = PyObject_Vectorcall(state->DBRef, dbref_args, 2, NULL); Py_DECREF(collection); Py_DECREF(id); break; @@ -2363,7 +2428,8 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, goto invalid; } *position += value_length; - value = PyObject_CallFunctionObjArgs(state->Code, code, NULL, NULL); + PyObject* code_args[1] = {code}; + value = PyObject_Vectorcall(state->Code, code_args, 1, NULL); Py_DECREF(code); break; } @@ -2429,7 +2495,8 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } *position += scope_size; - value = PyObject_CallFunctionObjArgs(state->Code, code, scope, NULL); + PyObject* code_scope_args[2] = {code, scope}; + value = PyObject_Vectorcall(state->Code, code_scope_args, 2, NULL); Py_DECREF(code); Py_DECREF(scope); break; @@ -2459,7 +2526,19 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, memcpy(&time, buffer + *position + 4, 4); inc = BSON_UINT32_FROM_LE(inc); time = BSON_UINT32_FROM_LE(time); - value = PyObject_CallFunction(state->Timestamp, "II", time, inc); + PyObject* time_obj = PyLong_FromUnsignedLong(time); + if (!time_obj) { + goto invalid; + } + PyObject* inc_obj = PyLong_FromUnsignedLong(inc); + if (!inc_obj) { + Py_DECREF(time_obj); + goto invalid; + } + PyObject* ts_args[2] = {time_obj, inc_obj}; + value = PyObject_Vectorcall(state->Timestamp, ts_args, 2, NULL); + Py_DECREF(time_obj); + Py_DECREF(inc_obj); *position += 8; break; } @@ -2471,7 +2550,13 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } memcpy(&ll, buffer + *position, 8); ll = (int64_t)BSON_UINT64_FROM_LE(ll); - value = PyObject_CallFunction(state->BSONInt64, "L", ll); + PyObject* ll_obj = PyLong_FromLongLong(ll); + if (!ll_obj) { + goto invalid; + } + PyObject* int64_args[1] = {ll_obj}; + value = PyObject_Vectorcall(state->BSONInt64, int64_args, 1, NULL); + Py_DECREF(ll_obj); *position += 8; break; } @@ -2484,19 +2569,21 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, if (!_bytes_obj) { goto invalid; } - value = PyObject_CallMethodObjArgs(state->Decimal128, state->_from_bid_str, _bytes_obj, NULL); + PyObject* dec128_args[2] = {state->Decimal128, _bytes_obj}; + value = PyObject_VectorcallMethod( + state->_from_bid_str, dec128_args, 2, NULL); Py_DECREF(_bytes_obj); *position += 16; break; } case 255: { - value = PyObject_CallFunctionObjArgs(state->MinKey, NULL); + value = PyObject_Vectorcall(state->MinKey, NULL, 0, NULL); break; } case 127: { - value = PyObject_CallFunctionObjArgs(state->MaxKey, NULL); + value = PyObject_Vectorcall(state->MaxKey, NULL, 0, NULL); break; } default: @@ -2548,7 +2635,8 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } converter = PyDict_GetItem(options->type_registry.decoder_map, value_type); if (converter != NULL) { - PyObject* new_value = PyObject_CallFunctionObjArgs(converter, value, NULL); + PyObject* converter_args[1] = {value}; + PyObject* new_value = PyObject_Vectorcall(converter, converter_args, 1, NULL); Py_DECREF(value_type); Py_DECREF(value); return new_value; @@ -2716,11 +2804,20 @@ static PyObject* _elements_to_dict(PyObject* self, const char* string, unsigned max, const codec_options_t* options) { unsigned position = 0; - PyObject* dict = PyObject_CallObject(options->document_class, NULL); + PyObject* dict; + int raw_array = 0; + + /* Use PyDict_New() directly when document_class is dict. + * This avoids the overhead of PyObject_CallObject() for the common case. */ + if (options->is_dict_class) { + dict = PyDict_New(); + } else { + dict = PyObject_CallObject(options->document_class, NULL); + } if (!dict) { return NULL; } - int raw_array = 0; + while (position < max) { PyObject* name = NULL; PyObject* value = NULL; @@ -2735,7 +2832,24 @@ static PyObject* _elements_to_dict(PyObject* self, const char* string, position = (unsigned)new_position; } - PyObject_SetItem(dict, name, value); + /* Use PyDict_SetItem() when document_class is dict. + * PyDict_SetItem() is faster than PyObject_SetItem() because it + * avoids method lookup overhead. */ + if (options->is_dict_class) { + if (PyDict_SetItem(dict, name, value) < 0) { + Py_DECREF(name); + Py_DECREF(value); + Py_DECREF(dict); + return NULL; + } + } else { + if (PyObject_SetItem(dict, name, value) < 0) { + Py_DECREF(name); + Py_DECREF(value); + Py_DECREF(dict); + return NULL; + } + } Py_DECREF(name); Py_DECREF(value); } @@ -2747,9 +2861,14 @@ static PyObject* elements_to_dict(PyObject* self, const char* string, const codec_options_t* options) { PyObject* result; if (options->is_raw_bson) { - return PyObject_CallFunction( - options->document_class, "y#O", - string, max, options->options_obj); + PyObject* bson_bytes = PyBytes_FromStringAndSize(string, max); + if (!bson_bytes) { + return NULL; + } + PyObject* raw_args[2] = {bson_bytes, options->options_obj}; + result = PyObject_Vectorcall(options->document_class, raw_args, 2, NULL); + Py_DECREF(bson_bytes); + return result; } if (Py_EnterRecursiveCall(" while decoding a BSON document")) return NULL; diff --git a/bson/_cbsonmodule.h b/bson/_cbsonmodule.h index 3be2b74427..a9bee24b8d 100644 --- a/bson/_cbsonmodule.h +++ b/bson/_cbsonmodule.h @@ -72,6 +72,7 @@ typedef struct codec_options_t { unsigned char datetime_conversion; PyObject* options_obj; unsigned char is_raw_bson; + unsigned char is_dict_class; } codec_options_t; /* C API functions */ From e028fe2a3824c23b2103d59e3c8b8a477c508f29 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:24:06 -0800 Subject: [PATCH 088/129] [Spec Resync] 03-02-2026 (#2716) Co-authored-by: Cloud User Co-authored-by: Iris <58442094+sleepyStick@users.noreply.github.com> --- test/csot/convenient-transactions.json | 107 ++++++++- .../errors/error_handling_handshake.json | 2 +- .../rs/disaggregated_storage_setversion.json | 167 +++++++++++++ ...h_unchanged_setversion_and_electionid.json | 227 ++++++++++++++++++ .../migration_from_disaggregated_storage.json | 167 +++++++++++++ .../migration_to_disaggregated_storage.json | 119 +++++++++ .../DeprioritizedNearestOnlyMatchingTags.json | 62 +++++ ...tizedPrimaryPreferredOnlyMatchingTags.json | 62 +++++ ...eprioritizedSecondaryOnlyMatchingTags.json | 62 +++++ ...zedSecondaryPreferredOnlyMatchingTags.json | 62 +++++ .../DeprioritizedNearestOnlyMatchingTags.json | 70 ++++++ ...tizedPrimaryPreferredOnlyMatchingTags.json | 70 ++++++ ...eprioritizedSecondaryOnlyMatchingTags.json | 70 ++++++ ...zedSecondaryPreferredOnlyMatchingTags.json | 70 ++++++ .../read/SecondaryPreferred_empty_tags.json | 41 ++++ 15 files changed, 1356 insertions(+), 2 deletions(-) create mode 100644 test/discovery_and_monitoring/rs/disaggregated_storage_setversion.json create mode 100644 test/discovery_and_monitoring/rs/member_list_update_with_unchanged_setversion_and_electionid.json create mode 100644 test/discovery_and_monitoring/rs/migration_from_disaggregated_storage.json create mode 100644 test/discovery_and_monitoring/rs/migration_to_disaggregated_storage.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearestOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json create mode 100644 test/server_selection/server_selection/ReplicaSetWithPrimary/read/SecondaryPreferred_empty_tags.json diff --git a/test/csot/convenient-transactions.json b/test/csot/convenient-transactions.json index f9d03429db..3400b82ba9 100644 --- a/test/csot/convenient-transactions.json +++ b/test/csot/convenient-transactions.json @@ -27,7 +27,8 @@ "awaitMinPoolSizeMS": 10000, "useMultipleMongoses": false, "observeEvents": [ - "commandStartedEvent" + "commandStartedEvent", + "commandFailedEvent" ] } }, @@ -188,6 +189,11 @@ } } }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, { "commandStartedEvent": { "commandName": "abortTransaction", @@ -206,6 +212,105 @@ ] } ] + }, + { + "description": "withTransaction surfaces a timeout after exhausting transient transaction retries, retaining the last transient error as the timeout cause.", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "insert" + ], + "blockConnection": true, + "blockTimeMS": 25, + "errorCode": 24, + "errorLabels": [ + "TransientTransactionError" + ] + } + } + } + }, + { + "name": "withTransaction", + "object": "session", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1 + }, + "session": "session" + }, + "expectError": { + "isError": true + } + } + ] + }, + "expectError": { + "isTimeoutError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "ignoreExtraEvents": true, + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + }, + { + "commandFailedEvent": { + "commandName": "abortTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + }, + { + "commandFailedEvent": { + "commandName": "abortTransaction" + } + } + ] + } + ] } ] } diff --git a/test/discovery_and_monitoring/errors/error_handling_handshake.json b/test/discovery_and_monitoring/errors/error_handling_handshake.json index 56ca7d1132..65f8ad0a33 100644 --- a/test/discovery_and_monitoring/errors/error_handling_handshake.json +++ b/test/discovery_and_monitoring/errors/error_handling_handshake.json @@ -85,7 +85,7 @@ } }, { - "description": "Mark server unknown on network timeout application error (beforeHandshakeCompletes)", + "description": "Ignore network timeout application error (beforeHandshakeCompletes)", "applicationErrors": [ { "address": "a:27017", diff --git a/test/discovery_and_monitoring/rs/disaggregated_storage_setversion.json b/test/discovery_and_monitoring/rs/disaggregated_storage_setversion.json new file mode 100644 index 0000000000..c8b41d30ca --- /dev/null +++ b/test/discovery_and_monitoring/rs/disaggregated_storage_setversion.json @@ -0,0 +1,167 @@ +{ + "description": "Static setVersion (DSC) is compatible with both pre and post DRIVERS-2412", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000005" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000005" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000005" + } + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000006" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "setVersion": null, + "electionId": null + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000006" + } + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000006" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000005" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "setVersion": null, + "electionId": null + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000006" + } + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000006" + } + } + } + ] +} diff --git a/test/discovery_and_monitoring/rs/member_list_update_with_unchanged_setversion_and_electionid.json b/test/discovery_and_monitoring/rs/member_list_update_with_unchanged_setversion_and_electionid.json new file mode 100644 index 0000000000..0045591db9 --- /dev/null +++ b/test/discovery_and_monitoring/rs/member_list_update_with_unchanged_setversion_and_electionid.json @@ -0,0 +1,227 @@ +{ + "description": "Member list is updated when setVersion and electionId remain the same", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "setVersion": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "responses": [ + [ + "c:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setName": "rs", + "setVersion": 1, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "c:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + } + ] +} diff --git a/test/discovery_and_monitoring/rs/migration_from_disaggregated_storage.json b/test/discovery_and_monitoring/rs/migration_from_disaggregated_storage.json new file mode 100644 index 0000000000..c5109026bc --- /dev/null +++ b/test/discovery_and_monitoring/rs/migration_from_disaggregated_storage.json @@ -0,0 +1,167 @@ +{ + "description": "DSC to ASC reverse migration - ASC primary with higher setVersion is accepted", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000005" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000005" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 1, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000005" + } + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1000, + "electionId": { + "$oid": "000000000000000000000006" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "setVersion": null, + "electionId": null + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1000, + "electionId": { + "$oid": "000000000000000000000006" + } + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1000, + "maxElectionId": { + "$oid": "000000000000000000000006" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000005" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "setVersion": null, + "electionId": null + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1000, + "electionId": { + "$oid": "000000000000000000000006" + } + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1000, + "maxElectionId": { + "$oid": "000000000000000000000006" + } + } + } + ] +} diff --git a/test/discovery_and_monitoring/rs/migration_to_disaggregated_storage.json b/test/discovery_and_monitoring/rs/migration_to_disaggregated_storage.json new file mode 100644 index 0000000000..57f39c93b2 --- /dev/null +++ b/test/discovery_and_monitoring/rs/migration_to_disaggregated_storage.json @@ -0,0 +1,119 @@ +{ + "description": "ASC to DSC forward migration - DSC uses setVersionASC + 1 to prevent false stale detection", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 10, + "electionId": { + "$oid": "000000000000000000000005" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 10, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 10, + "electionId": { + "$oid": "000000000000000000000005" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 10, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 10, + "maxElectionId": { + "$oid": "000000000000000000000005" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 11, + "electionId": { + "$oid": "000000000000000000000006" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 11, + "electionId": { + "$oid": "000000000000000000000006" + } + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs", + "setVersion": 10, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 11, + "maxElectionId": { + "$oid": "000000000000000000000006" + } + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearestOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearestOnlyMatchingTags.json new file mode 100644 index 0000000000..5a9e8797e4 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedNearestOnlyMatchingTags.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json new file mode 100644 index 0000000000..086532e710 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json new file mode 100644 index 0000000000..18926581c3 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Secondary", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json new file mode 100644 index 0000000000..ab51345353 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetNoPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json @@ -0,0 +1,62 @@ +{ + "topology_description": { + "type": "ReplicaSetNoPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestOnlyMatchingTags.json new file mode 100644 index 0000000000..021f361480 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedNearestOnlyMatchingTags.json @@ -0,0 +1,70 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json new file mode 100644 index 0000000000..4002907b3b --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedPrimaryPreferredOnlyMatchingTags.json @@ -0,0 +1,70 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "PrimaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json new file mode 100644 index 0000000000..2de5bdd4c7 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryOnlyMatchingTags.json @@ -0,0 +1,70 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 26, + "type": "RSPrimary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Secondary", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json new file mode 100644 index 0000000000..7e1f39a606 --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/DeprioritizedSecondaryPreferredOnlyMatchingTags.json @@ -0,0 +1,70 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + }, + { + "address": "c:27017", + "avg_rtt_ms": 100, + "type": "RSSecondary", + "tags": { + "data_center": "tokyo" + } + }, + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "tokyo" + } + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + } + ] + }, + "deprioritized_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary", + "tags": { + "data_center": "nyc" + } + } + ], + "suitable_servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "tokyo" + } + } + ], + "in_latency_window": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary", + "tags": { + "data_center": "tokyo" + } + } + ] +} diff --git a/test/server_selection/server_selection/ReplicaSetWithPrimary/read/SecondaryPreferred_empty_tags.json b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/SecondaryPreferred_empty_tags.json new file mode 100644 index 0000000000..8ec8049efe --- /dev/null +++ b/test/server_selection/server_selection/ReplicaSetWithPrimary/read/SecondaryPreferred_empty_tags.json @@ -0,0 +1,41 @@ +{ + "topology_description": { + "type": "ReplicaSetWithPrimary", + "servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSPrimary" + }, + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "SecondaryPreferred", + "tag_sets": [ + { + "data_center": "nyc" + }, + {} + ] + }, + "suitable_servers": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary" + } + ], + "in_latency_window": [ + { + "address": "b:27017", + "avg_rtt_ms": 5, + "type": "RSSecondary" + } + ] +} From f533157981c8f3cbcd80ede57297920ad4d4a69f Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 5 Mar 2026 08:04:37 -0800 Subject: [PATCH 089/129] Python 4542 - Improved sessions API (#2712) --- doc/changelog.rst | 9 ++ pymongo/asynchronous/client_session.py | 41 +++++++++ pymongo/asynchronous/mongo_client.py | 30 +++++-- pymongo/synchronous/client_session.py | 41 +++++++++ pymongo/synchronous/mongo_client.py | 30 +++++-- test/asynchronous/test_session.py | 113 +++++++++++++++++++++++++ test/test_session.py | 113 +++++++++++++++++++++++++ tools/synchro.py | 1 + 8 files changed, 364 insertions(+), 14 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 571ce3b63e..f38709203c 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,6 +1,15 @@ Changelog ========= +Changes in Version 4.17.0 (2026/XX/XX) +-------------------------------------- + +PyMongo 4.17 brings a number of changes including: + +- Added the :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.bind` and :meth:`~pymongo.client_session.ClientSession.bind` methods + that allow users to bind a session to all database operations within the scope of a context manager instead of having to explicitly pass the session to each individual operation. + See for examples and more information. + Changes in Version 4.16.0 (2026/01/07) -------------------------------------- diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index a12ca1f11b..c1e5a404d2 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -139,6 +139,7 @@ import time import uuid from collections.abc import Mapping as _Mapping +from contextvars import ContextVar, Token from typing import ( TYPE_CHECKING, Any, @@ -181,6 +182,28 @@ _IS_SYNC = False +_SESSION: ContextVar[Optional[AsyncClientSession]] = ContextVar("SESSION", default=None) + + +class _AsyncBoundSessionContext: + """Context manager returned by AsyncClientSession.bind() that manages bound state.""" + + def __init__(self, session: AsyncClientSession, end_session: bool) -> None: + self._session = session + self._session_token: Optional[Token[AsyncClientSession]] = None + self._end_session = end_session + + async def __aenter__(self) -> AsyncClientSession: + self._session_token = _SESSION.set(self._session) # type: ignore[assignment] + return self._session + + async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + if self._session_token: + _SESSION.reset(self._session_token) # type: ignore[arg-type] + self._session_token = None + if self._end_session: + await self._session.end_session() + class SessionOptions: """Options for a new :class:`AsyncClientSession`. @@ -547,6 +570,24 @@ def _check_ended(self) -> None: if self._server_session is None: raise InvalidOperation("Cannot use ended session") + def bind(self, end_session: bool = True) -> _AsyncBoundSessionContext: + """Bind this session so it is implicitly passed to all database operations within the returned context. + + .. code-block:: python + + async with client.start_session() as s: + async with s.bind(): + # session=s is passed implicitly + await client.db.collection.insert_one({"x": 1}) + + :param end_session: Whether to end the session on exiting the returned context. Defaults to True. + If set to False, :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.end_session()` must be called + once the session is no longer used. + + .. versionadded:: 4.17 + """ + return _AsyncBoundSessionContext(self, end_session) + async def __aenter__(self) -> AsyncClientSession: return self diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 7fa0983908..95f2e3746e 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -65,7 +65,7 @@ from pymongo.asynchronous import client_session, database, uri_parser from pymongo.asynchronous.change_stream import AsyncChangeStream, AsyncClusterChangeStream from pymongo.asynchronous.client_bulk import _AsyncClientBulk -from pymongo.asynchronous.client_session import _EmptyServerSession +from pymongo.asynchronous.client_session import _SESSION, _EmptyServerSession from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.settings import TopologySettings from pymongo.asynchronous.topology import Topology, _ErrorContext @@ -1408,7 +1408,8 @@ def start_session( def _ensure_session( self, session: Optional[AsyncClientSession] = None ) -> Optional[AsyncClientSession]: - """If provided session is None, lend a temporary session.""" + """If provided session and bound session are None, lend a temporary session.""" + session = session or self._get_bound_session() if session: return session @@ -2267,11 +2268,14 @@ async def _tmp_session( self, session: Optional[client_session.AsyncClientSession] ) -> AsyncGenerator[Optional[client_session.AsyncClientSession], None]: """If provided session is None, lend a temporary session.""" - if session is not None: - if not isinstance(session, client_session.AsyncClientSession): - raise ValueError( - f"'session' argument must be an AsyncClientSession or None, not {type(session)}" - ) + if session is not None and not isinstance(session, client_session.AsyncClientSession): + raise ValueError( + f"'session' argument must be an AsyncClientSession or None, not {type(session)}" + ) + + # Check for a bound session. If one exists, treat it as an explicitly passed session. + session = session or self._get_bound_session() + if session: # Don't call end_session. yield session return @@ -2301,6 +2305,18 @@ async def _process_response( if session is not None: session._process_response(reply) + def _get_bound_session(self) -> Optional[AsyncClientSession]: + bound_session = _SESSION.get() + if bound_session: + if bound_session.client is self: + return bound_session + else: + raise InvalidOperation( + "Only the client that created the bound session can perform operations within its context block. See for more information." + ) + else: + return None + async def server_info( self, session: Optional[client_session.AsyncClientSession] = None ) -> dict[str, Any]: diff --git a/pymongo/synchronous/client_session.py b/pymongo/synchronous/client_session.py index 8755e57261..5ef18a66bd 100644 --- a/pymongo/synchronous/client_session.py +++ b/pymongo/synchronous/client_session.py @@ -139,6 +139,7 @@ import time import uuid from collections.abc import Mapping as _Mapping +from contextvars import ContextVar, Token from typing import ( TYPE_CHECKING, Any, @@ -180,6 +181,28 @@ _IS_SYNC = True +_SESSION: ContextVar[Optional[ClientSession]] = ContextVar("SESSION", default=None) + + +class _BoundSessionContext: + """Context manager returned by ClientSession.bind() that manages bound state.""" + + def __init__(self, session: ClientSession, end_session: bool) -> None: + self._session = session + self._session_token: Optional[Token[ClientSession]] = None + self._end_session = end_session + + def __enter__(self) -> ClientSession: + self._session_token = _SESSION.set(self._session) # type: ignore[assignment] + return self._session + + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + if self._session_token: + _SESSION.reset(self._session_token) # type: ignore[arg-type] + self._session_token = None + if self._end_session: + self._session.end_session() + class SessionOptions: """Options for a new :class:`ClientSession`. @@ -546,6 +569,24 @@ def _check_ended(self) -> None: if self._server_session is None: raise InvalidOperation("Cannot use ended session") + def bind(self, end_session: bool = True) -> _BoundSessionContext: + """Bind this session so it is implicitly passed to all database operations within the returned context. + + .. code-block:: python + + with client.start_session() as s: + with s.bind(): + # session=s is passed implicitly + client.db.collection.insert_one({"x": 1}) + + :param end_session: Whether to end the session on exiting the returned context. Defaults to True. + If set to False, :meth:`~pymongo.client_session.ClientSession.end_session()` must be called + once the session is no longer used. + + .. versionadded:: 4.17 + """ + return _BoundSessionContext(self, end_session) + def __enter__(self) -> ClientSession: return self diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index badbeac09d..161a28d48d 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -108,7 +108,7 @@ from pymongo.synchronous import client_session, database, uri_parser from pymongo.synchronous.change_stream import ChangeStream, ClusterChangeStream from pymongo.synchronous.client_bulk import _ClientBulk -from pymongo.synchronous.client_session import _EmptyServerSession +from pymongo.synchronous.client_session import _SESSION, _EmptyServerSession from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.settings import TopologySettings from pymongo.synchronous.topology import Topology, _ErrorContext @@ -1406,7 +1406,8 @@ def start_session( ) def _ensure_session(self, session: Optional[ClientSession] = None) -> Optional[ClientSession]: - """If provided session is None, lend a temporary session.""" + """If provided session and bound session are None, lend a temporary session.""" + session = session or self._get_bound_session() if session: return session @@ -2263,11 +2264,14 @@ def _tmp_session( self, session: Optional[client_session.ClientSession] ) -> Generator[Optional[client_session.ClientSession], None]: """If provided session is None, lend a temporary session.""" - if session is not None: - if not isinstance(session, client_session.ClientSession): - raise ValueError( - f"'session' argument must be a ClientSession or None, not {type(session)}" - ) + if session is not None and not isinstance(session, client_session.ClientSession): + raise ValueError( + f"'session' argument must be a ClientSession or None, not {type(session)}" + ) + + # Check for a bound session. If one exists, treat it as an explicitly passed session. + session = session or self._get_bound_session() + if session: # Don't call end_session. yield session return @@ -2295,6 +2299,18 @@ def _process_response(self, reply: Mapping[str, Any], session: Optional[ClientSe if session is not None: session._process_response(reply) + def _get_bound_session(self) -> Optional[ClientSession]: + bound_session = _SESSION.get() + if bound_session: + if bound_session.client is self: + return bound_session + else: + raise InvalidOperation( + "Only the client that created the bound session can perform operations within its context block. See for more information." + ) + else: + return None + def server_info(self, session: Optional[client_session.ClientSession] = None) -> dict[str, Any]: """Get information about the MongoDB server we're connected to. diff --git a/test/asynchronous/test_session.py b/test/asynchronous/test_session.py index 19ce868c56..404a69fdee 100644 --- a/test/asynchronous/test_session.py +++ b/test/asynchronous/test_session.py @@ -189,6 +189,52 @@ async def _test_ops(self, client, *ops): f"{f.__name__} did not return implicit session to pool", ) + # Explicit bound session + for f, args, kw in ops: + async with client.start_session() as s: + async with s.bind(): + listener.reset() + s._materialize() + last_use = s._server_session.last_use + start = time.monotonic() + self.assertLessEqual(last_use, start) + # In case "f" modifies its inputs. + args = copy.copy(args) + kw = copy.copy(kw) + await f(*args, **kw) + self.assertGreaterEqual(len(listener.started_events), 1) + for event in listener.started_events: + self.assertIn( + "lsid", + event.command, + f"{f.__name__} sent no lsid with {event.command_name}", + ) + + self.assertEqual( + s.session_id, + event.command["lsid"], + f"{f.__name__} sent wrong lsid with {event.command_name}", + ) + + self.assertFalse(s.has_ended) + + self.assertTrue(s.has_ended) + with self.assertRaisesRegex(InvalidOperation, "ended session"): + async with s.bind(): + await f(*args, **kw) + + # Test a session cannot be used on another client. + async with self.client2.start_session() as s: + async with s.bind(): + # In case "f" modifies its inputs. + args = copy.copy(args) + kw = copy.copy(kw) + with self.assertRaisesRegex( + InvalidOperation, + "Only the client that created the bound session can perform operations within its context block", + ): + await f(*args, **kw) + async def test_implicit_sessions_checkout(self): # "To confirm that implicit sessions only allocate their server session after a # successful connection checkout" test from Driver Sessions Spec. @@ -825,6 +871,73 @@ async def test_session_not_copyable(self): async with client.start_session() as s: self.assertRaises(TypeError, lambda: copy.copy(s)) + async def test_nested_session_binding(self): + coll = self.client.pymongo_test.test + await coll.insert_one({"x": 1}) + + session1 = self.client.start_session() + session2 = self.client.start_session() + session1._materialize() + session2._materialize() + try: + self.listener.reset() + # Uses implicit session + await coll.find_one() + implicit_lsid = self.listener.started_events[0].command.get("lsid") + self.assertIsNotNone(implicit_lsid) + self.assertNotEqual(implicit_lsid, session1.session_id) + self.assertNotEqual(implicit_lsid, session2.session_id) + + async with session1.bind(end_session=False): + self.listener.reset() + # Uses bound session1 + await coll.find_one() + session1_lsid = self.listener.started_events[0].command.get("lsid") + self.assertEqual(session1_lsid, session1.session_id) + + async with session2.bind(end_session=False): + self.listener.reset() + # Uses bound session2 + await coll.find_one() + session2_lsid = self.listener.started_events[0].command.get("lsid") + self.assertEqual(session2_lsid, session2.session_id) + self.assertNotEqual(session2_lsid, session1.session_id) + + self.listener.reset() + # Use bound session1 again + await coll.find_one() + session1_lsid = self.listener.started_events[0].command.get("lsid") + self.assertEqual(session1_lsid, session1.session_id) + self.assertNotEqual(session1_lsid, session2.session_id) + + self.listener.reset() + # Uses implicit session + await coll.find_one() + implicit_lsid = self.listener.started_events[0].command.get("lsid") + self.assertIsNotNone(implicit_lsid) + self.assertNotEqual(implicit_lsid, session1.session_id) + self.assertNotEqual(implicit_lsid, session2.session_id) + + finally: + await session1.end_session() + await session2.end_session() + + async def test_session_binding_end_session(self): + coll = self.client.pymongo_test.test + await coll.insert_one({"x": 1}) + + async with self.client.start_session().bind() as s1: + await coll.find_one() + + self.assertTrue(s1.has_ended) + + async with self.client.start_session().bind(end_session=False) as s2: + await coll.find_one() + + self.assertFalse(s2.has_ended) + + await s2.end_session() + class TestCausalConsistency(AsyncUnitTest): listener: SessionTestListener diff --git a/test/test_session.py b/test/test_session.py index 40d0a53afb..3963f88da0 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -189,6 +189,52 @@ def _test_ops(self, client, *ops): f"{f.__name__} did not return implicit session to pool", ) + # Explicit bound session + for f, args, kw in ops: + with client.start_session() as s: + with s.bind(): + listener.reset() + s._materialize() + last_use = s._server_session.last_use + start = time.monotonic() + self.assertLessEqual(last_use, start) + # In case "f" modifies its inputs. + args = copy.copy(args) + kw = copy.copy(kw) + f(*args, **kw) + self.assertGreaterEqual(len(listener.started_events), 1) + for event in listener.started_events: + self.assertIn( + "lsid", + event.command, + f"{f.__name__} sent no lsid with {event.command_name}", + ) + + self.assertEqual( + s.session_id, + event.command["lsid"], + f"{f.__name__} sent wrong lsid with {event.command_name}", + ) + + self.assertFalse(s.has_ended) + + self.assertTrue(s.has_ended) + with self.assertRaisesRegex(InvalidOperation, "ended session"): + with s.bind(): + f(*args, **kw) + + # Test a session cannot be used on another client. + with self.client2.start_session() as s: + with s.bind(): + # In case "f" modifies its inputs. + args = copy.copy(args) + kw = copy.copy(kw) + with self.assertRaisesRegex( + InvalidOperation, + "Only the client that created the bound session can perform operations within its context block", + ): + f(*args, **kw) + def test_implicit_sessions_checkout(self): # "To confirm that implicit sessions only allocate their server session after a # successful connection checkout" test from Driver Sessions Spec. @@ -825,6 +871,73 @@ def test_session_not_copyable(self): with client.start_session() as s: self.assertRaises(TypeError, lambda: copy.copy(s)) + def test_nested_session_binding(self): + coll = self.client.pymongo_test.test + coll.insert_one({"x": 1}) + + session1 = self.client.start_session() + session2 = self.client.start_session() + session1._materialize() + session2._materialize() + try: + self.listener.reset() + # Uses implicit session + coll.find_one() + implicit_lsid = self.listener.started_events[0].command.get("lsid") + self.assertIsNotNone(implicit_lsid) + self.assertNotEqual(implicit_lsid, session1.session_id) + self.assertNotEqual(implicit_lsid, session2.session_id) + + with session1.bind(end_session=False): + self.listener.reset() + # Uses bound session1 + coll.find_one() + session1_lsid = self.listener.started_events[0].command.get("lsid") + self.assertEqual(session1_lsid, session1.session_id) + + with session2.bind(end_session=False): + self.listener.reset() + # Uses bound session2 + coll.find_one() + session2_lsid = self.listener.started_events[0].command.get("lsid") + self.assertEqual(session2_lsid, session2.session_id) + self.assertNotEqual(session2_lsid, session1.session_id) + + self.listener.reset() + # Use bound session1 again + coll.find_one() + session1_lsid = self.listener.started_events[0].command.get("lsid") + self.assertEqual(session1_lsid, session1.session_id) + self.assertNotEqual(session1_lsid, session2.session_id) + + self.listener.reset() + # Uses implicit session + coll.find_one() + implicit_lsid = self.listener.started_events[0].command.get("lsid") + self.assertIsNotNone(implicit_lsid) + self.assertNotEqual(implicit_lsid, session1.session_id) + self.assertNotEqual(implicit_lsid, session2.session_id) + + finally: + session1.end_session() + session2.end_session() + + def test_session_binding_end_session(self): + coll = self.client.pymongo_test.test + coll.insert_one({"x": 1}) + + with self.client.start_session().bind() as s1: + coll.find_one() + + self.assertTrue(s1.has_ended) + + with self.client.start_session().bind(end_session=False) as s2: + coll.find_one() + + self.assertFalse(s2.has_ended) + + s2.end_session() + class TestCausalConsistency(UnitTest): listener: SessionTestListener diff --git a/tools/synchro.py b/tools/synchro.py index 5735d0052a..ee719d7429 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -37,6 +37,7 @@ "AsyncRawBatchCursor": "RawBatchCursor", "AsyncRawBatchCommandCursor": "RawBatchCommandCursor", "AsyncClientSession": "ClientSession", + "_AsyncBoundSessionContext": "_BoundSessionContext", "AsyncChangeStream": "ChangeStream", "AsyncCollectionChangeStream": "CollectionChangeStream", "AsyncDatabaseChangeStream": "DatabaseChangeStream", From 926541fa4deb7cbc1b0434768f2fd7a9ff6977f0 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 9 Mar 2026 10:29:00 -0400 Subject: [PATCH 090/129] PYTHON-5742 - Add Copilot instructions (#2717) --- .github/copilot-instructions.md | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..b67cb49aca --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,44 @@ +When reviewing code, focus on: + +## Security Critical Issues +- Check for hardcoded secrets, API keys, or credentials. +- Check for instances of potential method call injection, dynamic code execution, symbol injection or other code injection vulnerabilities. + +## Performance Red Flags +- Spot inefficient loops and algorithmic issues. +- Check for memory leaks and resource cleanup. + +## Code Quality Essentials +- Methods should be focused and appropriately sized. If a method is doing too much, suggest refactorings to split it up. +- Use clear, descriptive naming conventions. +- Avoid encapsulation violations and ensure proper separation of concerns. +- All public classes, modules, and methods should have clear documentation in Sphinx format. + +## PyMongo-specific Concerns +- Do not review files within `pymongo/synchronous` or files in `test/` that also have a file of the same name in `test/asynchronous` unless the reviewed changes include a `_IS_SYNC` statement. PyMongo generates these files from `pymongo/asynchronous` and `test/asynchronous` using `tools/synchro.py`. +- All asynchronous functions must not call any blocking I/O. + +## Review Style +- Be specific and actionable in feedback. +- Explain the "why" behind recommendations. +- Acknowledge good patterns when you see them. +- Ask clarifying questions when code intent is unclear. + +Always prioritize security vulnerabilities and performance issues that could impact users. + +Always suggest changes to improve readability and testability. For example, this suggestion seeks to make the code more readable, reusable, and testable: + +```python +# Instead of: +if user.email and "@" in user.email and len(user.email) > 5: + submit_button.enabled = True +else: + submit_button.enabled = False + +# Consider: +def valid_email(email): + return email and "@" in email and len(email) > 5 + + +submit_button.enabled = valid_email(user.email) +``` From 38da6c3f9acc4d276c312c4abfb08f17d84b87c4 Mon Sep 17 00:00:00 2001 From: Iris <58442094+sleepyStick@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:24:59 -0700 Subject: [PATCH 091/129] PYTHON-5747 Add jira link to spec resync PR (#2723) --- .evergreen/scripts/resync-all-specs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index 1996d5d634..16782de9a8 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -7,6 +7,8 @@ from argparse import Namespace from subprocess import CalledProcessError +JIRA_FILTER = "https://jira.mongodb.org/issues/?jql=labels%20%3D%20automated-sync%20AND%20status%20!%3D%20Closed" + def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: """Actually sync the specs""" @@ -117,6 +119,7 @@ def write_summary(errored: dict[str, str], new: list[str], filename: str | None) pr_body += "\n -".join(new) pr_body += "\n" if pr_body != "": + pr_body = f"Jira tickets: {JIRA_FILTER}\n\n" + pr_body if filename is None: print(f"\n{pr_body}") else: From f303125cee3d00405fd710d64b216ab373fcf0cf Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Mon, 9 Mar 2026 11:53:40 -0700 Subject: [PATCH 092/129] PYTHON-5114 Test suite reduce killAllSessions calls (#2721) --- test/asynchronous/unified_format.py | 14 +++++++++----- test/unified_format.py | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index 6ce8f852cf..1fb93e7b86 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -1464,11 +1464,6 @@ async def verify_outcome(self, spec): self.assertListEqual(sorted_expected_documents, actual_documents) async def run_scenario(self, spec, uri=None): - # Kill all sessions before and after each test to prevent an open - # transaction (from a test failure) from blocking collection/database - # operations during test set up and tear down. - await self.kill_all_sessions() - # Handle flaky tests. flaky_tests = [ ("PYTHON-5170", ".*test_discovery_and_monitoring.*"), @@ -1504,6 +1499,15 @@ async def _run_scenario(self, spec, uri=None): if skip_reason is not None: raise unittest.SkipTest(f"{skip_reason}") + # Kill all sessions after each test with transactions to prevent an open + # transaction (from a test failure) from blocking collection/database + # operations during test set up and tear down. + for op in spec["operations"]: + name = op["name"] + if name == "startTransaction" or name == "withTransaction": + self.addAsyncCleanup(self.kill_all_sessions) + break + # process createEntities self._uri = uri self.entity_map = EntityMapUtil(self) diff --git a/test/unified_format.py b/test/unified_format.py index 9aee287256..5516a7adf1 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -1451,11 +1451,6 @@ def verify_outcome(self, spec): self.assertListEqual(sorted_expected_documents, actual_documents) def run_scenario(self, spec, uri=None): - # Kill all sessions before and after each test to prevent an open - # transaction (from a test failure) from blocking collection/database - # operations during test set up and tear down. - self.kill_all_sessions() - # Handle flaky tests. flaky_tests = [ ("PYTHON-5170", ".*test_discovery_and_monitoring.*"), @@ -1491,6 +1486,15 @@ def _run_scenario(self, spec, uri=None): if skip_reason is not None: raise unittest.SkipTest(f"{skip_reason}") + # Kill all sessions after each test with transactions to prevent an open + # transaction (from a test failure) from blocking collection/database + # operations during test set up and tear down. + for op in spec["operations"]: + name = op["name"] + if name == "startTransaction" or name == "withTransaction": + self.addCleanup(self.kill_all_sessions) + break + # process createEntities self._uri = uri self.entity_map = EntityMapUtil(self) From b6cc22ffdd91352544b555fcd1f070d8a4c34781 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Mon, 9 Mar 2026 12:37:32 -0700 Subject: [PATCH 093/129] PYTHON-5748 Remove unused SpecRunner class (#2725) --- test/asynchronous/utils_spec_runner.py | 632 +------------------------ test/utils_spec_runner.py | 630 +----------------------- 2 files changed, 8 insertions(+), 1254 deletions(-) diff --git a/test/asynchronous/utils_spec_runner.py b/test/asynchronous/utils_spec_runner.py index 63e7e9e150..ff5f61db06 100644 --- a/test/asynchronous/utils_spec_runner.py +++ b/test/asynchronous/utils_spec_runner.py @@ -16,43 +16,13 @@ from __future__ import annotations import asyncio -import functools import os -import time -import unittest -from collections import abc -from inspect import iscoroutinefunction -from test.asynchronous import AsyncIntegrationTest, async_client_context, client_knobs +from test.asynchronous import async_client_context from test.asynchronous.helpers import ConcurrentRunner -from test.utils_shared import ( - CMAPListener, - CompareType, - EventListener, - OvertCommandListener, - ScenarioDict, - ServerAndTopologyEventListener, - camel_to_snake, - camel_to_snake_args, - parse_spec_options, - prepare_spec_arguments, -) -from typing import List - -from bson import ObjectId, decode, encode, json_util -from bson.binary import Binary -from bson.int64 import Int64 -from bson.son import SON -from gridfs import GridFSBucket -from gridfs.asynchronous.grid_file import AsyncGridFSBucket -from pymongo.asynchronous import client_session -from pymongo.asynchronous.command_cursor import AsyncCommandCursor -from pymongo.asynchronous.cursor import AsyncCursor -from pymongo.errors import AutoReconnect, BulkWriteError, OperationFailure, PyMongoError +from test.utils_shared import ScenarioDict + +from bson import json_util from pymongo.lock import _async_cond_wait, _async_create_condition, _async_create_lock -from pymongo.read_concern import ReadConcern -from pymongo.read_preferences import ReadPreference -from pymongo.results import BulkWriteResult, _WriteResult -from pymongo.write_concern import WriteConcern _IS_SYNC = False @@ -219,597 +189,3 @@ def create_tests(self): self._create_tests() else: asyncio.run(self._create_tests()) - - -class AsyncSpecRunner(AsyncIntegrationTest): - mongos_clients: List - knobs: client_knobs - listener: EventListener - - async def asyncSetUp(self) -> None: - await super().asyncSetUp() - self.mongos_clients = [] - - # Speed up the tests by decreasing the heartbeat frequency. - self.knobs = client_knobs(heartbeat_frequency=0.1, min_heartbeat_interval=0.1) - self.knobs.enable() - self.targets = {} - self.listener = None # type: ignore - self.pool_listener = None - self.server_listener = None - self.maxDiff = None - - async def asyncTearDown(self) -> None: - self.knobs.disable() - - async def set_fail_point(self, command_args): - clients = self.mongos_clients if self.mongos_clients else [self.client] - for client in clients: - await self.configure_fail_point(client, command_args) - - async def targeted_fail_point(self, session, fail_point): - """Run the targetedFailPoint test operation. - - Enable the fail point on the session's pinned mongos. - """ - clients = {c.address: c for c in self.mongos_clients} - client = clients[session._pinned_address] - await self.configure_fail_point(client, fail_point) - self.addAsyncCleanup(self.set_fail_point, {"mode": "off"}) - - def assert_session_pinned(self, session): - """Run the assertSessionPinned test operation. - - Assert that the given session is pinned. - """ - self.assertIsNotNone(session._transaction.pinned_address) - - def assert_session_unpinned(self, session): - """Run the assertSessionUnpinned test operation. - - Assert that the given session is not pinned. - """ - self.assertIsNone(session._pinned_address) - self.assertIsNone(session._transaction.pinned_address) - - async def assert_collection_exists(self, database, collection): - """Run the assertCollectionExists test operation.""" - db = self.client[database] - self.assertIn(collection, await db.list_collection_names()) - - async def assert_collection_not_exists(self, database, collection): - """Run the assertCollectionNotExists test operation.""" - db = self.client[database] - self.assertNotIn(collection, await db.list_collection_names()) - - async def assert_index_exists(self, database, collection, index): - """Run the assertIndexExists test operation.""" - coll = self.client[database][collection] - self.assertIn(index, [doc["name"] async for doc in await coll.list_indexes()]) - - async def assert_index_not_exists(self, database, collection, index): - """Run the assertIndexNotExists test operation.""" - coll = self.client[database][collection] - self.assertNotIn(index, [doc["name"] async for doc in await coll.list_indexes()]) - - async def wait(self, ms): - """Run the "wait" test operation.""" - await asyncio.sleep(ms / 1000.0) - - def assertErrorLabelsContain(self, exc, expected_labels): - labels = [l for l in expected_labels if exc.has_error_label(l)] - self.assertEqual(labels, expected_labels) - - def assertErrorLabelsOmit(self, exc, omit_labels): - for label in omit_labels: - self.assertFalse( - exc.has_error_label(label), msg=f"error labels should not contain {label}" - ) - - async def kill_all_sessions(self): - clients = self.mongos_clients if self.mongos_clients else [self.client] - for client in clients: - try: - await client.admin.command("killAllSessions", []) - except (OperationFailure, AutoReconnect): - # "operation was interrupted" by killing the command's - # own session. - # On 8.0+ killAllSessions sometimes returns a network error. - pass - - def check_command_result(self, expected_result, result): - # Only compare the keys in the expected result. - filtered_result = {} - for key in expected_result: - try: - filtered_result[key] = result[key] - except KeyError: - pass - self.assertEqual(filtered_result, expected_result) - - # TODO: factor the following function with test_crud.py. - def check_result(self, expected_result, result): - if isinstance(result, _WriteResult): - for res in expected_result: - prop = camel_to_snake(res) - # SPEC-869: Only BulkWriteResult has upserted_count. - if prop == "upserted_count" and not isinstance(result, BulkWriteResult): - if result.upserted_id is not None: - upserted_count = 1 - else: - upserted_count = 0 - self.assertEqual(upserted_count, expected_result[res], prop) - elif prop == "inserted_ids": - # BulkWriteResult does not have inserted_ids. - if isinstance(result, BulkWriteResult): - self.assertEqual(len(expected_result[res]), result.inserted_count) - else: - # InsertManyResult may be compared to [id1] from the - # crud spec or {"0": id1} from the retryable write spec. - ids = expected_result[res] - if isinstance(ids, dict): - ids = [ids[str(i)] for i in range(len(ids))] - - self.assertEqual(ids, result.inserted_ids, prop) - elif prop == "upserted_ids": - # Convert indexes from strings to integers. - ids = expected_result[res] - expected_ids = {} - for str_index in ids: - expected_ids[int(str_index)] = ids[str_index] - self.assertEqual(expected_ids, result.upserted_ids, prop) - else: - self.assertEqual(getattr(result, prop), expected_result[res], prop) - - return True - else: - - def _helper(expected_result, result): - if isinstance(expected_result, abc.Mapping): - for i in expected_result.keys(): - self.assertEqual(expected_result[i], result[i]) - - elif isinstance(expected_result, list): - for i, k in zip(expected_result, result): - _helper(i, k) - else: - self.assertEqual(expected_result, result) - - _helper(expected_result, result) - return None - - def get_object_name(self, op): - """Allow subclasses to override handling of 'object' - - Transaction spec says 'object' is required. - """ - return op["object"] - - @staticmethod - def parse_options(opts): - return parse_spec_options(opts) - - async def run_operation(self, sessions, collection, operation): - original_collection = collection - name = camel_to_snake(operation["name"]) - if name == "run_command": - name = "command" - elif name == "download_by_name": - name = "open_download_stream_by_name" - elif name == "download": - name = "open_download_stream" - elif name == "map_reduce": - self.skipTest("PyMongo does not support mapReduce") - elif name == "count": - self.skipTest("PyMongo does not support count") - - database = collection.database - collection = database.get_collection(collection.name) - if "collectionOptions" in operation: - collection = collection.with_options( - **self.parse_options(operation["collectionOptions"]) - ) - - object_name = self.get_object_name(operation) - if object_name == "gridfsbucket": - # Only create the GridFSBucket when we need it (for the gridfs - # retryable reads tests). - obj = AsyncGridFSBucket(database, bucket_name=collection.name) - else: - objects = { - "client": database.client, - "database": database, - "collection": collection, - "testRunner": self, - } - objects.update(sessions) - obj = objects[object_name] - - # Combine arguments with options and handle special cases. - arguments = operation.get("arguments", {}) - arguments.update(arguments.pop("options", {})) - self.parse_options(arguments) - - cmd = getattr(obj, name) - - with_txn_callback = functools.partial( - self.run_operations, sessions, original_collection, in_with_transaction=True - ) - prepare_spec_arguments(operation, arguments, name, sessions, with_txn_callback) - - if name == "run_on_thread": - args = {"sessions": sessions, "collection": collection} - args.update(arguments) - arguments = args - - if not _IS_SYNC and iscoroutinefunction(cmd): - result = await cmd(**dict(arguments)) - else: - result = cmd(**dict(arguments)) - # Cleanup open change stream cursors. - if name == "watch": - self.addAsyncCleanup(result.close) - - if name == "aggregate": - if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: - # Read from the primary to ensure causal consistency. - out = collection.database.get_collection( - arguments["pipeline"][-1]["$out"], read_preference=ReadPreference.PRIMARY - ) - return out.find() - if "download" in name: - result = Binary(result.read()) - - if isinstance(result, AsyncCursor) or isinstance(result, AsyncCommandCursor): - return await result.to_list() - - return result - - def allowable_errors(self, op): - """Allow encryption spec to override expected error classes.""" - return (PyMongoError,) - - async def _run_op(self, sessions, collection, op, in_with_transaction): - expected_result = op.get("result") - if expect_error(op): - with self.assertRaises(self.allowable_errors(op), msg=op["name"]) as context: - await self.run_operation(sessions, collection, op.copy()) - exc = context.exception - if expect_error_message(expected_result): - if isinstance(exc, BulkWriteError): - errmsg = str(exc.details).lower() - else: - errmsg = str(exc).lower() - self.assertIn(expected_result["errorContains"].lower(), errmsg) - if expect_error_code(expected_result): - self.assertEqual(expected_result["errorCodeName"], exc.details.get("codeName")) - if expect_error_labels_contain(expected_result): - self.assertErrorLabelsContain(exc, expected_result["errorLabelsContain"]) - if expect_error_labels_omit(expected_result): - self.assertErrorLabelsOmit(exc, expected_result["errorLabelsOmit"]) - if expect_timeout_error(expected_result): - self.assertIsInstance(exc, PyMongoError) - if not exc.timeout: - # Re-raise the exception for better diagnostics. - raise exc - - # Reraise the exception if we're in the with_transaction - # callback. - if in_with_transaction: - raise context.exception - else: - result = await self.run_operation(sessions, collection, op.copy()) - if "result" in op: - if op["name"] == "runCommand": - self.check_command_result(expected_result, result) - else: - self.check_result(expected_result, result) - - async def run_operations(self, sessions, collection, ops, in_with_transaction=False): - for op in ops: - await self._run_op(sessions, collection, op, in_with_transaction) - - # TODO: factor with test_command_monitoring.py - def check_events(self, test, listener, session_ids): - events = listener.started_events - if not len(test["expectations"]): - return - - # Give a nicer message when there are missing or extra events - cmds = decode_raw([event.command for event in events]) - self.assertEqual(len(events), len(test["expectations"]), cmds) - for i, expectation in enumerate(test["expectations"]): - event_type = next(iter(expectation)) - event = events[i] - - # The tests substitute 42 for any number other than 0. - if event.command_name == "getMore" and event.command["getMore"]: - event.command["getMore"] = Int64(42) - elif event.command_name == "killCursors": - event.command["cursors"] = [Int64(42)] - elif event.command_name == "update": - # TODO: remove this once PYTHON-1744 is done. - # Add upsert and multi fields back into expectations. - updates = expectation[event_type]["command"]["updates"] - for update in updates: - update.setdefault("upsert", False) - update.setdefault("multi", False) - - # Replace afterClusterTime: 42 with actual afterClusterTime. - expected_cmd = expectation[event_type]["command"] - expected_read_concern = expected_cmd.get("readConcern") - if expected_read_concern is not None: - time = expected_read_concern.get("afterClusterTime") - if time == 42: - actual_time = event.command.get("readConcern", {}).get("afterClusterTime") - if actual_time is not None: - expected_read_concern["afterClusterTime"] = actual_time - - recovery_token = expected_cmd.get("recoveryToken") - if recovery_token == 42: - expected_cmd["recoveryToken"] = CompareType(dict) - - # Replace lsid with a name like "session0" to match test. - if "lsid" in event.command: - for name, lsid in session_ids.items(): - if event.command["lsid"] == lsid: - event.command["lsid"] = name - break - - for attr, expected in expectation[event_type].items(): - actual = getattr(event, attr) - expected = wrap_types(expected) - if isinstance(expected, dict): - for key, val in expected.items(): - if val is None: - if key in actual: - self.fail(f"Unexpected key [{key}] in {actual!r}") - elif key not in actual: - self.fail(f"Expected key [{key}] in {actual!r}") - else: - self.assertEqual( - val, decode_raw(actual[key]), f"Key [{key}] in {actual}" - ) - else: - self.assertEqual(actual, expected) - - def maybe_skip_scenario(self, test): - if test.get("skipReason"): - self.skipTest(test.get("skipReason")) - - def get_scenario_db_name(self, scenario_def): - """Allow subclasses to override a test's database name.""" - return scenario_def["database_name"] - - def get_scenario_coll_name(self, scenario_def): - """Allow subclasses to override a test's collection name.""" - return scenario_def["collection_name"] - - def get_outcome_coll_name(self, outcome, collection): - """Allow subclasses to override outcome collection.""" - return collection.name - - async def run_test_ops(self, sessions, collection, test): - """Added to allow retryable writes spec to override a test's - operation. - """ - await self.run_operations(sessions, collection, test["operations"]) - - def parse_client_options(self, opts): - """Allow encryption spec to override a clientOptions parsing.""" - return opts - - async def setup_scenario(self, scenario_def): - """Allow specs to override a test's setup.""" - db_name = self.get_scenario_db_name(scenario_def) - coll_name = self.get_scenario_coll_name(scenario_def) - documents = scenario_def["data"] - - # Setup the collection with as few majority writes as possible. - db = async_client_context.client.get_database(db_name) - coll_exists = bool(await db.list_collection_names(filter={"name": coll_name})) - if coll_exists: - await db[coll_name].delete_many({}) - # Only use majority wc only on the final write. - wc = WriteConcern(w="majority") - if documents: - db.get_collection(coll_name, write_concern=wc).insert_many(documents) - elif not coll_exists: - # Ensure collection exists. - await db.create_collection(coll_name, write_concern=wc) - - async def run_scenario(self, scenario_def, test): - self.maybe_skip_scenario(test) - - # Kill all sessions before and after each test to prevent an open - # transaction (from a test failure) from blocking collection/database - # operations during test set up and tear down. - await self.kill_all_sessions() - self.addAsyncCleanup(self.kill_all_sessions) - await self.setup_scenario(scenario_def) - database_name = self.get_scenario_db_name(scenario_def) - collection_name = self.get_scenario_coll_name(scenario_def) - # SPEC-1245 workaround StaleDbVersion on distinct - for c in self.mongos_clients: - await c[database_name][collection_name].distinct("x") - - # Configure the fail point before creating the client. - if "failPoint" in test: - fp = test["failPoint"] - await self.set_fail_point(fp) - self.addAsyncCleanup( - self.set_fail_point, {"configureFailPoint": fp["configureFailPoint"], "mode": "off"} - ) - - listener = OvertCommandListener() - pool_listener = CMAPListener() - server_listener = ServerAndTopologyEventListener() - # Create a new client, to avoid interference from pooled sessions. - client_options = self.parse_client_options(test["clientOptions"]) - use_multi_mongos = test["useMultipleMongoses"] - host = None - if use_multi_mongos: - if async_client_context.load_balancer: - host = async_client_context.MULTI_MONGOS_LB_URI - elif async_client_context.is_mongos: - host = async_client_context.mongos_seeds() - client = await self.async_rs_client( - h=host, event_listeners=[listener, pool_listener, server_listener], **client_options - ) - self.scenario_client = client - self.listener = listener - self.pool_listener = pool_listener - self.server_listener = server_listener - - # Create session0 and session1. - sessions = {} - session_ids = {} - for i in range(2): - # Don't attempt to create sessions if they are not supported by - # the running server version. - if not async_client_context.sessions_enabled: - break - session_name = "session%d" % i - opts = camel_to_snake_args(test["sessionOptions"][session_name]) - if "default_transaction_options" in opts: - txn_opts = self.parse_options(opts["default_transaction_options"]) - txn_opts = client_session.TransactionOptions(**txn_opts) - opts["default_transaction_options"] = txn_opts - - s = client.start_session(**dict(opts)) - - sessions[session_name] = s - # Store lsid so we can access it after end_session, in check_events. - session_ids[session_name] = s.session_id - - self.addAsyncCleanup(end_sessions, sessions) - - collection = client[database_name][collection_name] - await self.run_test_ops(sessions, collection, test) - - await end_sessions(sessions) - - self.check_events(test, listener, session_ids) - - # Disable fail points. - if "failPoint" in test: - fp = test["failPoint"] - await self.set_fail_point( - {"configureFailPoint": fp["configureFailPoint"], "mode": "off"} - ) - - # Assert final state is expected. - outcome = test["outcome"] - expected_c = outcome.get("collection") - if expected_c is not None: - outcome_coll_name = self.get_outcome_coll_name(outcome, collection) - - # Read from the primary with local read concern to ensure causal - # consistency. - outcome_coll = async_client_context.client[collection.database.name].get_collection( - outcome_coll_name, - read_preference=ReadPreference.PRIMARY, - read_concern=ReadConcern("local"), - ) - actual_data = await outcome_coll.find(sort=[("_id", 1)]).to_list() - - # The expected data needs to be the left hand side here otherwise - # CompareType(Binary) doesn't work. - self.assertEqual(wrap_types(expected_c["data"]), actual_data) - - -def expect_any_error(op): - if isinstance(op, dict): - return op.get("error") - - return False - - -def expect_error_message(expected_result): - if isinstance(expected_result, dict): - return isinstance(expected_result["errorContains"], str) - - return False - - -def expect_error_code(expected_result): - if isinstance(expected_result, dict): - return expected_result["errorCodeName"] - - return False - - -def expect_error_labels_contain(expected_result): - if isinstance(expected_result, dict): - return expected_result["errorLabelsContain"] - - return False - - -def expect_error_labels_omit(expected_result): - if isinstance(expected_result, dict): - return expected_result["errorLabelsOmit"] - - return False - - -def expect_timeout_error(expected_result): - if isinstance(expected_result, dict): - return expected_result["isTimeoutError"] - - return False - - -def expect_error(op): - expected_result = op.get("result") - return ( - expect_any_error(op) - or expect_error_message(expected_result) - or expect_error_code(expected_result) - or expect_error_labels_contain(expected_result) - or expect_error_labels_omit(expected_result) - or expect_timeout_error(expected_result) - ) - - -async def end_sessions(sessions): - for s in sessions.values(): - # Aborts the transaction if it's open. - await s.end_session() - - -def decode_raw(val): - """Decode RawBSONDocuments in the given container.""" - if isinstance(val, (list, abc.Mapping)): - return decode(encode({"v": val}))["v"] - return val - - -TYPES = { - "binData": Binary, - "long": Int64, - "int": int, - "string": str, - "objectId": ObjectId, - "object": dict, - "array": list, -} - - -def wrap_types(val): - """Support $$type assertion in command results.""" - if isinstance(val, list): - return [wrap_types(v) for v in val] - if isinstance(val, abc.Mapping): - typ = val.get("$$type") - if typ: - if isinstance(typ, str): - types = TYPES[typ] - else: - types = tuple(TYPES[t] for t in typ) - return CompareType(types) - d = {} - for key in val: - d[key] = wrap_types(val[key]) - return d - return val diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index 9bf155e8f3..f4c1c6bfcc 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -16,43 +16,13 @@ from __future__ import annotations import asyncio -import functools import os -import time -import unittest -from collections import abc -from inspect import iscoroutinefunction -from test import IntegrationTest, client_context, client_knobs +from test import client_context from test.helpers import ConcurrentRunner -from test.utils_shared import ( - CMAPListener, - CompareType, - EventListener, - OvertCommandListener, - ScenarioDict, - ServerAndTopologyEventListener, - camel_to_snake, - camel_to_snake_args, - parse_spec_options, - prepare_spec_arguments, -) -from typing import List - -from bson import ObjectId, decode, encode, json_util -from bson.binary import Binary -from bson.int64 import Int64 -from bson.son import SON -from gridfs import GridFSBucket -from gridfs.synchronous.grid_file import GridFSBucket -from pymongo.errors import AutoReconnect, BulkWriteError, OperationFailure, PyMongoError +from test.utils_shared import ScenarioDict + +from bson import json_util from pymongo.lock import _cond_wait, _create_condition, _create_lock -from pymongo.read_concern import ReadConcern -from pymongo.read_preferences import ReadPreference -from pymongo.results import BulkWriteResult, _WriteResult -from pymongo.synchronous import client_session -from pymongo.synchronous.command_cursor import CommandCursor -from pymongo.synchronous.cursor import Cursor -from pymongo.write_concern import WriteConcern _IS_SYNC = True @@ -219,595 +189,3 @@ def create_tests(self): self._create_tests() else: asyncio.run(self._create_tests()) - - -class SpecRunner(IntegrationTest): - mongos_clients: List - knobs: client_knobs - listener: EventListener - - def setUp(self) -> None: - super().setUp() - self.mongos_clients = [] - - # Speed up the tests by decreasing the heartbeat frequency. - self.knobs = client_knobs(heartbeat_frequency=0.1, min_heartbeat_interval=0.1) - self.knobs.enable() - self.targets = {} - self.listener = None # type: ignore - self.pool_listener = None - self.server_listener = None - self.maxDiff = None - - def tearDown(self) -> None: - self.knobs.disable() - - def set_fail_point(self, command_args): - clients = self.mongos_clients if self.mongos_clients else [self.client] - for client in clients: - self.configure_fail_point(client, command_args) - - def targeted_fail_point(self, session, fail_point): - """Run the targetedFailPoint test operation. - - Enable the fail point on the session's pinned mongos. - """ - clients = {c.address: c for c in self.mongos_clients} - client = clients[session._pinned_address] - self.configure_fail_point(client, fail_point) - self.addCleanup(self.set_fail_point, {"mode": "off"}) - - def assert_session_pinned(self, session): - """Run the assertSessionPinned test operation. - - Assert that the given session is pinned. - """ - self.assertIsNotNone(session._transaction.pinned_address) - - def assert_session_unpinned(self, session): - """Run the assertSessionUnpinned test operation. - - Assert that the given session is not pinned. - """ - self.assertIsNone(session._pinned_address) - self.assertIsNone(session._transaction.pinned_address) - - def assert_collection_exists(self, database, collection): - """Run the assertCollectionExists test operation.""" - db = self.client[database] - self.assertIn(collection, db.list_collection_names()) - - def assert_collection_not_exists(self, database, collection): - """Run the assertCollectionNotExists test operation.""" - db = self.client[database] - self.assertNotIn(collection, db.list_collection_names()) - - def assert_index_exists(self, database, collection, index): - """Run the assertIndexExists test operation.""" - coll = self.client[database][collection] - self.assertIn(index, [doc["name"] for doc in coll.list_indexes()]) - - def assert_index_not_exists(self, database, collection, index): - """Run the assertIndexNotExists test operation.""" - coll = self.client[database][collection] - self.assertNotIn(index, [doc["name"] for doc in coll.list_indexes()]) - - def wait(self, ms): - """Run the "wait" test operation.""" - time.sleep(ms / 1000.0) - - def assertErrorLabelsContain(self, exc, expected_labels): - labels = [l for l in expected_labels if exc.has_error_label(l)] - self.assertEqual(labels, expected_labels) - - def assertErrorLabelsOmit(self, exc, omit_labels): - for label in omit_labels: - self.assertFalse( - exc.has_error_label(label), msg=f"error labels should not contain {label}" - ) - - def kill_all_sessions(self): - clients = self.mongos_clients if self.mongos_clients else [self.client] - for client in clients: - try: - client.admin.command("killAllSessions", []) - except (OperationFailure, AutoReconnect): - # "operation was interrupted" by killing the command's - # own session. - # On 8.0+ killAllSessions sometimes returns a network error. - pass - - def check_command_result(self, expected_result, result): - # Only compare the keys in the expected result. - filtered_result = {} - for key in expected_result: - try: - filtered_result[key] = result[key] - except KeyError: - pass - self.assertEqual(filtered_result, expected_result) - - # TODO: factor the following function with test_crud.py. - def check_result(self, expected_result, result): - if isinstance(result, _WriteResult): - for res in expected_result: - prop = camel_to_snake(res) - # SPEC-869: Only BulkWriteResult has upserted_count. - if prop == "upserted_count" and not isinstance(result, BulkWriteResult): - if result.upserted_id is not None: - upserted_count = 1 - else: - upserted_count = 0 - self.assertEqual(upserted_count, expected_result[res], prop) - elif prop == "inserted_ids": - # BulkWriteResult does not have inserted_ids. - if isinstance(result, BulkWriteResult): - self.assertEqual(len(expected_result[res]), result.inserted_count) - else: - # InsertManyResult may be compared to [id1] from the - # crud spec or {"0": id1} from the retryable write spec. - ids = expected_result[res] - if isinstance(ids, dict): - ids = [ids[str(i)] for i in range(len(ids))] - - self.assertEqual(ids, result.inserted_ids, prop) - elif prop == "upserted_ids": - # Convert indexes from strings to integers. - ids = expected_result[res] - expected_ids = {} - for str_index in ids: - expected_ids[int(str_index)] = ids[str_index] - self.assertEqual(expected_ids, result.upserted_ids, prop) - else: - self.assertEqual(getattr(result, prop), expected_result[res], prop) - - return True - else: - - def _helper(expected_result, result): - if isinstance(expected_result, abc.Mapping): - for i in expected_result.keys(): - self.assertEqual(expected_result[i], result[i]) - - elif isinstance(expected_result, list): - for i, k in zip(expected_result, result): - _helper(i, k) - else: - self.assertEqual(expected_result, result) - - _helper(expected_result, result) - return None - - def get_object_name(self, op): - """Allow subclasses to override handling of 'object' - - Transaction spec says 'object' is required. - """ - return op["object"] - - @staticmethod - def parse_options(opts): - return parse_spec_options(opts) - - def run_operation(self, sessions, collection, operation): - original_collection = collection - name = camel_to_snake(operation["name"]) - if name == "run_command": - name = "command" - elif name == "download_by_name": - name = "open_download_stream_by_name" - elif name == "download": - name = "open_download_stream" - elif name == "map_reduce": - self.skipTest("PyMongo does not support mapReduce") - elif name == "count": - self.skipTest("PyMongo does not support count") - - database = collection.database - collection = database.get_collection(collection.name) - if "collectionOptions" in operation: - collection = collection.with_options( - **self.parse_options(operation["collectionOptions"]) - ) - - object_name = self.get_object_name(operation) - if object_name == "gridfsbucket": - # Only create the GridFSBucket when we need it (for the gridfs - # retryable reads tests). - obj = GridFSBucket(database, bucket_name=collection.name) - else: - objects = { - "client": database.client, - "database": database, - "collection": collection, - "testRunner": self, - } - objects.update(sessions) - obj = objects[object_name] - - # Combine arguments with options and handle special cases. - arguments = operation.get("arguments", {}) - arguments.update(arguments.pop("options", {})) - self.parse_options(arguments) - - cmd = getattr(obj, name) - - with_txn_callback = functools.partial( - self.run_operations, sessions, original_collection, in_with_transaction=True - ) - prepare_spec_arguments(operation, arguments, name, sessions, with_txn_callback) - - if name == "run_on_thread": - args = {"sessions": sessions, "collection": collection} - args.update(arguments) - arguments = args - - if not _IS_SYNC and iscoroutinefunction(cmd): - result = cmd(**dict(arguments)) - else: - result = cmd(**dict(arguments)) - # Cleanup open change stream cursors. - if name == "watch": - self.addCleanup(result.close) - - if name == "aggregate": - if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: - # Read from the primary to ensure causal consistency. - out = collection.database.get_collection( - arguments["pipeline"][-1]["$out"], read_preference=ReadPreference.PRIMARY - ) - return out.find() - if "download" in name: - result = Binary(result.read()) - - if isinstance(result, Cursor) or isinstance(result, CommandCursor): - return result.to_list() - - return result - - def allowable_errors(self, op): - """Allow encryption spec to override expected error classes.""" - return (PyMongoError,) - - def _run_op(self, sessions, collection, op, in_with_transaction): - expected_result = op.get("result") - if expect_error(op): - with self.assertRaises(self.allowable_errors(op), msg=op["name"]) as context: - self.run_operation(sessions, collection, op.copy()) - exc = context.exception - if expect_error_message(expected_result): - if isinstance(exc, BulkWriteError): - errmsg = str(exc.details).lower() - else: - errmsg = str(exc).lower() - self.assertIn(expected_result["errorContains"].lower(), errmsg) - if expect_error_code(expected_result): - self.assertEqual(expected_result["errorCodeName"], exc.details.get("codeName")) - if expect_error_labels_contain(expected_result): - self.assertErrorLabelsContain(exc, expected_result["errorLabelsContain"]) - if expect_error_labels_omit(expected_result): - self.assertErrorLabelsOmit(exc, expected_result["errorLabelsOmit"]) - if expect_timeout_error(expected_result): - self.assertIsInstance(exc, PyMongoError) - if not exc.timeout: - # Re-raise the exception for better diagnostics. - raise exc - - # Reraise the exception if we're in the with_transaction - # callback. - if in_with_transaction: - raise context.exception - else: - result = self.run_operation(sessions, collection, op.copy()) - if "result" in op: - if op["name"] == "runCommand": - self.check_command_result(expected_result, result) - else: - self.check_result(expected_result, result) - - def run_operations(self, sessions, collection, ops, in_with_transaction=False): - for op in ops: - self._run_op(sessions, collection, op, in_with_transaction) - - # TODO: factor with test_command_monitoring.py - def check_events(self, test, listener, session_ids): - events = listener.started_events - if not len(test["expectations"]): - return - - # Give a nicer message when there are missing or extra events - cmds = decode_raw([event.command for event in events]) - self.assertEqual(len(events), len(test["expectations"]), cmds) - for i, expectation in enumerate(test["expectations"]): - event_type = next(iter(expectation)) - event = events[i] - - # The tests substitute 42 for any number other than 0. - if event.command_name == "getMore" and event.command["getMore"]: - event.command["getMore"] = Int64(42) - elif event.command_name == "killCursors": - event.command["cursors"] = [Int64(42)] - elif event.command_name == "update": - # TODO: remove this once PYTHON-1744 is done. - # Add upsert and multi fields back into expectations. - updates = expectation[event_type]["command"]["updates"] - for update in updates: - update.setdefault("upsert", False) - update.setdefault("multi", False) - - # Replace afterClusterTime: 42 with actual afterClusterTime. - expected_cmd = expectation[event_type]["command"] - expected_read_concern = expected_cmd.get("readConcern") - if expected_read_concern is not None: - time = expected_read_concern.get("afterClusterTime") - if time == 42: - actual_time = event.command.get("readConcern", {}).get("afterClusterTime") - if actual_time is not None: - expected_read_concern["afterClusterTime"] = actual_time - - recovery_token = expected_cmd.get("recoveryToken") - if recovery_token == 42: - expected_cmd["recoveryToken"] = CompareType(dict) - - # Replace lsid with a name like "session0" to match test. - if "lsid" in event.command: - for name, lsid in session_ids.items(): - if event.command["lsid"] == lsid: - event.command["lsid"] = name - break - - for attr, expected in expectation[event_type].items(): - actual = getattr(event, attr) - expected = wrap_types(expected) - if isinstance(expected, dict): - for key, val in expected.items(): - if val is None: - if key in actual: - self.fail(f"Unexpected key [{key}] in {actual!r}") - elif key not in actual: - self.fail(f"Expected key [{key}] in {actual!r}") - else: - self.assertEqual( - val, decode_raw(actual[key]), f"Key [{key}] in {actual}" - ) - else: - self.assertEqual(actual, expected) - - def maybe_skip_scenario(self, test): - if test.get("skipReason"): - self.skipTest(test.get("skipReason")) - - def get_scenario_db_name(self, scenario_def): - """Allow subclasses to override a test's database name.""" - return scenario_def["database_name"] - - def get_scenario_coll_name(self, scenario_def): - """Allow subclasses to override a test's collection name.""" - return scenario_def["collection_name"] - - def get_outcome_coll_name(self, outcome, collection): - """Allow subclasses to override outcome collection.""" - return collection.name - - def run_test_ops(self, sessions, collection, test): - """Added to allow retryable writes spec to override a test's - operation. - """ - self.run_operations(sessions, collection, test["operations"]) - - def parse_client_options(self, opts): - """Allow encryption spec to override a clientOptions parsing.""" - return opts - - def setup_scenario(self, scenario_def): - """Allow specs to override a test's setup.""" - db_name = self.get_scenario_db_name(scenario_def) - coll_name = self.get_scenario_coll_name(scenario_def) - documents = scenario_def["data"] - - # Setup the collection with as few majority writes as possible. - db = client_context.client.get_database(db_name) - coll_exists = bool(db.list_collection_names(filter={"name": coll_name})) - if coll_exists: - db[coll_name].delete_many({}) - # Only use majority wc only on the final write. - wc = WriteConcern(w="majority") - if documents: - db.get_collection(coll_name, write_concern=wc).insert_many(documents) - elif not coll_exists: - # Ensure collection exists. - db.create_collection(coll_name, write_concern=wc) - - def run_scenario(self, scenario_def, test): - self.maybe_skip_scenario(test) - - # Kill all sessions before and after each test to prevent an open - # transaction (from a test failure) from blocking collection/database - # operations during test set up and tear down. - self.kill_all_sessions() - self.addCleanup(self.kill_all_sessions) - self.setup_scenario(scenario_def) - database_name = self.get_scenario_db_name(scenario_def) - collection_name = self.get_scenario_coll_name(scenario_def) - # SPEC-1245 workaround StaleDbVersion on distinct - for c in self.mongos_clients: - c[database_name][collection_name].distinct("x") - - # Configure the fail point before creating the client. - if "failPoint" in test: - fp = test["failPoint"] - self.set_fail_point(fp) - self.addCleanup( - self.set_fail_point, {"configureFailPoint": fp["configureFailPoint"], "mode": "off"} - ) - - listener = OvertCommandListener() - pool_listener = CMAPListener() - server_listener = ServerAndTopologyEventListener() - # Create a new client, to avoid interference from pooled sessions. - client_options = self.parse_client_options(test["clientOptions"]) - use_multi_mongos = test["useMultipleMongoses"] - host = None - if use_multi_mongos: - if client_context.load_balancer: - host = client_context.MULTI_MONGOS_LB_URI - elif client_context.is_mongos: - host = client_context.mongos_seeds() - client = self.rs_client( - h=host, event_listeners=[listener, pool_listener, server_listener], **client_options - ) - self.scenario_client = client - self.listener = listener - self.pool_listener = pool_listener - self.server_listener = server_listener - - # Create session0 and session1. - sessions = {} - session_ids = {} - for i in range(2): - # Don't attempt to create sessions if they are not supported by - # the running server version. - if not client_context.sessions_enabled: - break - session_name = "session%d" % i - opts = camel_to_snake_args(test["sessionOptions"][session_name]) - if "default_transaction_options" in opts: - txn_opts = self.parse_options(opts["default_transaction_options"]) - txn_opts = client_session.TransactionOptions(**txn_opts) - opts["default_transaction_options"] = txn_opts - - s = client.start_session(**dict(opts)) - - sessions[session_name] = s - # Store lsid so we can access it after end_session, in check_events. - session_ids[session_name] = s.session_id - - self.addCleanup(end_sessions, sessions) - - collection = client[database_name][collection_name] - self.run_test_ops(sessions, collection, test) - - end_sessions(sessions) - - self.check_events(test, listener, session_ids) - - # Disable fail points. - if "failPoint" in test: - fp = test["failPoint"] - self.set_fail_point({"configureFailPoint": fp["configureFailPoint"], "mode": "off"}) - - # Assert final state is expected. - outcome = test["outcome"] - expected_c = outcome.get("collection") - if expected_c is not None: - outcome_coll_name = self.get_outcome_coll_name(outcome, collection) - - # Read from the primary with local read concern to ensure causal - # consistency. - outcome_coll = client_context.client[collection.database.name].get_collection( - outcome_coll_name, - read_preference=ReadPreference.PRIMARY, - read_concern=ReadConcern("local"), - ) - actual_data = outcome_coll.find(sort=[("_id", 1)]).to_list() - - # The expected data needs to be the left hand side here otherwise - # CompareType(Binary) doesn't work. - self.assertEqual(wrap_types(expected_c["data"]), actual_data) - - -def expect_any_error(op): - if isinstance(op, dict): - return op.get("error") - - return False - - -def expect_error_message(expected_result): - if isinstance(expected_result, dict): - return isinstance(expected_result["errorContains"], str) - - return False - - -def expect_error_code(expected_result): - if isinstance(expected_result, dict): - return expected_result["errorCodeName"] - - return False - - -def expect_error_labels_contain(expected_result): - if isinstance(expected_result, dict): - return expected_result["errorLabelsContain"] - - return False - - -def expect_error_labels_omit(expected_result): - if isinstance(expected_result, dict): - return expected_result["errorLabelsOmit"] - - return False - - -def expect_timeout_error(expected_result): - if isinstance(expected_result, dict): - return expected_result["isTimeoutError"] - - return False - - -def expect_error(op): - expected_result = op.get("result") - return ( - expect_any_error(op) - or expect_error_message(expected_result) - or expect_error_code(expected_result) - or expect_error_labels_contain(expected_result) - or expect_error_labels_omit(expected_result) - or expect_timeout_error(expected_result) - ) - - -def end_sessions(sessions): - for s in sessions.values(): - # Aborts the transaction if it's open. - s.end_session() - - -def decode_raw(val): - """Decode RawBSONDocuments in the given container.""" - if isinstance(val, (list, abc.Mapping)): - return decode(encode({"v": val}))["v"] - return val - - -TYPES = { - "binData": Binary, - "long": Int64, - "int": int, - "string": str, - "objectId": ObjectId, - "object": dict, - "array": list, -} - - -def wrap_types(val): - """Support $$type assertion in command results.""" - if isinstance(val, list): - return [wrap_types(v) for v in val] - if isinstance(val, abc.Mapping): - typ = val.get("$$type") - if typ: - if isinstance(typ, str): - types = TYPES[typ] - else: - types = tuple(TYPES[t] for t in typ) - return CompareType(types) - d = {} - for key in val: - d[key] = wrap_types(val[key]) - return d - return val From 3d89d9faca27ad7a9cc82f6bf05f01250a6a1655 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 11 Mar 2026 14:09:11 -0400 Subject: [PATCH 094/129] PYTHON-5754 Fix USE_ACTIVE_VENV support (#2728) --- .evergreen/scripts/setup_tests.py | 4 ++++ justfile | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 009de9f1d1..e188dcaa9d 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -153,6 +153,10 @@ def handle_test_env() -> None: # Start compiling the args we'll pass to uv. UV_ARGS = ["--extra test --no-group dev"] + # If USE_ACTIVE_VENV is set, add --active to UV_ARGS so run-tests.sh uses the active venv. + if is_set("USE_ACTIVE_VENV"): + UV_ARGS.append("--active") + test_title = test_name if sub_test_name: test_title += f" {sub_test_name}" diff --git a/justfile b/justfile index 82b1ac91dc..78f48c2c75 100644 --- a/justfile +++ b/justfile @@ -57,7 +57,9 @@ lint-manual *args="": && resync [group('test')] test *args="-v --durations=5 --maxfail=10": && resync - uv run --extra test python -m pytest {{args}} + #!/usr/bin/env bash + set -euo pipefail + uv run ${USE_ACTIVE_VENV:+--active} --extra test python -m pytest {{args}} [group('test')] test-numpy *args="": && resync From 80c3ff2aee38c8f0e9bdb50d325fe267d0e1d1fb Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Thu, 12 Mar 2026 12:42:15 -0400 Subject: [PATCH 095/129] PYTHON-5753 Add just recipes for running coverage tests locally (#2727) --- .gitignore | 1 + CONTRIBUTING.md | 1 + justfile | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/.gitignore b/.gitignore index cb4940a55e..f69f274044 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ test/lambda/*.json xunit-results/ coverage.xml server.log +.coverage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4eafeddcb..86c5e64559 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -205,6 +205,7 @@ the pages will re-render and the browser will automatically refresh. and the `` to test a full module. For example: `just test test/test_change_stream.py::TestUnifiedChangeStreamsErrors::test_change_stream_errors_on_ElectionInProgress`. - Use the `-k` argument to select tests by pattern. +- Run `just test-coverage` to run tests with coverage and display a report. After running tests with coverage, use `just coverage-html` to generate an HTML report in `htmlcov/index.html`. ## Running tests that require secrets, services, or other configuration diff --git a/justfile b/justfile index 78f48c2c75..3a8e216dbc 100644 --- a/justfile +++ b/justfile @@ -82,6 +82,25 @@ teardown-tests: integration-tests: bash integration_tests/run.sh +[group('test')] +test-coverage *args="": + just setup-tests --cov + just run-tests {{args}} + +[group('coverage')] +coverage-report: + uv tool run --with "coverage[toml]" coverage report + +[group('coverage')] +coverage-html: + uv tool run --with "coverage[toml]" coverage html + @echo "Coverage report generated in htmlcov/index.html" + +[group('coverage')] +coverage-xml: + uv tool run --with "coverage[toml]" coverage xml + @echo "Coverage report generated in coverage.xml" + [group('server')] run-server *args="": bash .evergreen/scripts/run-server.sh {{args}} From 13085ff6791968d573177664abac1ca1ba6929be Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 18 Mar 2026 13:19:18 -0400 Subject: [PATCH 096/129] PYTHON-5758 Remove unused validation functions (#2733) --- pymongo/common.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/pymongo/common.py b/pymongo/common.py index e23adac426..118cca89db 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -233,13 +233,6 @@ def validate_readable(option: str, value: Any) -> Optional[str]: return value -def validate_positive_integer_or_none(option: str, value: Any) -> Optional[int]: - """Validate that 'value' is a positive integer or None.""" - if value is None: - return value - return validate_positive_integer(option, value) - - def validate_non_negative_integer_or_none(option: str, value: Any) -> Optional[int]: """Validate that 'value' is a positive integer or 0 or None.""" if value is None: @@ -261,20 +254,6 @@ def validate_string_or_none(option: str, value: Any) -> Optional[str]: return validate_string(option, value) -def validate_int_or_basestring(option: str, value: Any) -> Union[int, str]: - """Validates that 'value' is an integer or string.""" - if isinstance(value, int): - return value - elif isinstance(value, str): - try: - return int(value) - except ValueError: - return value - raise TypeError( - f"Wrong type for {option}, value must be an integer or a string, not {type(value)}" - ) - - def validate_non_negative_int_or_basestring(option: Any, value: Any) -> Union[int, str]: """Validates that 'value' is an integer or string.""" if isinstance(value, int): @@ -817,16 +796,6 @@ def validate_server_monitoring_mode(option: str, value: str) -> str: "waitqueuetimeoutms", ] -_AUTH_OPTIONS = frozenset(["authmechanismproperties"]) - - -def validate_auth_option(option: str, value: Any) -> tuple[str, Any]: - """Validate optional authentication parameters.""" - lower, value = validate(option, value) - if lower not in _AUTH_OPTIONS: - raise ConfigurationError(f"Unknown option: {option}. Must be in {_AUTH_OPTIONS}") - return option, value - def _get_validator( key: str, validators: dict[str, Callable[[Any, Any], Any]], normed_key: Optional[str] = None From ec9d95413c2bc993d8cf0cd7fa7fbd83daf862b8 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 18 Mar 2026 17:46:23 -0400 Subject: [PATCH 097/129] PYTHON-5757 Deprecate Python 2 methods in SON (#2732) --- bson/son.py | 16 ++++++++++++++++ doc/changelog.rst | 5 +++++ test/test_son.py | 4 +--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/bson/son.py b/bson/son.py index 8fd4f95cd2..ccb6bdb273 100644 --- a/bson/son.py +++ b/bson/son.py @@ -22,6 +22,7 @@ import copy import re +import warnings from collections.abc import Mapping as _Mapping from typing import ( Any, @@ -99,13 +100,28 @@ def __iter__(self) -> Iterator[_Key]: yield from self.__keys def has_key(self, key: _Key) -> bool: + warnings.warn( + "SON.has_key() is deprecated, use the in operator instead", + DeprecationWarning, + stacklevel=2, + ) return key in self.__keys def iterkeys(self) -> Iterator[_Key]: + warnings.warn( + "SON.iterkeys() is deprecated, use the keys() method instead", + DeprecationWarning, + stacklevel=2, + ) return self.__iter__() # fourth level uses definitions from lower levels def itervalues(self) -> Iterator[_Value]: + warnings.warn( + "SON.itervalues() is deprecated, use the values() method instead", + DeprecationWarning, + stacklevel=2, + ) for _, v in self.items(): yield v diff --git a/doc/changelog.rst b/doc/changelog.rst index f38709203c..23d5b2fc9e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,6 +6,11 @@ Changes in Version 4.17.0 (2026/XX/XX) PyMongo 4.17 brings a number of changes including: +- ``has_key``, ``iterkeys`` and ``itervalues`` in :class:`bson.son.SON` have + been deprecated and will be removed in PyMongo 5.0. These methods were + deprecated in favor of the standard dictionary containment operator ``in`` + and the ``keys()`` and ``values()`` methods, respectively. + - Added the :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.bind` and :meth:`~pymongo.client_session.ClientSession.bind` methods that allow users to bind a session to all database operations within the scope of a context manager instead of having to explicitly pass the session to each individual operation. See for examples and more information. diff --git a/test/test_son.py b/test/test_son.py index 36a6834889..3d2069a4c2 100644 --- a/test/test_son.py +++ b/test/test_son.py @@ -145,13 +145,11 @@ def test_iteration(self): self.assertEqual(ele * 100, test_son[ele]) def test_contains_has(self): - """has_key and __contains__""" + """Test key membership via 'in' and __contains__.""" test_son = SON([(1, 100), (2, 200), (3, 300)]) self.assertIn(1, test_son) self.assertIn(2, test_son, "in failed") self.assertNotIn(22, test_son, "in succeeded when it shouldn't") - self.assertTrue(test_son.has_key(2), "has_key failed") - self.assertFalse(test_son.has_key(22), "has_key succeeded when it shouldn't") def test_clears(self): """Test clear()""" From c3428789fb8bc2a1be20aa345b8f508d05e79be8 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Mon, 23 Mar 2026 10:55:50 -0400 Subject: [PATCH 098/129] PYTHON-5766 Add codecov badge to readme (#2737) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c807733e5b..c28ef713b1 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Python Versions](https://img.shields.io/pypi/pyversions/pymongo)](https://pypi.org/project/pymongo) [![Monthly Downloads](https://static.pepy.tech/badge/pymongo/month)](https://pepy.tech/project/pymongo) [![API Documentation Status](https://readthedocs.org/projects/pymongo/badge/?version=stable)](http://pymongo.readthedocs.io/en/stable/api?badge=stable) +[![codecov](https://codecov.io/gh/mongodb/mongo-python-driver/graph/badge.svg?branch=master)](https://codecov.io/gh/mongodb/mongo-python-driver) ## About From daba50c797abc6ed8cae8d2087002903396c93be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:56:12 -0400 Subject: [PATCH 099/129] Bump the actions group across 1 directory with 4 updates (#2736) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dist.yml | 10 +++++----- .github/workflows/release-python.yml | 2 +- .github/workflows/sbom.yml | 2 +- .github/workflows/test-python.yml | 4 ++-- .github/workflows/zizmor.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 645cb74de2..579bcc5f49 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -61,7 +61,7 @@ jobs: - name: Set up QEMU if: runner.os == 'Linux' - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 with: # setup-qemu-action by default uses `tonistiigi/binfmt:latest` image, # which is out of date. This causes seg faults during build. @@ -92,7 +92,7 @@ jobs: # Free-threading builds: ls wheelhouse/*cp314t*.whl - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: wheel-${{ matrix.buildplat[1] }} path: ./wheelhouse/*.whl @@ -125,7 +125,7 @@ jobs: cd .. python -c "from pymongo import has_c; assert has_c()" - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: "sdist" path: ./dist/*.tar.gz @@ -136,13 +136,13 @@ jobs: name: Download Wheels steps: - name: Download all workflow run artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 - name: Flatten directory working-directory: . run: | find . -mindepth 2 -type f -exec mv {} . \; find . -type d -empty -delete - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: all-dist-${{ github.run_id }} path: "./*" diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index e3dd1edb1c..4387303224 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -75,7 +75,7 @@ jobs: id-token: write steps: - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: all-dist-${{ github.run_id }} path: dist/ diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 69a07c8be2..ce7308cf1b 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -67,7 +67,7 @@ jobs: run: rm -rf .venv .venv-sbom sbom-requirements.txt - name: Upload SBOM artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: sbom path: sbom.json diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index e8fb9b9185..7c7793ed25 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -245,7 +245,7 @@ jobs: run: | pip install build python -m build --sdist - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: "sdist" path: dist/*.tar.gz @@ -257,7 +257,7 @@ jobs: timeout-minutes: 20 steps: - name: Download sdist - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: sdist/ - name: Unpack SDist diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index c64a9e32ed..6a642977be 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 + uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 From ce416a094450c3702670a6793781ae9fa9eeeae5 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:41:46 -0700 Subject: [PATCH 100/129] [Spec Resync] 03-30-2026 (#2741) Co-authored-by: Cloud User Co-authored-by: Iris Ho --- .evergreen/spec-patch/PYTHON-5668.patch | 1578 +++++++++++++++++++++++ .evergreen/spec-patch/PYTHON-5759.patch | 460 +++++++ 2 files changed, 2038 insertions(+) create mode 100644 .evergreen/spec-patch/PYTHON-5668.patch create mode 100644 .evergreen/spec-patch/PYTHON-5759.patch diff --git a/.evergreen/spec-patch/PYTHON-5668.patch b/.evergreen/spec-patch/PYTHON-5668.patch new file mode 100644 index 0000000000..d48b18fe65 --- /dev/null +++ b/.evergreen/spec-patch/PYTHON-5668.patch @@ -0,0 +1,1578 @@ +diff --git a/test/transactions/unified/backpressure-retryable-abort.json b/test/transactions/unified/backpressure-retryable-abort.json +new file mode 100644 +index 00000000..53fc9c6f +--- /dev/null ++++ b/test/transactions/unified/backpressure-retryable-abort.json +@@ -0,0 +1,357 @@ ++{ ++ "description": "backpressure-retryable-abort", ++ "schemaVersion": "1.3", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "topologies": [ ++ "replicaset", ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "test" ++ } ++ }, ++ { ++ "session": { ++ "id": "session0", ++ "client": "client0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "abortTransaction retries if backpressure labels are added", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 2 ++ }, ++ "data": { ++ "failCommands": [ ++ "abortTransaction" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "abortTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": true, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "abortTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "abortTransaction", ++ "databaseName": "admin" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "abortTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "abortTransaction", ++ "databaseName": "admin" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "abortTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "abortTransaction", ++ "databaseName": "admin" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ] ++ }, ++ { ++ "description": "abortTransaction is retried maxAttempts=5 times if backpressure labels are added", ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": "alwaysOn", ++ "data": { ++ "failCommands": [ ++ "abortTransaction" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "abortTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": true, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "abortTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "abortTransaction", ++ "databaseName": "admin" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/transactions/unified/backpressure-retryable-commit.json b/test/transactions/unified/backpressure-retryable-commit.json +new file mode 100644 +index 00000000..ae873561 +--- /dev/null ++++ b/test/transactions/unified/backpressure-retryable-commit.json +@@ -0,0 +1,374 @@ ++{ ++ "description": "backpressure-retryable-commit", ++ "schemaVersion": "1.4", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "topologies": [ ++ "sharded", ++ "replicaset", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "test" ++ } ++ }, ++ { ++ "session": { ++ "id": "session0", ++ "client": "client0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "commitTransaction retries if backpressure labels are added", ++ "runOnRequirements": [ ++ { ++ "serverless": "forbid" ++ } ++ ], ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 2 ++ }, ++ "data": { ++ "failCommands": [ ++ "commitTransaction" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "commitTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": true, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "commitTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "commitTransaction", ++ "databaseName": "admin" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "commitTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "commitTransaction", ++ "databaseName": "admin" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "commitTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "commitTransaction", ++ "databaseName": "admin" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "commitTransaction is retried maxAttempts=5 times if backpressure labels are added", ++ "runOnRequirements": [ ++ { ++ "serverless": "forbid" ++ } ++ ], ++ "operations": [ ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": "alwaysOn", ++ "data": { ++ "failCommands": [ ++ "commitTransaction" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "commitTransaction", ++ "expectError": { ++ "isError": true ++ } ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": true, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "commitTransaction": 1, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "commitTransaction", ++ "databaseName": "admin" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "commitTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "commitTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "commitTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "commitTransaction" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "commitTransaction" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/transactions/unified/backpressure-retryable-reads.json b/test/transactions/unified/backpressure-retryable-reads.json +new file mode 100644 +index 00000000..73176283 +--- /dev/null ++++ b/test/transactions/unified/backpressure-retryable-reads.json +@@ -0,0 +1,328 @@ ++{ ++ "description": "backpressure-retryable-reads", ++ "schemaVersion": "1.3", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "topologies": [ ++ "replicaset", ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "test" ++ } ++ }, ++ { ++ "session": { ++ "id": "session0", ++ "client": "client0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "reads are retried if backpressure labels are added", ++ "operations": [ ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "collection0", ++ "name": "find", ++ "arguments": { ++ "filter": {}, ++ "session": "session0" ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "commitTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": true, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "test", ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "find", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "test", ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "find", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "abortTransaction": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "commitTransaction", ++ "databaseName": "admin" ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "reads are retried maxAttempts=5 times if backpressure labels are added", ++ "operations": [ ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": "alwaysOn", ++ "data": { ++ "failCommands": [ ++ "find" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "collection0", ++ "name": "find", ++ "arguments": { ++ "filter": {}, ++ "session": "session0" ++ }, ++ "expectError": { ++ "isError": true ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "abortTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/transactions/unified/backpressure-retryable-writes.json b/test/transactions/unified/backpressure-retryable-writes.json +new file mode 100644 +index 00000000..eea0e6b5 +--- /dev/null ++++ b/test/transactions/unified/backpressure-retryable-writes.json +@@ -0,0 +1,454 @@ ++{ ++ "description": "backpressure-retryable-writes", ++ "schemaVersion": "1.3", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.4", ++ "topologies": [ ++ "replicaset", ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "useMultipleMongoses": false, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "test" ++ } ++ }, ++ { ++ "session": { ++ "id": "session0", ++ "client": "client0" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "writes are retried if backpressure labels are added", ++ "operations": [ ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 2 ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "commitTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 1 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": true, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 2 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "test", ++ "documents": [ ++ { ++ "_id": 2 ++ } ++ ], ++ "ordered": true, ++ "readConcern": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "abortTransaction": { ++ "$$exists": false ++ }, ++ "lsid": { ++ "$$sessionLsid": "session0" ++ }, ++ "txnNumber": { ++ "$numberLong": "1" ++ }, ++ "startTransaction": { ++ "$$exists": false ++ }, ++ "autocommit": false, ++ "writeConcern": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "commitTransaction", ++ "databaseName": "admin" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [ ++ { ++ "_id": 1 ++ }, ++ { ++ "_id": 2 ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "writes are retried maxAttempts=5 times if backpressure labels are added", ++ "operations": [ ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": { ++ "$$unsetOrMatches": { ++ "insertedId": { ++ "$$unsetOrMatches": 1 ++ } ++ } ++ } ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": "alwaysOn", ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 2 ++ } ++ }, ++ "expectError": { ++ "isError": true ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "abortTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "abortTransaction" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ] ++ }, ++ { ++ "description": "retry succeeds if backpressure labels are added to the first operation in a transaction", ++ "operations": [ ++ { ++ "object": "session0", ++ "name": "startTransaction" ++ }, ++ { ++ "object": "testRunner", ++ "name": "failPoint", ++ "arguments": { ++ "client": "client0", ++ "failPoint": { ++ "configureFailPoint": "failCommand", ++ "mode": { ++ "times": 1 ++ }, ++ "data": { ++ "failCommands": [ ++ "insert" ++ ], ++ "errorLabels": [ ++ "RetryableError", ++ "SystemOverloadedError" ++ ], ++ "errorCode": 112 ++ } ++ } ++ } ++ }, ++ { ++ "object": "collection0", ++ "name": "insertOne", ++ "arguments": { ++ "session": "session0", ++ "document": { ++ "_id": 2 ++ } ++ } ++ }, ++ { ++ "object": "session0", ++ "name": "abortTransaction" ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "startTransaction": true ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "startTransaction": true ++ }, ++ "commandName": "insert", ++ "databaseName": "transaction-tests" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "startTransaction": { ++ "$$exists": false ++ } ++ }, ++ "commandName": "abortTransaction", ++ "databaseName": "admin" ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "test", ++ "databaseName": "transaction-tests", ++ "documents": [] ++ } ++ ] ++ } ++ ] ++} +diff --git b/test/uri_options/client-backpressure-options.json a/test/uri_options/client-backpressure-options.json +new file mode 100644 +index 00000000..3fcf2c86 +--- /dev/null ++++ a/test/uri_options/client-backpressure-options.json +@@ -0,0 +1,35 @@ ++{ ++ "tests": [ ++ { ++ "description": "adaptiveRetries=true is parsed correctly", ++ "uri": "mongodb://example.com/?adaptiveRetries=true", ++ "valid": true, ++ "warning": false, ++ "hosts": null, ++ "auth": null, ++ "options": { ++ "adaptiveRetries": true ++ } ++ }, ++ { ++ "description": "adaptiveRetries=false is parsed correctly", ++ "uri": "mongodb://example.com/?adaptiveRetries=false", ++ "valid": true, ++ "warning": false, ++ "hosts": null, ++ "auth": null, ++ "options": { ++ "adaptiveRetries": false ++ } ++ }, ++ { ++ "description": "adaptiveRetries with invalid value causes a warning", ++ "uri": "mongodb://example.com/?adaptiveRetries=invalid", ++ "valid": true, ++ "warning": true, ++ "hosts": null, ++ "auth": null, ++ "options": null ++ } ++ ] ++} diff --git a/.evergreen/spec-patch/PYTHON-5759.patch b/.evergreen/spec-patch/PYTHON-5759.patch new file mode 100644 index 0000000000..3b19ed065e --- /dev/null +++ b/.evergreen/spec-patch/PYTHON-5759.patch @@ -0,0 +1,460 @@ +diff --git a/test/client-side-encryption/spec/unified/accessToken-azure.json b/test/client-side-encryption/spec/unified/accessToken-azure.json +new file mode 100644 +index 00000000..510d8795 +--- /dev/null ++++ b/test/client-side-encryption/spec/unified/accessToken-azure.json +@@ -0,0 +1,186 @@ ++{ ++ "description": "accessToken-azure", ++ "schemaVersion": "1.28", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.1.10", ++ "csfle": { ++ "minLibmongocryptVersion": "1.6.0" ++ } ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client", ++ "autoEncryptOpts": { ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "azure": { ++ "accessToken": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ } ++ } ++ }, ++ { ++ "database": { ++ "id": "db", ++ "client": "client", ++ "databaseName": "db" ++ } ++ }, ++ { ++ "collection": { ++ "id": "coll", ++ "database": "db", ++ "collectionName": "coll" ++ } ++ }, ++ { ++ "clientEncryption": { ++ "id": "clientEncryption", ++ "clientEncryptionOpts": { ++ "keyVaultClient": "client", ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "azure": { ++ "accessToken": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ } ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "db", ++ "collectionName": "coll", ++ "documents": [], ++ "createOptions": { ++ "validator": { ++ "$jsonSchema": { ++ "properties": { ++ "secret": { ++ "encrypt": { ++ "keyId": [ ++ { ++ "$binary": { ++ "base64": "AZURE+AAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ } ++ ], ++ "bsonType": "string", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" ++ } ++ } ++ }, ++ "bsonType": "object" ++ } ++ } ++ } ++ }, ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "_id": { ++ "$binary": { ++ "base64": "AZURE+AAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ }, ++ "keyAltNames": [ ++ "my-key" ++ ], ++ "keyMaterial": { ++ "$binary": { ++ "base64": "n+HWZ0ZSVOYA3cvQgP7inN4JSXfOH85IngmeQxRpQHjCCcqT3IFqEWNlrsVHiz3AELimHhX4HKqOLWMUeSIT6emUDDoQX9BAv8DR1+E1w4nGs/NyEneac78EYFkK3JysrFDOgl2ypCCTKAypkn9CkAx1if4cfgQE93LW4kczcyHdGiH36CIxrCDGv1UzAvERN5Qa47DVwsM6a+hWsF2AAAJVnF0wYLLJU07TuRHdMrrphPWXZsFgyV+lRqJ7DDpReKNO8nMPLV/mHqHBHGPGQiRdb9NoJo8CvokGz4+KE8oLwzKf6V24dtwZmRkrsDV4iOhvROAzz+Euo1ypSkL3mw==", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "status": { ++ "$numberInt": "0" ++ }, ++ "masterKey": { ++ "provider": "azure", ++ "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", ++ "keyName": "key-name-csfle" ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "Auto encrypt using access token Azure credentials", ++ "operations": [ ++ { ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "secret": "string0" ++ } ++ }, ++ "object": "coll" ++ } ++ ], ++ "outcome": [ ++ { ++ "documents": [ ++ { ++ "_id": 1, ++ "secret": { ++ "$binary": { ++ "base64": "AQGVERPgAAAAAAAAAAAAAAAC5DbBSwPwfSlBrDtRuglvNvCXD1KzDuCKY2P+4bRFtHDjpTOE2XuytPAUaAbXf1orsPq59PVZmsbTZbt2CB8qaQ==", ++ "subType": "06" ++ } ++ } ++ } ++ ], ++ "collectionName": "coll", ++ "databaseName": "db" ++ } ++ ] ++ }, ++ { ++ "description": "Explicit encrypt using access token Azure credentials", ++ "operations": [ ++ { ++ "name": "encrypt", ++ "object": "clientEncryption", ++ "arguments": { ++ "value": "string0", ++ "opts": { ++ "keyAltName": "my-key", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" ++ } ++ }, ++ "expectResult": { ++ "$binary": { ++ "base64": "AQGVERPgAAAAAAAAAAAAAAAC5DbBSwPwfSlBrDtRuglvNvCXD1KzDuCKY2P+4bRFtHDjpTOE2XuytPAUaAbXf1orsPq59PVZmsbTZbt2CB8qaQ==", ++ "subType": "06" ++ } ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/client-side-encryption/spec/unified/accessToken-gcp.json b/test/client-side-encryption/spec/unified/accessToken-gcp.json +new file mode 100644 +index 00000000..f5cf8914 +--- /dev/null ++++ b/test/client-side-encryption/spec/unified/accessToken-gcp.json +@@ -0,0 +1,188 @@ ++{ ++ "description": "accessToken-gcp", ++ "schemaVersion": "1.28", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.1.10", ++ "csfle": { ++ "minLibmongocryptVersion": "1.6.0" ++ } ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client", ++ "autoEncryptOpts": { ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "gcp": { ++ "accessToken": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ } ++ } ++ }, ++ { ++ "database": { ++ "id": "db", ++ "client": "client", ++ "databaseName": "db" ++ } ++ }, ++ { ++ "collection": { ++ "id": "coll", ++ "database": "db", ++ "collectionName": "coll" ++ } ++ }, ++ { ++ "clientEncryption": { ++ "id": "clientEncryption", ++ "clientEncryptionOpts": { ++ "keyVaultClient": "client", ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "gcp": { ++ "accessToken": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ } ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "db", ++ "collectionName": "coll", ++ "documents": [], ++ "createOptions": { ++ "validator": { ++ "$jsonSchema": { ++ "properties": { ++ "secret": { ++ "encrypt": { ++ "keyId": [ ++ { ++ "$binary": { ++ "base64": "GCP+AAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ } ++ ], ++ "bsonType": "string", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" ++ } ++ } ++ }, ++ "bsonType": "object" ++ } ++ } ++ } ++ }, ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "_id": { ++ "$binary": { ++ "base64": "GCP+AAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ }, ++ "keyAltNames": [ ++ "my-key" ++ ], ++ "keyMaterial": { ++ "$binary": { ++ "base64": "CiQAIgLj0WyktnB4dfYHo5SLZ41K4ASQrjJUaSzl5vvVH0G12G0SiQEAjlV8XPlbnHDEDFbdTO4QIe8ER2/172U1ouLazG0ysDtFFIlSvWX5ZnZUrRMmp/R2aJkzLXEt/zf8Mn4Lfm+itnjgo5R9K4pmPNvvPKNZX5C16lrPT+aA+rd+zXFSmlMg3i5jnxvTdLHhg3G7Q/Uv1ZIJskKt95bzLoe0tUVzRWMYXLIEcohnQg==", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "status": { ++ "$numberInt": "0" ++ }, ++ "masterKey": { ++ "provider": "gcp", ++ "projectId": "devprod-drivers", ++ "location": "global", ++ "keyRing": "key-ring-csfle", ++ "keyName": "key-name-csfle" ++ } ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "Auto encrypt using access token GCP credentials", ++ "operations": [ ++ { ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "secret": "string0" ++ } ++ }, ++ "object": "coll" ++ } ++ ], ++ "outcome": [ ++ { ++ "documents": [ ++ { ++ "_id": 1, ++ "secret": { ++ "$binary": { ++ "base64": "ARgj/gAAAAAAAAAAAAAAAAACwFd+Y5Ojw45GUXNvbcIpN9YkRdoHDHkR4kssdn0tIMKlDQOLFkWFY9X07IRlXsxPD8DcTiKnl6XINK28vhcGlg==", ++ "subType": "06" ++ } ++ } ++ } ++ ], ++ "collectionName": "coll", ++ "databaseName": "db" ++ } ++ ] ++ }, ++ { ++ "description": "Explicit encrypt using access token GCP credentials", ++ "operations": [ ++ { ++ "name": "encrypt", ++ "object": "clientEncryption", ++ "arguments": { ++ "value": "string0", ++ "opts": { ++ "keyAltName": "my-key", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" ++ } ++ }, ++ "expectResult": { ++ "$binary": { ++ "base64": "ARgj/gAAAAAAAAAAAAAAAAACwFd+Y5Ojw45GUXNvbcIpN9YkRdoHDHkR4kssdn0tIMKlDQOLFkWFY9X07IRlXsxPD8DcTiKnl6XINK28vhcGlg==", ++ "subType": "06" ++ } ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-azure-accessToken-type.json b/test/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-azure-accessToken-type.json +new file mode 100644 +index 00000000..8fe5c150 +--- /dev/null ++++ b/test/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-azure-accessToken-type.json +@@ -0,0 +1,31 @@ ++{ ++ "description": "clientEncryptionOpts-kmsProviders-azure-accessToken-type", ++ "schemaVersion": "1.28", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0" ++ } ++ }, ++ { ++ "clientEncryption": { ++ "id": "clientEncryption0", ++ "clientEncryptionOpts": { ++ "keyVaultClient": "client0", ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "azure": { ++ "accessToken": 0 ++ } ++ } ++ } ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "", ++ "operations": [] ++ } ++ ] ++} +diff --git a/test/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-gcp-accessToken-type.json b/test/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-gcp-accessToken-type.json +new file mode 100644 +index 00000000..2284e26c +--- /dev/null ++++ b/test/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-gcp-accessToken-type.json +@@ -0,0 +1,31 @@ ++{ ++ "description": "clientEncryptionOpts-kmsProviders-gcp-accessToken-type", ++ "schemaVersion": "1.28", ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0" ++ } ++ }, ++ { ++ "clientEncryption": { ++ "id": "clientEncryption0", ++ "clientEncryptionOpts": { ++ "keyVaultClient": "client0", ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "gcp": { ++ "accessToken": 0 ++ } ++ } ++ } ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "", ++ "operations": [] ++ } ++ ] ++} From ee851ba974fcc5f9e1c39a5f6f4f9be89399aba8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:50:25 -0700 Subject: [PATCH 101/129] Bump astral-sh/setup-uv from 7.3.0 to 7.6.0 in the actions group (#2740) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test-python.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 7c7793ed25..947e8e3193 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -90,7 +90,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "3.10" @@ -118,7 +118,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "3.10" @@ -143,7 +143,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "3.10" @@ -162,7 +162,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "3.10" @@ -184,7 +184,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -205,7 +205,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: enable-cache: true python-version: "3.10" @@ -295,7 +295,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 with: python-version: "3.9" - id: setup-mongodb From db4db928d373261d8b087e78d6eca70d046e5461 Mon Sep 17 00:00:00 2001 From: Jib Date: Wed, 1 Apr 2026 11:51:53 -0400 Subject: [PATCH 102/129] PYTHON-5401: Add AI Generated Contributions Policy (#2696) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/pull_request_template.md | 4 +-- CONTRIBUTING.md | 62 +++++++++++++++++--------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2e1a132c95..b1f0987b3c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,8 +6,8 @@ If you are an external contributor and there is no JIRA ticket associated with y for the PR title. A MongoDB employee will create a JIRA ticket and edit the name and links as appropriate. Note on AI Contributions: -We do not accept pull requests that are primarily or substantially generated by AI tools (ChatGPT, Copilot, etc.). -All contributions must be written and understood by human contributors. +We only accept pull requests that are authored and submitted by human contributors who fully understand the changes they are proposing. +All contributions must be written and understood by human contributors. Please read about our policy in our contributing guide. --> [JIRA TICKET] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86c5e64559..77888eb087 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,49 +85,53 @@ likelihood for getting review sooner shoots up. - `versionadded:: 3.11` - `versionchanged:: 3.5` -**Pull Request Template Breakdown** +### AI-Generated Contributions Policy -- **Github PR Title** +#### Our Stance - - The PR Title format should always be - `[JIRA-ID] : Jira Title or Blurb Summary`. +We only accept pull requests that are authored and submitted by human contributors who fully understand the changes they are proposing. Pull requests that are not clearly owned and understood by a human contributor may be closed. **All contributions must be submitted, reviewed, and understood by human contributors.** -- **JIRA LINK** +##### Why This Policy Exists -- Convenient link to the associated JIRA ticket. +At MongoDB, we understand the power and prevalence of AI tools in software development. With that being said, many MongoDB libraries are foundational tools used in production systems worldwide. The nature of these libraries requires: -- **Summary** +- **Deep domain expertise**: MongoDB's wire protocol, BSON specification, connection pooling, authentication mechanisms, and concurrency patterns require an understanding that AI alone cannot substantiate. - - Small blurb on why this is needed. The JIRA task should have - the more in-depth description, but this should still, at a - high level, give anyone looking an understanding of why the - PR has been checked in. +- **Long-term maintainability**: Contributors need to be able to explain *why* code is written a certain way, explain design decisions, and be available to iterate on their contributions. -- **Changes in this PR** +- **Security responsibility**: Authentication, credential handling, and TLS implementation cannot be left to probabilistic code generation. - - The explicit code changes that this PR is introducing. This - should be more specific than just the task name. (Unless the - task name is very clear). +##### What This Means for Contributors -- **Test Plan** +**Required:** - - Everything needs a test description. Describe what you did - to validate your changes actually worked; if you did - nothing, then document you did not test it. Aim to make - these steps reproducible by other engineers, specifically - with your primary reviewer in mind. +- Full understanding of every line of code you submit +- Ability to explain and defend your implementation choices +- Willingness to iterate and maintain your contributions -- **Screenshots** +**Encouraged:** - - Any images that provide more context to the PR. Usually, - these just coincide with the test plan. +- Using AI assistants as learning tools to understand concepts +- IDE autocomplete features that suggest standard patterns +- AI help for brainstorming approaches (but write the code yourself) +- Writing code using AI tools, reviewing each line and revising code as necessary. -- **Callouts or follow-up items** +**Not allowed:** - - This is a good place for identifying "to-dos" that you've - placed in the code (Must have an accompanying JIRA Ticket). - - Potential bugs that you are unsure how to test in the code. - - Opinions you want to receive about your code. +- Submitting PRs generated solely by AI tools +- Copy-pasting AI-generated code without full understanding + +##### Disclosure + +If you used AI assistance in any way during your contribution, please disclose what the AI assistant was used for in your PR description. We would love to know what tools developers have found useful in iterating in their day to day. + +##### Questions? + +If you're unsure whether your contribution complies with this policy, please ask for guidance within the scope of the PR and clarify any uncertainty. We're happy to guide contributors toward successful contributions. + +--- + +*This policy helps us maintain the reliability, security, and trustworthiness that production applications depend on. Thank you for understanding and for contributing thoughtfully to PyMongo.* ## Running Linters From 08b806fd8764e1fe07aa2582452752f3bea2e632 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 7 Apr 2026 12:20:27 -0400 Subject: [PATCH 103/129] PYTHON-5768 Add AGENTS.md w/copilot instructions (#2744) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 45 ++------------------------------- AGENTS.md | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 AGENTS.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b67cb49aca..a8943d11ac 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,44 +1,3 @@ -When reviewing code, focus on: +Please see [AGENTS.md](../AGENTS.md). -## Security Critical Issues -- Check for hardcoded secrets, API keys, or credentials. -- Check for instances of potential method call injection, dynamic code execution, symbol injection or other code injection vulnerabilities. - -## Performance Red Flags -- Spot inefficient loops and algorithmic issues. -- Check for memory leaks and resource cleanup. - -## Code Quality Essentials -- Methods should be focused and appropriately sized. If a method is doing too much, suggest refactorings to split it up. -- Use clear, descriptive naming conventions. -- Avoid encapsulation violations and ensure proper separation of concerns. -- All public classes, modules, and methods should have clear documentation in Sphinx format. - -## PyMongo-specific Concerns -- Do not review files within `pymongo/synchronous` or files in `test/` that also have a file of the same name in `test/asynchronous` unless the reviewed changes include a `_IS_SYNC` statement. PyMongo generates these files from `pymongo/asynchronous` and `test/asynchronous` using `tools/synchro.py`. -- All asynchronous functions must not call any blocking I/O. - -## Review Style -- Be specific and actionable in feedback. -- Explain the "why" behind recommendations. -- Acknowledge good patterns when you see them. -- Ask clarifying questions when code intent is unclear. - -Always prioritize security vulnerabilities and performance issues that could impact users. - -Always suggest changes to improve readability and testability. For example, this suggestion seeks to make the code more readable, reusable, and testable: - -```python -# Instead of: -if user.email and "@" in user.email and len(user.email) > 5: - submit_button.enabled = True -else: - submit_button.enabled = False - -# Consider: -def valid_email(email): - return email and "@" in email and len(email) > 5 - - -submit_button.enabled = valid_email(user.email) -``` +Follow the repository instructions defined in `AGENTS.md` when working in this codebase. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..b67cb49aca --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +When reviewing code, focus on: + +## Security Critical Issues +- Check for hardcoded secrets, API keys, or credentials. +- Check for instances of potential method call injection, dynamic code execution, symbol injection or other code injection vulnerabilities. + +## Performance Red Flags +- Spot inefficient loops and algorithmic issues. +- Check for memory leaks and resource cleanup. + +## Code Quality Essentials +- Methods should be focused and appropriately sized. If a method is doing too much, suggest refactorings to split it up. +- Use clear, descriptive naming conventions. +- Avoid encapsulation violations and ensure proper separation of concerns. +- All public classes, modules, and methods should have clear documentation in Sphinx format. + +## PyMongo-specific Concerns +- Do not review files within `pymongo/synchronous` or files in `test/` that also have a file of the same name in `test/asynchronous` unless the reviewed changes include a `_IS_SYNC` statement. PyMongo generates these files from `pymongo/asynchronous` and `test/asynchronous` using `tools/synchro.py`. +- All asynchronous functions must not call any blocking I/O. + +## Review Style +- Be specific and actionable in feedback. +- Explain the "why" behind recommendations. +- Acknowledge good patterns when you see them. +- Ask clarifying questions when code intent is unclear. + +Always prioritize security vulnerabilities and performance issues that could impact users. + +Always suggest changes to improve readability and testability. For example, this suggestion seeks to make the code more readable, reusable, and testable: + +```python +# Instead of: +if user.email and "@" in user.email and len(user.email) > 5: + submit_button.enabled = True +else: + submit_button.enabled = False + +# Consider: +def valid_email(email): + return email and "@" in email and len(email) > 5 + + +submit_button.enabled = valid_email(user.email) +``` From ee20ef52ec1f4a5949563c99af5547760a893476 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 13 Apr 2026 14:01:14 -0400 Subject: [PATCH 104/129] PYTHON-5791 - test_list_database_names should not check ordering (#2751) --- test/asynchronous/test_client.py | 2 +- test/test_client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index 5511765bae..98596b33c0 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -1034,7 +1034,7 @@ async def test_list_database_names(self): db_names = await self.client.list_database_names() self.assertIn("pymongo_test", db_names) self.assertIn("pymongo_test_mike", db_names) - self.assertEqual(db_names, cmd_names) + self.assertCountEqual(db_names, cmd_names) async def test_drop_database(self): with self.assertRaises(TypeError): diff --git a/test/test_client.py b/test/test_client.py index 737b3afe60..2bb74e8dc0 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -1007,7 +1007,7 @@ def test_list_database_names(self): db_names = self.client.list_database_names() self.assertIn("pymongo_test", db_names) self.assertIn("pymongo_test_mike", db_names) - self.assertEqual(db_names, cmd_names) + self.assertCountEqual(db_names, cmd_names) def test_drop_database(self): with self.assertRaises(TypeError): From e1751ff253fe4ef888275f81e7c5ec3932fa752f Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 14 Apr 2026 12:25:29 -0400 Subject: [PATCH 105/129] PYTHON-5668 - Merge backpressure branch into mainline (#2729) Co-authored-by: Steven Silvester Co-authored-by: Shane Harvey Co-authored-by: Steven Silvester Co-authored-by: Iris <58442094+sleepyStick@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kevin Albertson Co-authored-by: Casey Clements Co-authored-by: Sergey Zelenov Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .evergreen/resync-specs.sh | 3 + .evergreen/spec-patch/PYTHON-5668.patch | 1578 ------ doc/changelog.rst | 3 + pymongo/asynchronous/client_bulk.py | 11 +- pymongo/asynchronous/client_session.py | 71 +- pymongo/asynchronous/collection.py | 84 +- pymongo/asynchronous/database.py | 49 +- pymongo/asynchronous/helpers.py | 46 + pymongo/asynchronous/mongo_client.py | 157 +- pymongo/asynchronous/pool.py | 39 +- pymongo/asynchronous/topology.py | 4 +- pymongo/client_options.py | 26 + pymongo/common.py | 10 + pymongo/synchronous/client_bulk.py | 11 +- pymongo/synchronous/client_session.py | 70 +- pymongo/synchronous/collection.py | 80 +- pymongo/synchronous/database.py | 45 +- pymongo/synchronous/helpers.py | 46 + pymongo/synchronous/mongo_client.py | 157 +- pymongo/synchronous/pool.py | 39 +- pymongo/synchronous/topology.py | 4 +- test/asynchronous/test_client.py | 32 + test/asynchronous/test_client_backpressure.py | 312 ++ test/asynchronous/test_client_metadata.py | 13 + .../test_discovery_and_monitoring.py | 65 +- test/asynchronous/test_pooling.py | 33 + test/asynchronous/test_retryable_reads.py | 50 +- test/asynchronous/test_retryable_writes.py | 186 +- test/asynchronous/test_transactions.py | 117 +- .../backpressure-connection-checkin.json | 111 + .../backpressure-retry-loop.json | 4553 +++++++++++++++++ .../backpressure-retry-max-attempts.json | 2569 ++++++++++ test/client-backpressure/getMore-retried.json | 253 + .../pool-create-min-size-error.json | 4 +- .../errors/error_handling_handshake.json | 16 +- ...ressure-network-error-fail-replicaset.json | 142 + ...ackpressure-network-error-fail-single.json | 142 + ...ssure-network-timeout-fail-replicaset.json | 145 + ...kpressure-network-timeout-fail-single.json | 145 + ...ged-on-min-pool-size-population-error.json | 106 + test/test_client.py | 32 + test/test_client_backpressure.py | 310 ++ test/test_client_metadata.py | 13 + test/test_discovery_and_monitoring.py | 63 +- test/test_pooling.py | 33 + test/test_retryable_reads.py | 50 +- test/test_retryable_writes.py | 186 +- test/test_transactions.py | 117 +- .../unified/backpressure-retryable-abort.json | 342 ++ .../backpressure-retryable-commit.json | 359 ++ .../unified/backpressure-retryable-reads.json | 313 ++ .../backpressure-retryable-writes.json | 439 ++ .../client-backpressure-options.json | 66 + tools/synchro.py | 3 +- 54 files changed, 12045 insertions(+), 1808 deletions(-) delete mode 100644 .evergreen/spec-patch/PYTHON-5668.patch create mode 100644 test/asynchronous/test_client_backpressure.py create mode 100644 test/client-backpressure/backpressure-connection-checkin.json create mode 100644 test/client-backpressure/backpressure-retry-loop.json create mode 100644 test/client-backpressure/backpressure-retry-max-attempts.json create mode 100644 test/client-backpressure/getMore-retried.json create mode 100644 test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json create mode 100644 test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json create mode 100644 test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json create mode 100644 test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json create mode 100644 test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json create mode 100644 test/test_client_backpressure.py create mode 100644 test/transactions/unified/backpressure-retryable-abort.json create mode 100644 test/transactions/unified/backpressure-retryable-commit.json create mode 100644 test/transactions/unified/backpressure-retryable-reads.json create mode 100644 test/transactions/unified/backpressure-retryable-writes.json create mode 100644 test/uri_options/client-backpressure-options.json diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index d2bd89c781..4bb9c86304 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -94,6 +94,9 @@ do change-streams|change_streams) cpjson change-streams/tests/ change_streams/ ;; + client-backpressure|client_backpressure) + cpjson client-backpressure/tests client-backpressure + ;; client-side-encryption|csfle|fle) cpjson client-side-encryption/tests/ client-side-encryption/spec cpjson client-side-encryption/corpus/ client-side-encryption/corpus diff --git a/.evergreen/spec-patch/PYTHON-5668.patch b/.evergreen/spec-patch/PYTHON-5668.patch deleted file mode 100644 index d48b18fe65..0000000000 --- a/.evergreen/spec-patch/PYTHON-5668.patch +++ /dev/null @@ -1,1578 +0,0 @@ -diff --git a/test/transactions/unified/backpressure-retryable-abort.json b/test/transactions/unified/backpressure-retryable-abort.json -new file mode 100644 -index 00000000..53fc9c6f ---- /dev/null -+++ b/test/transactions/unified/backpressure-retryable-abort.json -@@ -0,0 +1,357 @@ -+{ -+ "description": "backpressure-retryable-abort", -+ "schemaVersion": "1.3", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "topologies": [ -+ "replicaset", -+ "sharded", -+ "load-balanced" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "client0", -+ "useMultipleMongoses": false, -+ "observeEvents": [ -+ "commandStartedEvent" -+ ] -+ } -+ }, -+ { -+ "database": { -+ "id": "database0", -+ "client": "client0", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "collection": { -+ "id": "collection0", -+ "database": "database0", -+ "collectionName": "test" -+ } -+ }, -+ { -+ "session": { -+ "id": "session0", -+ "client": "client0" -+ } -+ } -+ ], -+ "initialData": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ], -+ "tests": [ -+ { -+ "description": "abortTransaction retries if backpressure labels are added", -+ "operations": [ -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": { -+ "times": 2 -+ }, -+ "data": { -+ "failCommands": [ -+ "abortTransaction" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "abortTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": true, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "abortTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "abortTransaction", -+ "databaseName": "admin" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "abortTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "abortTransaction", -+ "databaseName": "admin" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "abortTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "abortTransaction", -+ "databaseName": "admin" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ] -+ }, -+ { -+ "description": "abortTransaction is retried maxAttempts=5 times if backpressure labels are added", -+ "operations": [ -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": "alwaysOn", -+ "data": { -+ "failCommands": [ -+ "abortTransaction" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "abortTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": true, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "abortTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "abortTransaction", -+ "databaseName": "admin" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ] -+ } -+ ] -+} -diff --git a/test/transactions/unified/backpressure-retryable-commit.json b/test/transactions/unified/backpressure-retryable-commit.json -new file mode 100644 -index 00000000..ae873561 ---- /dev/null -+++ b/test/transactions/unified/backpressure-retryable-commit.json -@@ -0,0 +1,374 @@ -+{ -+ "description": "backpressure-retryable-commit", -+ "schemaVersion": "1.4", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "topologies": [ -+ "sharded", -+ "replicaset", -+ "load-balanced" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "client0", -+ "useMultipleMongoses": false, -+ "observeEvents": [ -+ "commandStartedEvent" -+ ] -+ } -+ }, -+ { -+ "database": { -+ "id": "database0", -+ "client": "client0", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "collection": { -+ "id": "collection0", -+ "database": "database0", -+ "collectionName": "test" -+ } -+ }, -+ { -+ "session": { -+ "id": "session0", -+ "client": "client0" -+ } -+ } -+ ], -+ "initialData": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ], -+ "tests": [ -+ { -+ "description": "commitTransaction retries if backpressure labels are added", -+ "runOnRequirements": [ -+ { -+ "serverless": "forbid" -+ } -+ ], -+ "operations": [ -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": { -+ "times": 2 -+ }, -+ "data": { -+ "failCommands": [ -+ "commitTransaction" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "commitTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": true, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "commitTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "commitTransaction", -+ "databaseName": "admin" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "commitTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "commitTransaction", -+ "databaseName": "admin" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "commitTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "commitTransaction", -+ "databaseName": "admin" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ] -+ } -+ ] -+ }, -+ { -+ "description": "commitTransaction is retried maxAttempts=5 times if backpressure labels are added", -+ "runOnRequirements": [ -+ { -+ "serverless": "forbid" -+ } -+ ], -+ "operations": [ -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": "alwaysOn", -+ "data": { -+ "failCommands": [ -+ "commitTransaction" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "commitTransaction", -+ "expectError": { -+ "isError": true -+ } -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": true, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "commitTransaction": 1, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "commitTransaction", -+ "databaseName": "admin" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "commitTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "commitTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "commitTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "commitTransaction" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "commitTransaction" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ] -+ } -+ ] -+} -diff --git a/test/transactions/unified/backpressure-retryable-reads.json b/test/transactions/unified/backpressure-retryable-reads.json -new file mode 100644 -index 00000000..73176283 ---- /dev/null -+++ b/test/transactions/unified/backpressure-retryable-reads.json -@@ -0,0 +1,328 @@ -+{ -+ "description": "backpressure-retryable-reads", -+ "schemaVersion": "1.3", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "topologies": [ -+ "replicaset", -+ "sharded", -+ "load-balanced" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "client0", -+ "useMultipleMongoses": false, -+ "observeEvents": [ -+ "commandStartedEvent" -+ ] -+ } -+ }, -+ { -+ "database": { -+ "id": "database0", -+ "client": "client0", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "collection": { -+ "id": "collection0", -+ "database": "database0", -+ "collectionName": "test" -+ } -+ }, -+ { -+ "session": { -+ "id": "session0", -+ "client": "client0" -+ } -+ } -+ ], -+ "initialData": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ], -+ "tests": [ -+ { -+ "description": "reads are retried if backpressure labels are added", -+ "operations": [ -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": { -+ "times": 1 -+ }, -+ "data": { -+ "failCommands": [ -+ "find" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "collection0", -+ "name": "find", -+ "arguments": { -+ "filter": {}, -+ "session": "session0" -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "commitTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": true, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "find": "test", -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "find", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "find": "test", -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "find", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "abortTransaction": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "commitTransaction", -+ "databaseName": "admin" -+ } -+ } -+ ] -+ } -+ ] -+ }, -+ { -+ "description": "reads are retried maxAttempts=5 times if backpressure labels are added", -+ "operations": [ -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": "alwaysOn", -+ "data": { -+ "failCommands": [ -+ "find" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "collection0", -+ "name": "find", -+ "arguments": { -+ "filter": {}, -+ "session": "session0" -+ }, -+ "expectError": { -+ "isError": true -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "abortTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "find" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "find" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "find" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "find" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "find" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "find" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ } -+ ] -+ } -+ ] -+ } -+ ] -+} -diff --git a/test/transactions/unified/backpressure-retryable-writes.json b/test/transactions/unified/backpressure-retryable-writes.json -new file mode 100644 -index 00000000..eea0e6b5 ---- /dev/null -+++ b/test/transactions/unified/backpressure-retryable-writes.json -@@ -0,0 +1,454 @@ -+{ -+ "description": "backpressure-retryable-writes", -+ "schemaVersion": "1.3", -+ "runOnRequirements": [ -+ { -+ "minServerVersion": "4.4", -+ "topologies": [ -+ "replicaset", -+ "sharded", -+ "load-balanced" -+ ] -+ } -+ ], -+ "createEntities": [ -+ { -+ "client": { -+ "id": "client0", -+ "useMultipleMongoses": false, -+ "observeEvents": [ -+ "commandStartedEvent" -+ ] -+ } -+ }, -+ { -+ "database": { -+ "id": "database0", -+ "client": "client0", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "collection": { -+ "id": "collection0", -+ "database": "database0", -+ "collectionName": "test" -+ } -+ }, -+ { -+ "session": { -+ "id": "session0", -+ "client": "client0" -+ } -+ } -+ ], -+ "initialData": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ], -+ "tests": [ -+ { -+ "description": "writes are retried if backpressure labels are added", -+ "operations": [ -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": { -+ "times": 1 -+ }, -+ "data": { -+ "failCommands": [ -+ "insert" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 2 -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "commitTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 1 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": true, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 2 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "insert": "test", -+ "documents": [ -+ { -+ "_id": 2 -+ } -+ ], -+ "ordered": true, -+ "readConcern": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "abortTransaction": { -+ "$$exists": false -+ }, -+ "lsid": { -+ "$$sessionLsid": "session0" -+ }, -+ "txnNumber": { -+ "$numberLong": "1" -+ }, -+ "startTransaction": { -+ "$$exists": false -+ }, -+ "autocommit": false, -+ "writeConcern": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "commitTransaction", -+ "databaseName": "admin" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [ -+ { -+ "_id": 1 -+ }, -+ { -+ "_id": 2 -+ } -+ ] -+ } -+ ] -+ }, -+ { -+ "description": "writes are retried maxAttempts=5 times if backpressure labels are added", -+ "operations": [ -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 1 -+ } -+ }, -+ "expectResult": { -+ "$$unsetOrMatches": { -+ "insertedId": { -+ "$$unsetOrMatches": 1 -+ } -+ } -+ } -+ }, -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": "alwaysOn", -+ "data": { -+ "failCommands": [ -+ "insert" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 2 -+ } -+ }, -+ "expectError": { -+ "isError": true -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "abortTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "insert" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "commandName": "abortTransaction" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ] -+ }, -+ { -+ "description": "retry succeeds if backpressure labels are added to the first operation in a transaction", -+ "operations": [ -+ { -+ "object": "session0", -+ "name": "startTransaction" -+ }, -+ { -+ "object": "testRunner", -+ "name": "failPoint", -+ "arguments": { -+ "client": "client0", -+ "failPoint": { -+ "configureFailPoint": "failCommand", -+ "mode": { -+ "times": 1 -+ }, -+ "data": { -+ "failCommands": [ -+ "insert" -+ ], -+ "errorLabels": [ -+ "RetryableError", -+ "SystemOverloadedError" -+ ], -+ "errorCode": 112 -+ } -+ } -+ } -+ }, -+ { -+ "object": "collection0", -+ "name": "insertOne", -+ "arguments": { -+ "session": "session0", -+ "document": { -+ "_id": 2 -+ } -+ } -+ }, -+ { -+ "object": "session0", -+ "name": "abortTransaction" -+ } -+ ], -+ "expectEvents": [ -+ { -+ "client": "client0", -+ "events": [ -+ { -+ "commandStartedEvent": { -+ "command": { -+ "startTransaction": true -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "startTransaction": true -+ }, -+ "commandName": "insert", -+ "databaseName": "transaction-tests" -+ } -+ }, -+ { -+ "commandStartedEvent": { -+ "command": { -+ "startTransaction": { -+ "$$exists": false -+ } -+ }, -+ "commandName": "abortTransaction", -+ "databaseName": "admin" -+ } -+ } -+ ] -+ } -+ ], -+ "outcome": [ -+ { -+ "collectionName": "test", -+ "databaseName": "transaction-tests", -+ "documents": [] -+ } -+ ] -+ } -+ ] -+} -diff --git b/test/uri_options/client-backpressure-options.json a/test/uri_options/client-backpressure-options.json -new file mode 100644 -index 00000000..3fcf2c86 ---- /dev/null -+++ a/test/uri_options/client-backpressure-options.json -@@ -0,0 +1,35 @@ -+{ -+ "tests": [ -+ { -+ "description": "adaptiveRetries=true is parsed correctly", -+ "uri": "mongodb://example.com/?adaptiveRetries=true", -+ "valid": true, -+ "warning": false, -+ "hosts": null, -+ "auth": null, -+ "options": { -+ "adaptiveRetries": true -+ } -+ }, -+ { -+ "description": "adaptiveRetries=false is parsed correctly", -+ "uri": "mongodb://example.com/?adaptiveRetries=false", -+ "valid": true, -+ "warning": false, -+ "hosts": null, -+ "auth": null, -+ "options": { -+ "adaptiveRetries": false -+ } -+ }, -+ { -+ "description": "adaptiveRetries with invalid value causes a warning", -+ "uri": "mongodb://example.com/?adaptiveRetries=invalid", -+ "valid": true, -+ "warning": true, -+ "hosts": null, -+ "auth": null, -+ "options": null -+ } -+ ] -+} diff --git a/doc/changelog.rst b/doc/changelog.rst index 23d5b2fc9e..a1178ef3db 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -14,6 +14,9 @@ PyMongo 4.17 brings a number of changes including: - Added the :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.bind` and :meth:`~pymongo.client_session.ClientSession.bind` methods that allow users to bind a session to all database operations within the scope of a context manager instead of having to explicitly pass the session to each individual operation. See for examples and more information. +- Added support for MongoDB's Intelligent Workload Management (IWM) and ingress connection rate limiting features. + The driver now gracefully handles write-blocking scenarios and optimizes connection establishment during high-load conditions to maintain application availability. + See and for more information. Changes in Version 4.16.0 (2026/01/07) -------------------------------------- diff --git a/pymongo/asynchronous/client_bulk.py b/pymongo/asynchronous/client_bulk.py index 151942c8a8..015947d7ef 100644 --- a/pymongo/asynchronous/client_bulk.py +++ b/pymongo/asynchronous/client_bulk.py @@ -59,6 +59,7 @@ InvalidOperation, NotPrimaryError, OperationFailure, + PyMongoError, WaitQueueTimeoutError, ) from pymongo.helpers_shared import _RETRYABLE_ERROR_CODES @@ -563,9 +564,17 @@ async def _execute_command( error, ConnectionFailure ) and not isinstance(error, (NotPrimaryError, WaitQueueTimeoutError)) + retryable_label_error = isinstance( + error, PyMongoError + ) and error.has_error_label("RetryableError") + # Synthesize the full bulk result without modifying the # current one because this write operation may be retried. - if retryable and (retryable_top_level_error or retryable_network_error): + if retryable and ( + retryable_top_level_error + or retryable_network_error + or retryable_label_error + ): full = copy.deepcopy(full_result) _merge_command(self.ops, self.idx_offset, full, result) _throw_client_bulk_write_exception(full, self.verbose_results) diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index c1e5a404d2..31e6ceb386 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -135,7 +135,9 @@ from __future__ import annotations +import asyncio import collections +import random import time import uuid from collections.abc import Mapping as _Mapping @@ -162,7 +164,9 @@ from pymongo.errors import ( ConfigurationError, ConnectionFailure, + ExecutionTimeout, InvalidOperation, + NetworkTimeout, OperationFailure, PyMongoError, WTimeoutError, @@ -427,6 +431,7 @@ def __init__(self, opts: Optional[TransactionOptions], client: AsyncMongoClient[ self.recovery_token = None self.attempt = 0 self.client = client + self.has_completed_command = False def active(self) -> bool: return self.state in (_TxnState.STARTING, _TxnState.IN_PROGRESS) @@ -434,6 +439,9 @@ def active(self) -> bool: def starting(self) -> bool: return self.state == _TxnState.STARTING + def set_starting(self) -> None: + self.state = _TxnState.STARTING + @property def pinned_conn(self) -> Optional[AsyncConnection]: if self.active() and self.conn_mgr: @@ -459,6 +467,7 @@ async def reset(self) -> None: self.sharded = False self.recovery_token = None self.attempt = 0 + self.has_completed_command = False def __del__(self) -> None: if self.conn_mgr: @@ -493,11 +502,29 @@ def _max_time_expired_error(exc: PyMongoError) -> bool: # This limit is non-configurable and was chosen to be twice the 60 second # default value of MongoDB's `transactionLifetimeLimitSeconds` parameter. _WITH_TRANSACTION_RETRY_TIME_LIMIT = 120 +_BACKOFF_MAX = 0.500 # 500ms max backoff +_BACKOFF_INITIAL = 0.005 # 5ms initial backoff -def _within_time_limit(start_time: float) -> bool: +def _within_time_limit(start_time: float, backoff: float = 0) -> bool: """Are we within the with_transaction retry limit?""" - return time.monotonic() - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT + remaining = _csot.remaining() + if remaining is not None and remaining <= 0: + return False + return time.monotonic() + backoff - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT + + +def _make_timeout_error(error: BaseException) -> PyMongoError: + """Convert error to a NetworkTimeout or ExecutionTimeout as appropriate.""" + if _csot.remaining() is not None: + timeout_error: PyMongoError = ExecutionTimeout( + str(error), 50, {"ok": 0, "errmsg": str(error), "code": 50} + ) + else: + timeout_error = NetworkTimeout(str(error)) + if isinstance(error, PyMongoError): + timeout_error._error_labels = error._error_labels.copy() + return timeout_error _T = TypeVar("_T") @@ -744,7 +771,17 @@ async def callback(session, custom_arg, custom_kwarg=None): https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/transactions-convenient-api.md#handling-errors-inside-the-callback """ start_time = time.monotonic() + retry = 0 + last_error: Optional[BaseException] = None while True: + if retry: # Implement exponential backoff on retry. + jitter = random.random() # noqa: S311 + backoff = jitter * min(_BACKOFF_INITIAL * (1.5**retry), _BACKOFF_MAX) + if not _within_time_limit(start_time, backoff): + assert last_error is not None + raise _make_timeout_error(last_error) from last_error + await asyncio.sleep(backoff) + retry += 1 await self.start_transaction( read_concern, write_concern, read_preference, max_commit_time_ms ) @@ -752,15 +789,16 @@ async def callback(session, custom_arg, custom_kwarg=None): ret = await callback(self) # Catch KeyboardInterrupt, CancelledError, etc. and cleanup. except BaseException as exc: + last_error = exc if self.in_transaction: await self.abort_transaction() - if ( - isinstance(exc, PyMongoError) - and exc.has_error_label("TransientTransactionError") - and _within_time_limit(start_time) + if isinstance(exc, PyMongoError) and exc.has_error_label( + "TransientTransactionError" ): - # Retry the entire transaction. - continue + if _within_time_limit(start_time): + # Retry the entire transaction. + continue + raise _make_timeout_error(last_error) from exc raise if not self.in_transaction: @@ -771,17 +809,18 @@ async def callback(session, custom_arg, custom_kwarg=None): try: await self.commit_transaction() except PyMongoError as exc: - if ( - exc.has_error_label("UnknownTransactionCommitResult") - and _within_time_limit(start_time) - and not _max_time_expired_error(exc) - ): + last_error = exc + if exc.has_error_label( + "UnknownTransactionCommitResult" + ) and not _max_time_expired_error(exc): + if not _within_time_limit(start_time): + raise _make_timeout_error(last_error) from exc # Retry the commit. continue - if exc.has_error_label("TransientTransactionError") and _within_time_limit( - start_time - ): + if exc.has_error_label("TransientTransactionError"): + if not _within_time_limit(start_time): + raise _make_timeout_error(last_error) from exc # Retry the entire transaction. break raise diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index 53b4992493..4fff6650f1 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -20,7 +20,6 @@ from typing import ( TYPE_CHECKING, Any, - AsyncContextManager, Callable, Coroutine, Generic, @@ -571,11 +570,6 @@ async def watch( await change_stream._initialize_cursor() return change_stream - async def _conn_for_writes( - self, session: Optional[AsyncClientSession], operation: str - ) -> AsyncContextManager[AsyncConnection]: - return await self._database.client._conn_for_writes(session, operation) - async def _command( self, conn: AsyncConnection, @@ -652,7 +646,10 @@ async def _create_helper( if "size" in options: options["size"] = float(options["size"]) cmd.update(options) - async with await self._conn_for_writes(session, operation=_Op.CREATE) as conn: + + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> None: if qev2_required and conn.max_wire_version < 21: raise ConfigurationError( "Driver support of Queryable Encryption is incompatible with server. " @@ -669,6 +666,8 @@ async def _create_helper( session=session, ) + await self.database.client._retryable_write(False, inner, session, _Op.CREATE) + async def _create( self, options: MutableMapping[str, Any], @@ -2240,7 +2239,10 @@ async def _create_indexes( command (like maxTimeMS) can be passed as keyword arguments. """ names = [] - async with await self._conn_for_writes(session, operation=_Op.CREATE_INDEXES) as conn: + + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> list[str]: supports_quorum = conn.max_wire_version >= 9 def gen_indexes() -> Iterator[Mapping[str, Any]]: @@ -2269,7 +2271,11 @@ def gen_indexes() -> Iterator[Mapping[str, Any]]: write_concern=self._write_concern_for(session), session=session, ) - return names + return names + + return await self.database.client._retryable_write( + False, inner, session, _Op.CREATE_INDEXES + ) async def create_index( self, @@ -2422,7 +2428,6 @@ async def drop_indexes( kwargs["comment"] = comment await self._drop_index("*", session=session, **kwargs) - @_csot.apply async def drop_index( self, index_or_name: _IndexKeyHint, @@ -2490,7 +2495,10 @@ async def _drop_index( cmd.update(kwargs) if comment is not None: cmd["comment"] = comment - async with await self._conn_for_writes(session, operation=_Op.DROP_INDEXES) as conn: + + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> None: await self._command( conn, cmd, @@ -2500,6 +2508,8 @@ async def _drop_index( session=session, ) + await self.database.client._retryable_write(False, inner, session, _Op.DROP_INDEXES) + async def list_indexes( self, session: Optional[AsyncClientSession] = None, @@ -2763,17 +2773,22 @@ def gen_indexes() -> Iterator[Mapping[str, Any]]: cmd = {"createSearchIndexes": self.name, "indexes": list(gen_indexes())} cmd.update(kwargs) - async with await self._conn_for_writes( - session, operation=_Op.CREATE_SEARCH_INDEXES - ) as conn: + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> list[str]: resp = await self._command( conn, cmd, read_preference=ReadPreference.PRIMARY, codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, + session=session, ) return [index["name"] for index in resp["indexesCreated"]] + return await self.database.client._retryable_write( + False, inner, session, _Op.CREATE_SEARCH_INDEXES + ) + async def drop_search_index( self, name: str, @@ -2799,15 +2814,21 @@ async def drop_search_index( cmd.update(kwargs) if comment is not None: cmd["comment"] = comment - async with await self._conn_for_writes(session, operation=_Op.DROP_SEARCH_INDEXES) as conn: + + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> None: await self._command( conn, cmd, read_preference=ReadPreference.PRIMARY, allowable_errors=["ns not found", 26], codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, + session=session, ) + await self.database.client._retryable_write(False, inner, session, _Op.DROP_SEARCH_INDEXES) + async def update_search_index( self, name: str, @@ -2835,15 +2856,21 @@ async def update_search_index( cmd.update(kwargs) if comment is not None: cmd["comment"] = comment - async with await self._conn_for_writes(session, operation=_Op.UPDATE_SEARCH_INDEX) as conn: + + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> None: await self._command( conn, cmd, read_preference=ReadPreference.PRIMARY, allowable_errors=["ns not found", 26], codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, + session=session, ) + await self.database.client._retryable_write(False, inner, session, _Op.UPDATE_SEARCH_INDEX) + async def options( self, session: Optional[AsyncClientSession] = None, @@ -2918,6 +2945,7 @@ async def _aggregate( session, retryable=not cmd._performs_write, operation=_Op.AGGREGATE, + is_aggregate_write=cmd._performs_write, ) async def aggregate( @@ -3123,17 +3151,21 @@ async def rename( if comment is not None: cmd["comment"] = comment write_concern = self._write_concern_for_cmd(cmd, session) + client = self._database.client - async with await self._conn_for_writes(session, operation=_Op.RENAME) as conn: - async with self._database.client._tmp_session(session) as s: - return await conn.command( - "admin", - cmd, - write_concern=write_concern, - parse_write_concern_error=True, - session=s, - client=self._database.client, - ) + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> MutableMapping[str, Any]: + return await conn.command( + "admin", + cmd, + write_concern=write_concern, + parse_write_concern_error=True, + session=session, + client=client, + ) + + return await client._retryable_write(False, inner, session, _Op.RENAME) async def distinct( self, diff --git a/pymongo/asynchronous/database.py b/pymongo/asynchronous/database.py index 5aa206ee24..28ed36073c 100644 --- a/pymongo/asynchronous/database.py +++ b/pymongo/asynchronous/database.py @@ -931,14 +931,15 @@ async def command( if read_preference is None: read_preference = (session and session._txn_read_preference()) or ReadPreference.PRIMARY - async with await self._client._conn_for_reads( - read_preference, session, operation=command_name - ) as ( - connection, - read_preference, - ): + + async def inner( + session: Optional[AsyncClientSession], + _server: Server, + conn: AsyncConnection, + read_preference: _ServerMode, + ) -> Union[dict[str, Any], _CodecDocumentType]: return await self._command( - connection, + conn, command, value, check, @@ -949,6 +950,10 @@ async def command( **kwargs, ) + return await self._client._retryable_read( + inner, read_preference, session, command_name, None, False, is_run_command=True + ) + @_csot.apply async def cursor_command( self, @@ -1016,17 +1021,17 @@ async def cursor_command( async with self._client._tmp_session(session) as tmp_session: opts = codec_options or DEFAULT_CODEC_OPTIONS - if read_preference is None: read_preference = ( tmp_session and tmp_session._txn_read_preference() ) or ReadPreference.PRIMARY - async with await self._client._conn_for_reads( - read_preference, tmp_session, command_name - ) as ( - conn, - read_preference, - ): + + async def inner( + session: Optional[AsyncClientSession], + _server: Server, + conn: AsyncConnection, + read_preference: _ServerMode, + ) -> AsyncCommandCursor[_DocumentType]: response = await self._command( conn, command, @@ -1035,7 +1040,7 @@ async def cursor_command( None, read_preference, opts, - session=tmp_session, + session=session, **kwargs, ) coll = self.get_collection("$cmd", read_preference=read_preference) @@ -1045,7 +1050,7 @@ async def cursor_command( response["cursor"], conn.address, max_await_time_ms=max_await_time_ms, - session=tmp_session, + session=session, comment=comment, ) await cmd_cursor._maybe_pin_connection(conn) @@ -1053,6 +1058,10 @@ async def cursor_command( else: raise InvalidOperation("Command does not return a cursor.") + return await self.client._retryable_read( + inner, read_preference, tmp_session, command_name, None, False + ) + async def _retryable_read_command( self, command: Union[str, MutableMapping[str, Any]], @@ -1254,9 +1263,11 @@ async def _drop_helper( if comment is not None: command["comment"] = comment - async with await self._client._conn_for_writes(session, operation=_Op.DROP) as connection: + async def inner( + session: Optional[AsyncClientSession], conn: AsyncConnection, _retryable_write: bool + ) -> dict[str, Any]: return await self._command( - connection, + conn, command, allowable_errors=["ns not found", 26], write_concern=self._write_concern_for(session), @@ -1264,6 +1275,8 @@ async def _drop_helper( session=session, ) + return await self.client._retryable_write(False, inner, session, _Op.DROP) + @_csot.apply async def drop_collection( self, diff --git a/pymongo/asynchronous/helpers.py b/pymongo/asynchronous/helpers.py index ccda16e28b..16b007c38f 100644 --- a/pymongo/asynchronous/helpers.py +++ b/pymongo/asynchronous/helpers.py @@ -17,8 +17,11 @@ import asyncio import builtins +import functools +import random import socket import sys +import time as time # noqa: PLC0414 # needed in sync version from typing import ( Any, Callable, @@ -26,6 +29,8 @@ cast, ) +from pymongo import _csot +from pymongo.common import MAX_ADAPTIVE_RETRIES from pymongo.errors import ( OperationFailure, ) @@ -38,6 +43,7 @@ def _handle_reauth(func: F) -> F: + @functools.wraps(func) async def inner(*args: Any, **kwargs: Any) -> Any: no_reauth = kwargs.pop("no_reauth", False) from pymongo.asynchronous.pool import AsyncConnection @@ -70,6 +76,46 @@ async def inner(*args: Any, **kwargs: Any) -> Any: return cast(F, inner) +_BACKOFF_INITIAL = 0.1 +_BACKOFF_MAX = 10 + + +def _backoff( + attempt: int, initial_delay: float = _BACKOFF_INITIAL, max_delay: float = _BACKOFF_MAX +) -> float: + jitter = random.random() # noqa: S311 + return jitter * min(initial_delay * (2**attempt), max_delay) + + +class _RetryPolicy: + """A retry limiter that performs exponential backoff with jitter.""" + + def __init__( + self, + attempts: int = MAX_ADAPTIVE_RETRIES, + backoff_initial: float = _BACKOFF_INITIAL, + backoff_max: float = _BACKOFF_MAX, + ): + self.attempts = attempts + self.backoff_initial = backoff_initial + self.backoff_max = backoff_max + + def backoff(self, attempt: int) -> float: + """Return the backoff duration for the given attempt.""" + return _backoff(max(0, attempt - 1), self.backoff_initial, self.backoff_max) + + async def should_retry(self, attempt: int, delay: float) -> bool: + """Return if we have retry attempts remaining and the next backoff would not exceed a timeout.""" + if attempt > self.attempts: + return False + + if _csot.get_timeout(): + if time.monotonic() + delay > _csot.get_deadline(): + return False + + return True + + async def _getaddrinfo( host: Any, port: Any, **kwargs: Any ) -> list[ diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 95f2e3746e..03e2d6073a 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -35,6 +35,7 @@ import asyncio import contextlib import os +import time as time # noqa: PLC0414 # needed in sync version import warnings import weakref from collections import defaultdict @@ -67,6 +68,9 @@ from pymongo.asynchronous.client_bulk import _AsyncClientBulk from pymongo.asynchronous.client_session import _SESSION, _EmptyServerSession from pymongo.asynchronous.command_cursor import AsyncCommandCursor +from pymongo.asynchronous.helpers import ( + _RetryPolicy, +) from pymongo.asynchronous.settings import TopologySettings from pymongo.asynchronous.topology import Topology, _ErrorContext from pymongo.client_options import ClientOptions @@ -610,8 +614,18 @@ def __init__( client to use Stable API. See `versioned API `_ for details. + | **Overload retry options:** + + - `max_adaptive_retries`: (int) How many retries to allow for overload errors. Defaults to ``2``. + - `enable_overload_retargeting`: (boolean) Whether overload retargeting is enabled for this client. + If enabled, server overload errors will cause retry attempts to select a server that has not yet returned an overload error, if possible. + Defaults to ``False``. + .. seealso:: The MongoDB documentation on `connections `_. + .. versionchanged:: 4.17 + Added the ``max_adaptive_retries`` and ``enable_overload_retargeting`` URI and keyword arguments. + .. versionchanged:: 4.5 Added the ``serverMonitoringMode`` keyword argument. @@ -879,11 +893,14 @@ def __init__( self._options.read_concern, ) + self._retry_policy = _RetryPolicy(attempts=self._options.max_adaptive_retries) + self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name) self._opened = False self._closed = False self._loop: Optional[asyncio.AbstractEventLoop] = None + if not is_srv: self._init_background() @@ -1991,6 +2008,8 @@ async def _retry_internal( read_pref: Optional[_ServerMode] = None, retryable: bool = False, operation_id: Optional[int] = None, + is_run_command: bool = False, + is_aggregate_write: bool = False, ) -> T: """Internal retryable helper for all client transactions. @@ -2002,6 +2021,8 @@ async def _retry_internal( :param address: Server Address, defaults to None :param read_pref: Topology of read operation, defaults to None :param retryable: If the operation should be retried once, defaults to None + :param is_run_command: If this is a runCommand operation, defaults to False + :param is_aggregate_write: If this is a aggregate operation with a write, defaults to False. :return: Output of the calling func() """ @@ -2016,6 +2037,8 @@ async def _retry_internal( address=address, retryable=retryable, operation_id=operation_id, + is_run_command=is_run_command, + is_aggregate_write=is_aggregate_write, ).run() async def _retryable_read( @@ -2027,6 +2050,8 @@ async def _retryable_read( address: Optional[_Address] = None, retryable: bool = True, operation_id: Optional[int] = None, + is_run_command: bool = False, + is_aggregate_write: bool = False, ) -> T: """Execute an operation with consecutive retries if possible @@ -2042,6 +2067,8 @@ async def _retryable_read( :param address: Optional address when sending a message, defaults to None :param retryable: if we should attempt retries (may not always be supported even if supplied), defaults to False + :param is_run_command: If this is a runCommand operation, defaults to False. + :param is_aggregate_write: If this is a aggregate operation with a write, defaults to False. """ # Ensure that the client supports retrying on reads and there is no session in @@ -2060,6 +2087,8 @@ async def _retryable_read( read_pref=read_pref, retryable=retryable, operation_id=operation_id, + is_run_command=is_run_command, + is_aggregate_write=is_aggregate_write, ) async def _retryable_write( @@ -2454,15 +2483,13 @@ async def drop_database( f"name_or_database must be an instance of str or a AsyncDatabase, not {type(name)}" ) - async with await self._conn_for_writes(session, operation=_Op.DROP_DATABASE) as conn: - await self[name]._command( - conn, - {"dropDatabase": 1, "comment": comment}, - read_preference=ReadPreference.PRIMARY, - write_concern=self._write_concern_for(session), - parse_write_concern_error=True, - session=session, - ) + await self[name].command( + {"dropDatabase": 1, "comment": comment}, + read_preference=ReadPreference.PRIMARY, + write_concern=self._write_concern_for(session), + parse_write_concern_error=True, + session=session, + ) @_csot.apply async def bulk_write( @@ -2746,12 +2773,15 @@ def __init__( address: Optional[_Address] = None, retryable: bool = False, operation_id: Optional[int] = None, + is_run_command: bool = False, + is_aggregate_write: bool = False, ): self._last_error: Optional[Exception] = None self._retrying = False + self._always_retryable = False self._multiple_retries = _csot.get_timeout() is not None self._client = mongo_client - + self._retry_policy = mongo_client._retry_policy self._func = func self._bulk = bulk self._session = session @@ -2767,6 +2797,8 @@ def __init__( self._operation = operation self._operation_id = operation_id self._attempt_number = 0 + self._is_run_command = is_run_command + self._is_aggregate_write = is_aggregate_write async def run(self) -> T: """Runs the supplied func() and attempts a retry @@ -2786,7 +2818,13 @@ async def run(self) -> T: while True: self._check_last_error(check_csot=True) try: - return await self._read() if self._is_read else await self._write() + res = await self._read() if self._is_read else await self._write() + # Track whether the transaction has completed a command. + # If we need to apply backpressure to the first command, + # we will need to revert back to starting state. + if self._session is not None and self._session.in_transaction: + self._session._transaction.has_completed_command = True + return res except ServerSelectionTimeoutError: # The application may think the write was never attempted # if we raise ServerSelectionTimeoutError on the retry @@ -2797,37 +2835,76 @@ async def run(self) -> T: # most likely be a waste of time. raise except PyMongoError as exc: + always_retryable = False + overloaded = False + exc_to_check = exc + + if self._is_run_command and not ( + self._client.options.retry_reads and self._client.options.retry_writes + ): + raise + if self._is_aggregate_write and not self._client.options.retry_writes: + raise + # Execute specialized catch on read if self._is_read: if isinstance(exc, (ConnectionFailure, OperationFailure)): # ConnectionFailures do not supply a code property exc_code = getattr(exc, "code", None) - if self._is_not_eligible_for_retry() or ( - isinstance(exc, OperationFailure) - and exc_code not in helpers_shared._RETRYABLE_ERROR_CODES + overloaded = exc.has_error_label("SystemOverloadedError") + always_retryable = exc.has_error_label("RetryableError") and overloaded + if not self._client.options.retry_reads or ( + not always_retryable + and ( + self._is_not_eligible_for_retry() + or ( + isinstance(exc, OperationFailure) + and exc_code not in helpers_shared._RETRYABLE_ERROR_CODES + ) + ) ): raise self._retrying = True self._last_error = exc self._attempt_number += 1 + + # Revert back to starting state if we're in a transaction but haven't completed the first + # command. + if ( + overloaded + and self._session is not None + and self._session.in_transaction + ): + transaction = self._session._transaction + if not transaction.has_completed_command: + transaction.set_starting() + transaction.attempt = 0 else: raise # Specialized catch on write operation if not self._is_read: - if not self._retryable: + if isinstance(exc, ClientBulkWriteException) and isinstance( + exc.error, PyMongoError + ): + exc_to_check = exc.error + retryable_write_label = exc_to_check.has_error_label("RetryableWriteError") + overloaded = exc_to_check.has_error_label("SystemOverloadedError") + always_retryable = exc_to_check.has_error_label("RetryableError") and overloaded + + # Always retry abortTransaction and commitTransaction up to once + if self._operation not in ["abortTransaction", "commitTransaction"] and ( + not self._client.options.retry_writes + or not (self._retryable or always_retryable) + ): raise - if isinstance(exc, ClientBulkWriteException) and exc.error: - retryable_write_error_exc = isinstance( - exc.error, PyMongoError - ) and exc.error.has_error_label("RetryableWriteError") - else: - retryable_write_error_exc = exc.has_error_label("RetryableWriteError") - if retryable_write_error_exc: + if retryable_write_label or always_retryable: assert self._session await self._session._unpin() - if not retryable_write_error_exc or self._is_not_eligible_for_retry(): - if exc.has_error_label("NoWritesPerformed") and self._last_error: + if not always_retryable and ( + not retryable_write_label or self._is_not_eligible_for_retry() + ): + if exc_to_check.has_error_label("NoWritesPerformed") and self._last_error: raise self._last_error from exc else: raise @@ -2836,18 +2913,34 @@ async def run(self) -> T: self._bulk.retrying = True else: self._retrying = True - if not exc.has_error_label("NoWritesPerformed"): + if not exc_to_check.has_error_label("NoWritesPerformed"): self._last_error = exc if self._last_error is None: self._last_error = exc - - if ( - self._server is not None - and self._client.topology_description.topology_type_name == "Sharded" - or exc.has_error_label("SystemOverloadedError") + # Revert back to starting state if we're in a transaction but haven't completed the first + # command. + if overloaded and self._session is not None and self._session.in_transaction: + transaction = self._session._transaction + if not transaction.has_completed_command: + transaction.set_starting() + transaction.attempt = 0 + + if self._server is not None and ( + self._client.topology_description.topology_type_name == "Sharded" + or (overloaded and self._client.options.enable_overload_retargeting) ): self._deprioritized_servers.append(self._server) + self._always_retryable = always_retryable + if overloaded: + delay = self._retry_policy.backoff(self._attempt_number) + if not await self._retry_policy.should_retry(self._attempt_number, delay): + if exc_to_check.has_error_label("NoWritesPerformed") and self._last_error: + raise self._last_error from exc + else: + raise + await asyncio.sleep(delay) + def _is_not_eligible_for_retry(self) -> bool: """Checks if the exchange is not eligible for retry""" return not self._retryable or (self._is_retrying() and not self._multiple_retries) @@ -2909,7 +3002,7 @@ async def _write(self) -> T: and conn.supports_sessions ) is_mongos = conn.is_mongos - if not sessions_supported: + if not self._always_retryable and not sessions_supported: # A retry is not possible because this server does # not support sessions raise the last error. self._check_last_error() @@ -2941,7 +3034,7 @@ async def _read(self) -> T: conn, read_pref, ): - if self._retrying and not self._retryable: + if self._retrying and not self._retryable and not self._always_retryable: self._check_last_error() if self._retrying: _debug_log( diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index 4e1e7c0638..3c1a85246e 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -19,6 +19,8 @@ import contextlib import logging import os +import socket +import ssl import sys import time import weakref @@ -52,10 +54,12 @@ DocumentTooLarge, ExecutionTimeout, InvalidOperation, + NetworkTimeout, NotPrimaryError, OperationFailure, PyMongoError, WaitQueueTimeoutError, + _CertificateError, ) from pymongo.hello import Hello, HelloCompat from pymongo.helpers_shared import _get_timeout_details, format_timeout_details @@ -250,6 +254,7 @@ async def _hello( cmd = self.hello_cmd() performing_handshake = not self.performed_handshake awaitable = False + cmd["backpressure"] = True if performing_handshake: self.performed_handshake = True cmd["client"] = self.opts.metadata @@ -752,8 +757,8 @@ def __init__( # Enforces: maxConnecting # Also used for: clearing the wait queue self._max_connecting_cond = _async_create_condition(self.lock) - self._max_connecting = self.opts.max_connecting self._pending = 0 + self._max_connecting = self.opts.max_connecting self._client_id = client_id if self.enabled_for_cmap: assert self.opts._event_listeners is not None @@ -986,6 +991,21 @@ async def remove_stale_sockets(self, reference_generation: int) -> None: self.requests -= 1 self.size_cond.notify() + def _handle_connection_error(self, error: BaseException) -> None: + # Handle system overload condition for non-sdam pools. + # Look for errors of type AutoReconnect and add error labels if appropriate. + if self.is_sdam or type(error) not in (AutoReconnect, NetworkTimeout): + return + assert isinstance(error, AutoReconnect) # Appease type checker. + # If the original error was a DNS, certificate, or SSL error, ignore it. + if isinstance(error.__cause__, (_CertificateError, SSLErrors, socket.gaierror)): + # End of file errors are excluded, because the server may have disconnected + # during the handshake. + if not isinstance(error.__cause__, (ssl.SSLEOFError, ssl.SSLZeroReturnError)): + return + error._add_error_label("SystemOverloadedError") + error._add_error_label("RetryableError") + async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> AsyncConnection: """Connect to Mongo and return a new AsyncConnection. @@ -1037,10 +1057,10 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR), error=ConnectionClosedReason.ERROR, ) + self._handle_connection_error(error) if isinstance(error, (IOError, OSError, *SSLErrors)): details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) - raise conn = AsyncConnection(networking_interface, self, self.address, conn_id, self.is_sdam) # type: ignore[arg-type] @@ -1049,18 +1069,22 @@ async def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> A self.active_contexts.discard(tmp_context) if tmp_context.cancelled: conn.cancel_context.cancel() + completed_hello = False try: if not self.is_sdam: await conn.hello() + completed_hello = True self.is_writable = conn.is_writable if handler: handler.contribute_socket(conn, completed_handshake=False) await conn.authenticate() # Catch KeyboardInterrupt, CancelledError, etc. and cleanup. - except BaseException: + except BaseException as e: async with self.lock: self.active_contexts.discard(conn.cancel_context) + if not completed_hello: + self._handle_connection_error(e) await conn.close_conn(ConnectionClosedReason.ERROR) raise @@ -1389,8 +1413,8 @@ async def _perished(self, conn: AsyncConnection) -> bool: :class:`~pymongo.errors.AutoReconnect` exceptions on server hiccups, etc. We only check if the socket was closed by an external error if it has been > 1 second since the socket was checked into the - pool, to keep performance reasonable - we can't avoid AutoReconnects - completely anyway. + pool to keep performance reasonable - + we can't avoid AutoReconnects completely anyway. """ idle_time_seconds = conn.idle_time_seconds() # If socket is idle, open a new one. @@ -1401,8 +1425,9 @@ async def _perished(self, conn: AsyncConnection) -> bool: await conn.close_conn(ConnectionClosedReason.IDLE) return True - if self._check_interval_seconds is not None and ( - self._check_interval_seconds == 0 or idle_time_seconds > self._check_interval_seconds + check_interval_seconds = self._check_interval_seconds + if check_interval_seconds is not None and ( + check_interval_seconds == 0 or idle_time_seconds > check_interval_seconds ): if conn.conn_closed(): await conn.close_conn(ConnectionClosedReason.ERROR) diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index c171848cac..01e346bfa8 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -913,7 +913,9 @@ async def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None # Clear the pool. await server.reset(service_id) elif isinstance(error, ConnectionFailure): - if isinstance(error, WaitQueueTimeoutError): + if isinstance(error, WaitQueueTimeoutError) or ( + error.has_error_label("SystemOverloadedError") + ): return # "Client MUST replace the server's description with type Unknown # ... MUST NOT request an immediate check of the server." diff --git a/pymongo/client_options.py b/pymongo/client_options.py index 8b4eea7e65..e5dc609946 100644 --- a/pymongo/client_options.py +++ b/pymongo/client_options.py @@ -235,6 +235,16 @@ def __init__( self.__server_monitoring_mode = options.get( "servermonitoringmode", common.SERVER_MONITORING_MODE ) + self.__max_adaptive_retries = ( + options.get("max_adaptive_retries", common.MAX_ADAPTIVE_RETRIES) + if "max_adaptive_retries" in options + else options.get("maxadaptiveretries", common.MAX_ADAPTIVE_RETRIES) + ) + self.__enable_overload_retargeting = ( + options.get("enable_overload_retargeting", common.ENABLE_OVERLOAD_RETARGETING) + if "enable_overload_retargeting" in options + else options.get("enableoverloadretargeting", common.ENABLE_OVERLOAD_RETARGETING) + ) @property def _options(self) -> Mapping[str, Any]: @@ -346,3 +356,19 @@ def server_monitoring_mode(self) -> str: .. versionadded:: 4.5 """ return self.__server_monitoring_mode + + @property + def max_adaptive_retries(self) -> int: + """The configured maxAdaptiveRetries option. + + .. versionadded:: 4.17 + """ + return self.__max_adaptive_retries + + @property + def enable_overload_retargeting(self) -> bool: + """The configured enableOverloadRetargeting option. + + .. versionadded:: 4.17 + """ + return self.__enable_overload_retargeting diff --git a/pymongo/common.py b/pymongo/common.py index 118cca89db..ea349b3d23 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -140,6 +140,12 @@ # Default value for serverMonitoringMode SERVER_MONITORING_MODE = "auto" # poll/stream/auto +# Default value for max adaptive retries +MAX_ADAPTIVE_RETRIES = 2 + +# Default value for enableOverloadRetargeting +ENABLE_OVERLOAD_RETARGETING = False + # Auth mechanism properties that must raise an error instead of warning if they invalidate. _MECH_PROP_MUST_RAISE = ["CANONICALIZE_HOST_NAME"] @@ -717,6 +723,8 @@ def validate_server_monitoring_mode(option: str, value: str) -> str: "srvmaxhosts": validate_non_negative_integer, "timeoutms": validate_timeoutms, "servermonitoringmode": validate_server_monitoring_mode, + "maxadaptiveretries": validate_non_negative_integer, + "enableoverloadretargeting": validate_boolean_or_string, } # Dictionary where keys are the names of URI options specific to pymongo, @@ -750,6 +758,8 @@ def validate_server_monitoring_mode(option: str, value: str) -> str: "server_selector": validate_is_callable_or_none, "auto_encryption_opts": validate_auto_encryption_opts_or_none, "authoidcallowedhosts": validate_list, + "max_adaptive_retries": validate_non_negative_integer, + "enable_overload_retargeting": validate_boolean_or_string, } # Dictionary where keys are any URI option name, and values are the diff --git a/pymongo/synchronous/client_bulk.py b/pymongo/synchronous/client_bulk.py index a606d028e1..1134594ae9 100644 --- a/pymongo/synchronous/client_bulk.py +++ b/pymongo/synchronous/client_bulk.py @@ -59,6 +59,7 @@ InvalidOperation, NotPrimaryError, OperationFailure, + PyMongoError, WaitQueueTimeoutError, ) from pymongo.helpers_shared import _RETRYABLE_ERROR_CODES @@ -561,9 +562,17 @@ def _execute_command( error, ConnectionFailure ) and not isinstance(error, (NotPrimaryError, WaitQueueTimeoutError)) + retryable_label_error = isinstance( + error, PyMongoError + ) and error.has_error_label("RetryableError") + # Synthesize the full bulk result without modifying the # current one because this write operation may be retried. - if retryable and (retryable_top_level_error or retryable_network_error): + if retryable and ( + retryable_top_level_error + or retryable_network_error + or retryable_label_error + ): full = copy.deepcopy(full_result) _merge_command(self.ops, self.idx_offset, full, result) _throw_client_bulk_write_exception(full, self.verbose_results) diff --git a/pymongo/synchronous/client_session.py b/pymongo/synchronous/client_session.py index 5ef18a66bd..3165dd52b7 100644 --- a/pymongo/synchronous/client_session.py +++ b/pymongo/synchronous/client_session.py @@ -136,6 +136,7 @@ from __future__ import annotations import collections +import random import time import uuid from collections.abc import Mapping as _Mapping @@ -160,7 +161,9 @@ from pymongo.errors import ( ConfigurationError, ConnectionFailure, + ExecutionTimeout, InvalidOperation, + NetworkTimeout, OperationFailure, PyMongoError, WTimeoutError, @@ -426,6 +429,7 @@ def __init__(self, opts: Optional[TransactionOptions], client: MongoClient[Any]) self.recovery_token = None self.attempt = 0 self.client = client + self.has_completed_command = False def active(self) -> bool: return self.state in (_TxnState.STARTING, _TxnState.IN_PROGRESS) @@ -433,6 +437,9 @@ def active(self) -> bool: def starting(self) -> bool: return self.state == _TxnState.STARTING + def set_starting(self) -> None: + self.state = _TxnState.STARTING + @property def pinned_conn(self) -> Optional[Connection]: if self.active() and self.conn_mgr: @@ -458,6 +465,7 @@ def reset(self) -> None: self.sharded = False self.recovery_token = None self.attempt = 0 + self.has_completed_command = False def __del__(self) -> None: if self.conn_mgr: @@ -492,11 +500,29 @@ def _max_time_expired_error(exc: PyMongoError) -> bool: # This limit is non-configurable and was chosen to be twice the 60 second # default value of MongoDB's `transactionLifetimeLimitSeconds` parameter. _WITH_TRANSACTION_RETRY_TIME_LIMIT = 120 +_BACKOFF_MAX = 0.500 # 500ms max backoff +_BACKOFF_INITIAL = 0.005 # 5ms initial backoff -def _within_time_limit(start_time: float) -> bool: +def _within_time_limit(start_time: float, backoff: float = 0) -> bool: """Are we within the with_transaction retry limit?""" - return time.monotonic() - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT + remaining = _csot.remaining() + if remaining is not None and remaining <= 0: + return False + return time.monotonic() + backoff - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT + + +def _make_timeout_error(error: BaseException) -> PyMongoError: + """Convert error to a NetworkTimeout or ExecutionTimeout as appropriate.""" + if _csot.remaining() is not None: + timeout_error: PyMongoError = ExecutionTimeout( + str(error), 50, {"ok": 0, "errmsg": str(error), "code": 50} + ) + else: + timeout_error = NetworkTimeout(str(error)) + if isinstance(error, PyMongoError): + timeout_error._error_labels = error._error_labels.copy() + return timeout_error _T = TypeVar("_T") @@ -743,21 +769,32 @@ def callback(session, custom_arg, custom_kwarg=None): https://github.com/mongodb/specifications/blob/master/source/transactions-convenient-api/transactions-convenient-api.md#handling-errors-inside-the-callback """ start_time = time.monotonic() + retry = 0 + last_error: Optional[BaseException] = None while True: + if retry: # Implement exponential backoff on retry. + jitter = random.random() # noqa: S311 + backoff = jitter * min(_BACKOFF_INITIAL * (1.5**retry), _BACKOFF_MAX) + if not _within_time_limit(start_time, backoff): + assert last_error is not None + raise _make_timeout_error(last_error) from last_error + time.sleep(backoff) + retry += 1 self.start_transaction(read_concern, write_concern, read_preference, max_commit_time_ms) try: ret = callback(self) # Catch KeyboardInterrupt, CancelledError, etc. and cleanup. except BaseException as exc: + last_error = exc if self.in_transaction: self.abort_transaction() - if ( - isinstance(exc, PyMongoError) - and exc.has_error_label("TransientTransactionError") - and _within_time_limit(start_time) + if isinstance(exc, PyMongoError) and exc.has_error_label( + "TransientTransactionError" ): - # Retry the entire transaction. - continue + if _within_time_limit(start_time): + # Retry the entire transaction. + continue + raise _make_timeout_error(last_error) from exc raise if not self.in_transaction: @@ -768,17 +805,18 @@ def callback(session, custom_arg, custom_kwarg=None): try: self.commit_transaction() except PyMongoError as exc: - if ( - exc.has_error_label("UnknownTransactionCommitResult") - and _within_time_limit(start_time) - and not _max_time_expired_error(exc) - ): + last_error = exc + if exc.has_error_label( + "UnknownTransactionCommitResult" + ) and not _max_time_expired_error(exc): + if not _within_time_limit(start_time): + raise _make_timeout_error(last_error) from exc # Retry the commit. continue - if exc.has_error_label("TransientTransactionError") and _within_time_limit( - start_time - ): + if exc.has_error_label("TransientTransactionError"): + if not _within_time_limit(start_time): + raise _make_timeout_error(last_error) from exc # Retry the entire transaction. break raise diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index edc6047330..1057151e59 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -21,7 +21,6 @@ TYPE_CHECKING, Any, Callable, - ContextManager, Generic, Iterable, Iterator, @@ -572,11 +571,6 @@ def watch( change_stream._initialize_cursor() return change_stream - def _conn_for_writes( - self, session: Optional[ClientSession], operation: str - ) -> ContextManager[Connection]: - return self._database.client._conn_for_writes(session, operation) - def _command( self, conn: Connection, @@ -653,7 +647,10 @@ def _create_helper( if "size" in options: options["size"] = float(options["size"]) cmd.update(options) - with self._conn_for_writes(session, operation=_Op.CREATE) as conn: + + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> None: if qev2_required and conn.max_wire_version < 21: raise ConfigurationError( "Driver support of Queryable Encryption is incompatible with server. " @@ -670,6 +667,8 @@ def _create_helper( session=session, ) + self.database.client._retryable_write(False, inner, session, _Op.CREATE) + def _create( self, options: MutableMapping[str, Any], @@ -2237,7 +2236,10 @@ def _create_indexes( command (like maxTimeMS) can be passed as keyword arguments. """ names = [] - with self._conn_for_writes(session, operation=_Op.CREATE_INDEXES) as conn: + + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> list[str]: supports_quorum = conn.max_wire_version >= 9 def gen_indexes() -> Iterator[Mapping[str, Any]]: @@ -2266,7 +2268,9 @@ def gen_indexes() -> Iterator[Mapping[str, Any]]: write_concern=self._write_concern_for(session), session=session, ) - return names + return names + + return self.database.client._retryable_write(False, inner, session, _Op.CREATE_INDEXES) def create_index( self, @@ -2419,7 +2423,6 @@ def drop_indexes( kwargs["comment"] = comment self._drop_index("*", session=session, **kwargs) - @_csot.apply def drop_index( self, index_or_name: _IndexKeyHint, @@ -2487,7 +2490,10 @@ def _drop_index( cmd.update(kwargs) if comment is not None: cmd["comment"] = comment - with self._conn_for_writes(session, operation=_Op.DROP_INDEXES) as conn: + + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> None: self._command( conn, cmd, @@ -2497,6 +2503,8 @@ def _drop_index( session=session, ) + self.database.client._retryable_write(False, inner, session, _Op.DROP_INDEXES) + def list_indexes( self, session: Optional[ClientSession] = None, @@ -2760,15 +2768,22 @@ def gen_indexes() -> Iterator[Mapping[str, Any]]: cmd = {"createSearchIndexes": self.name, "indexes": list(gen_indexes())} cmd.update(kwargs) - with self._conn_for_writes(session, operation=_Op.CREATE_SEARCH_INDEXES) as conn: + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> list[str]: resp = self._command( conn, cmd, read_preference=ReadPreference.PRIMARY, codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, + session=session, ) return [index["name"] for index in resp["indexesCreated"]] + return self.database.client._retryable_write( + False, inner, session, _Op.CREATE_SEARCH_INDEXES + ) + def drop_search_index( self, name: str, @@ -2794,15 +2809,21 @@ def drop_search_index( cmd.update(kwargs) if comment is not None: cmd["comment"] = comment - with self._conn_for_writes(session, operation=_Op.DROP_SEARCH_INDEXES) as conn: + + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> None: self._command( conn, cmd, read_preference=ReadPreference.PRIMARY, allowable_errors=["ns not found", 26], codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, + session=session, ) + self.database.client._retryable_write(False, inner, session, _Op.DROP_SEARCH_INDEXES) + def update_search_index( self, name: str, @@ -2830,15 +2851,21 @@ def update_search_index( cmd.update(kwargs) if comment is not None: cmd["comment"] = comment - with self._conn_for_writes(session, operation=_Op.UPDATE_SEARCH_INDEX) as conn: + + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> None: self._command( conn, cmd, read_preference=ReadPreference.PRIMARY, allowable_errors=["ns not found", 26], codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, + session=session, ) + self.database.client._retryable_write(False, inner, session, _Op.UPDATE_SEARCH_INDEX) + def options( self, session: Optional[ClientSession] = None, @@ -2911,6 +2938,7 @@ def _aggregate( session, retryable=not cmd._performs_write, operation=_Op.AGGREGATE, + is_aggregate_write=cmd._performs_write, ) def aggregate( @@ -3116,17 +3144,21 @@ def rename( if comment is not None: cmd["comment"] = comment write_concern = self._write_concern_for_cmd(cmd, session) + client = self._database.client - with self._conn_for_writes(session, operation=_Op.RENAME) as conn: - with self._database.client._tmp_session(session) as s: - return conn.command( - "admin", - cmd, - write_concern=write_concern, - parse_write_concern_error=True, - session=s, - client=self._database.client, - ) + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> MutableMapping[str, Any]: + return conn.command( + "admin", + cmd, + write_concern=write_concern, + parse_write_concern_error=True, + session=session, + client=client, + ) + + return client._retryable_write(False, inner, session, _Op.RENAME) def distinct( self, diff --git a/pymongo/synchronous/database.py b/pymongo/synchronous/database.py index a453a94265..1956795bcb 100644 --- a/pymongo/synchronous/database.py +++ b/pymongo/synchronous/database.py @@ -931,12 +931,15 @@ def command( if read_preference is None: read_preference = (session and session._txn_read_preference()) or ReadPreference.PRIMARY - with self._client._conn_for_reads(read_preference, session, operation=command_name) as ( - connection, - read_preference, - ): + + def inner( + session: Optional[ClientSession], + _server: Server, + conn: Connection, + read_preference: _ServerMode, + ) -> Union[dict[str, Any], _CodecDocumentType]: return self._command( - connection, + conn, command, value, check, @@ -947,6 +950,10 @@ def command( **kwargs, ) + return self._client._retryable_read( + inner, read_preference, session, command_name, None, False, is_run_command=True + ) + @_csot.apply def cursor_command( self, @@ -1014,15 +1021,17 @@ def cursor_command( with self._client._tmp_session(session) as tmp_session: opts = codec_options or DEFAULT_CODEC_OPTIONS - if read_preference is None: read_preference = ( tmp_session and tmp_session._txn_read_preference() ) or ReadPreference.PRIMARY - with self._client._conn_for_reads(read_preference, tmp_session, command_name) as ( - conn, - read_preference, - ): + + def inner( + session: Optional[ClientSession], + _server: Server, + conn: Connection, + read_preference: _ServerMode, + ) -> CommandCursor[_DocumentType]: response = self._command( conn, command, @@ -1031,7 +1040,7 @@ def cursor_command( None, read_preference, opts, - session=tmp_session, + session=session, **kwargs, ) coll = self.get_collection("$cmd", read_preference=read_preference) @@ -1041,7 +1050,7 @@ def cursor_command( response["cursor"], conn.address, max_await_time_ms=max_await_time_ms, - session=tmp_session, + session=session, comment=comment, ) cmd_cursor._maybe_pin_connection(conn) @@ -1049,6 +1058,10 @@ def cursor_command( else: raise InvalidOperation("Command does not return a cursor.") + return self.client._retryable_read( + inner, read_preference, tmp_session, command_name, None, False + ) + def _retryable_read_command( self, command: Union[str, MutableMapping[str, Any]], @@ -1247,9 +1260,11 @@ def _drop_helper( if comment is not None: command["comment"] = comment - with self._client._conn_for_writes(session, operation=_Op.DROP) as connection: + def inner( + session: Optional[ClientSession], conn: Connection, _retryable_write: bool + ) -> dict[str, Any]: return self._command( - connection, + conn, command, allowable_errors=["ns not found", 26], write_concern=self._write_concern_for(session), @@ -1257,6 +1272,8 @@ def _drop_helper( session=session, ) + return self.client._retryable_write(False, inner, session, _Op.DROP) + @_csot.apply def drop_collection( self, diff --git a/pymongo/synchronous/helpers.py b/pymongo/synchronous/helpers.py index 1fff9a0f23..bbe8963fe7 100644 --- a/pymongo/synchronous/helpers.py +++ b/pymongo/synchronous/helpers.py @@ -17,8 +17,11 @@ import asyncio import builtins +import functools +import random import socket import sys +import time as time # noqa: PLC0414 # needed in sync version from typing import ( Any, Callable, @@ -26,6 +29,8 @@ cast, ) +from pymongo import _csot +from pymongo.common import MAX_ADAPTIVE_RETRIES from pymongo.errors import ( OperationFailure, ) @@ -38,6 +43,7 @@ def _handle_reauth(func: F) -> F: + @functools.wraps(func) def inner(*args: Any, **kwargs: Any) -> Any: no_reauth = kwargs.pop("no_reauth", False) from pymongo.message import _BulkWriteContext @@ -70,6 +76,46 @@ def inner(*args: Any, **kwargs: Any) -> Any: return cast(F, inner) +_BACKOFF_INITIAL = 0.1 +_BACKOFF_MAX = 10 + + +def _backoff( + attempt: int, initial_delay: float = _BACKOFF_INITIAL, max_delay: float = _BACKOFF_MAX +) -> float: + jitter = random.random() # noqa: S311 + return jitter * min(initial_delay * (2**attempt), max_delay) + + +class _RetryPolicy: + """A retry limiter that performs exponential backoff with jitter.""" + + def __init__( + self, + attempts: int = MAX_ADAPTIVE_RETRIES, + backoff_initial: float = _BACKOFF_INITIAL, + backoff_max: float = _BACKOFF_MAX, + ): + self.attempts = attempts + self.backoff_initial = backoff_initial + self.backoff_max = backoff_max + + def backoff(self, attempt: int) -> float: + """Return the backoff duration for the given attempt.""" + return _backoff(max(0, attempt - 1), self.backoff_initial, self.backoff_max) + + def should_retry(self, attempt: int, delay: float) -> bool: + """Return if we have retry attempts remaining and the next backoff would not exceed a timeout.""" + if attempt > self.attempts: + return False + + if _csot.get_timeout(): + if time.monotonic() + delay > _csot.get_deadline(): + return False + + return True + + def _getaddrinfo( host: Any, port: Any, **kwargs: Any ) -> list[ diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 161a28d48d..c049dcaeae 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -35,6 +35,7 @@ import asyncio import contextlib import os +import time as time # noqa: PLC0414 # needed in sync version import warnings import weakref from collections import defaultdict @@ -110,6 +111,9 @@ from pymongo.synchronous.client_bulk import _ClientBulk from pymongo.synchronous.client_session import _SESSION, _EmptyServerSession from pymongo.synchronous.command_cursor import CommandCursor +from pymongo.synchronous.helpers import ( + _RetryPolicy, +) from pymongo.synchronous.settings import TopologySettings from pymongo.synchronous.topology import Topology, _ErrorContext from pymongo.topology_description import TOPOLOGY_TYPE, TopologyDescription @@ -610,8 +614,18 @@ def __init__( client to use Stable API. See `versioned API `_ for details. + | **Overload retry options:** + + - `max_adaptive_retries`: (int) How many retries to allow for overload errors. Defaults to ``2``. + - `enable_overload_retargeting`: (boolean) Whether overload retargeting is enabled for this client. + If enabled, server overload errors will cause retry attempts to select a server that has not yet returned an overload error, if possible. + Defaults to ``False``. + .. seealso:: The MongoDB documentation on `connections `_. + .. versionchanged:: 4.17 + Added the ``max_adaptive_retries`` and ``enable_overload_retargeting`` URI and keyword arguments. + .. versionchanged:: 4.5 Added the ``serverMonitoringMode`` keyword argument. @@ -879,11 +893,14 @@ def __init__( self._options.read_concern, ) + self._retry_policy = _RetryPolicy(attempts=self._options.max_adaptive_retries) + self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name) self._opened = False self._closed = False self._loop: Optional[asyncio.AbstractEventLoop] = None + if not is_srv: self._init_background() @@ -1987,6 +2004,8 @@ def _retry_internal( read_pref: Optional[_ServerMode] = None, retryable: bool = False, operation_id: Optional[int] = None, + is_run_command: bool = False, + is_aggregate_write: bool = False, ) -> T: """Internal retryable helper for all client transactions. @@ -1998,6 +2017,8 @@ def _retry_internal( :param address: Server Address, defaults to None :param read_pref: Topology of read operation, defaults to None :param retryable: If the operation should be retried once, defaults to None + :param is_run_command: If this is a runCommand operation, defaults to False + :param is_aggregate_write: If this is a aggregate operation with a write, defaults to False. :return: Output of the calling func() """ @@ -2012,6 +2033,8 @@ def _retry_internal( address=address, retryable=retryable, operation_id=operation_id, + is_run_command=is_run_command, + is_aggregate_write=is_aggregate_write, ).run() def _retryable_read( @@ -2023,6 +2046,8 @@ def _retryable_read( address: Optional[_Address] = None, retryable: bool = True, operation_id: Optional[int] = None, + is_run_command: bool = False, + is_aggregate_write: bool = False, ) -> T: """Execute an operation with consecutive retries if possible @@ -2038,6 +2063,8 @@ def _retryable_read( :param address: Optional address when sending a message, defaults to None :param retryable: if we should attempt retries (may not always be supported even if supplied), defaults to False + :param is_run_command: If this is a runCommand operation, defaults to False. + :param is_aggregate_write: If this is a aggregate operation with a write, defaults to False. """ # Ensure that the client supports retrying on reads and there is no session in @@ -2056,6 +2083,8 @@ def _retryable_read( read_pref=read_pref, retryable=retryable, operation_id=operation_id, + is_run_command=is_run_command, + is_aggregate_write=is_aggregate_write, ) def _retryable_write( @@ -2444,15 +2473,13 @@ def drop_database( f"name_or_database must be an instance of str or a Database, not {type(name)}" ) - with self._conn_for_writes(session, operation=_Op.DROP_DATABASE) as conn: - self[name]._command( - conn, - {"dropDatabase": 1, "comment": comment}, - read_preference=ReadPreference.PRIMARY, - write_concern=self._write_concern_for(session), - parse_write_concern_error=True, - session=session, - ) + self[name].command( + {"dropDatabase": 1, "comment": comment}, + read_preference=ReadPreference.PRIMARY, + write_concern=self._write_concern_for(session), + parse_write_concern_error=True, + session=session, + ) @_csot.apply def bulk_write( @@ -2736,12 +2763,15 @@ def __init__( address: Optional[_Address] = None, retryable: bool = False, operation_id: Optional[int] = None, + is_run_command: bool = False, + is_aggregate_write: bool = False, ): self._last_error: Optional[Exception] = None self._retrying = False + self._always_retryable = False self._multiple_retries = _csot.get_timeout() is not None self._client = mongo_client - + self._retry_policy = mongo_client._retry_policy self._func = func self._bulk = bulk self._session = session @@ -2757,6 +2787,8 @@ def __init__( self._operation = operation self._operation_id = operation_id self._attempt_number = 0 + self._is_run_command = is_run_command + self._is_aggregate_write = is_aggregate_write def run(self) -> T: """Runs the supplied func() and attempts a retry @@ -2776,7 +2808,13 @@ def run(self) -> T: while True: self._check_last_error(check_csot=True) try: - return self._read() if self._is_read else self._write() + res = self._read() if self._is_read else self._write() + # Track whether the transaction has completed a command. + # If we need to apply backpressure to the first command, + # we will need to revert back to starting state. + if self._session is not None and self._session.in_transaction: + self._session._transaction.has_completed_command = True + return res except ServerSelectionTimeoutError: # The application may think the write was never attempted # if we raise ServerSelectionTimeoutError on the retry @@ -2787,37 +2825,76 @@ def run(self) -> T: # most likely be a waste of time. raise except PyMongoError as exc: + always_retryable = False + overloaded = False + exc_to_check = exc + + if self._is_run_command and not ( + self._client.options.retry_reads and self._client.options.retry_writes + ): + raise + if self._is_aggregate_write and not self._client.options.retry_writes: + raise + # Execute specialized catch on read if self._is_read: if isinstance(exc, (ConnectionFailure, OperationFailure)): # ConnectionFailures do not supply a code property exc_code = getattr(exc, "code", None) - if self._is_not_eligible_for_retry() or ( - isinstance(exc, OperationFailure) - and exc_code not in helpers_shared._RETRYABLE_ERROR_CODES + overloaded = exc.has_error_label("SystemOverloadedError") + always_retryable = exc.has_error_label("RetryableError") and overloaded + if not self._client.options.retry_reads or ( + not always_retryable + and ( + self._is_not_eligible_for_retry() + or ( + isinstance(exc, OperationFailure) + and exc_code not in helpers_shared._RETRYABLE_ERROR_CODES + ) + ) ): raise self._retrying = True self._last_error = exc self._attempt_number += 1 + + # Revert back to starting state if we're in a transaction but haven't completed the first + # command. + if ( + overloaded + and self._session is not None + and self._session.in_transaction + ): + transaction = self._session._transaction + if not transaction.has_completed_command: + transaction.set_starting() + transaction.attempt = 0 else: raise # Specialized catch on write operation if not self._is_read: - if not self._retryable: + if isinstance(exc, ClientBulkWriteException) and isinstance( + exc.error, PyMongoError + ): + exc_to_check = exc.error + retryable_write_label = exc_to_check.has_error_label("RetryableWriteError") + overloaded = exc_to_check.has_error_label("SystemOverloadedError") + always_retryable = exc_to_check.has_error_label("RetryableError") and overloaded + + # Always retry abortTransaction and commitTransaction up to once + if self._operation not in ["abortTransaction", "commitTransaction"] and ( + not self._client.options.retry_writes + or not (self._retryable or always_retryable) + ): raise - if isinstance(exc, ClientBulkWriteException) and exc.error: - retryable_write_error_exc = isinstance( - exc.error, PyMongoError - ) and exc.error.has_error_label("RetryableWriteError") - else: - retryable_write_error_exc = exc.has_error_label("RetryableWriteError") - if retryable_write_error_exc: + if retryable_write_label or always_retryable: assert self._session self._session._unpin() - if not retryable_write_error_exc or self._is_not_eligible_for_retry(): - if exc.has_error_label("NoWritesPerformed") and self._last_error: + if not always_retryable and ( + not retryable_write_label or self._is_not_eligible_for_retry() + ): + if exc_to_check.has_error_label("NoWritesPerformed") and self._last_error: raise self._last_error from exc else: raise @@ -2826,18 +2903,34 @@ def run(self) -> T: self._bulk.retrying = True else: self._retrying = True - if not exc.has_error_label("NoWritesPerformed"): + if not exc_to_check.has_error_label("NoWritesPerformed"): self._last_error = exc if self._last_error is None: self._last_error = exc - - if ( - self._server is not None - and self._client.topology_description.topology_type_name == "Sharded" - or exc.has_error_label("SystemOverloadedError") + # Revert back to starting state if we're in a transaction but haven't completed the first + # command. + if overloaded and self._session is not None and self._session.in_transaction: + transaction = self._session._transaction + if not transaction.has_completed_command: + transaction.set_starting() + transaction.attempt = 0 + + if self._server is not None and ( + self._client.topology_description.topology_type_name == "Sharded" + or (overloaded and self._client.options.enable_overload_retargeting) ): self._deprioritized_servers.append(self._server) + self._always_retryable = always_retryable + if overloaded: + delay = self._retry_policy.backoff(self._attempt_number) + if not self._retry_policy.should_retry(self._attempt_number, delay): + if exc_to_check.has_error_label("NoWritesPerformed") and self._last_error: + raise self._last_error from exc + else: + raise + time.sleep(delay) + def _is_not_eligible_for_retry(self) -> bool: """Checks if the exchange is not eligible for retry""" return not self._retryable or (self._is_retrying() and not self._multiple_retries) @@ -2899,7 +2992,7 @@ def _write(self) -> T: and conn.supports_sessions ) is_mongos = conn.is_mongos - if not sessions_supported: + if not self._always_retryable and not sessions_supported: # A retry is not possible because this server does # not support sessions raise the last error. self._check_last_error() @@ -2931,7 +3024,7 @@ def _read(self) -> T: conn, read_pref, ): - if self._retrying and not self._retryable: + if self._retrying and not self._retryable and not self._always_retryable: self._check_last_error() if self._retrying: _debug_log( diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 89d2080bc8..d33cb59a98 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -19,6 +19,8 @@ import contextlib import logging import os +import socket +import ssl import sys import time import weakref @@ -49,10 +51,12 @@ DocumentTooLarge, ExecutionTimeout, InvalidOperation, + NetworkTimeout, NotPrimaryError, OperationFailure, PyMongoError, WaitQueueTimeoutError, + _CertificateError, ) from pymongo.hello import Hello, HelloCompat from pymongo.helpers_shared import _get_timeout_details, format_timeout_details @@ -250,6 +254,7 @@ def _hello( cmd = self.hello_cmd() performing_handshake = not self.performed_handshake awaitable = False + cmd["backpressure"] = True if performing_handshake: self.performed_handshake = True cmd["client"] = self.opts.metadata @@ -750,8 +755,8 @@ def __init__( # Enforces: maxConnecting # Also used for: clearing the wait queue self._max_connecting_cond = _create_condition(self.lock) - self._max_connecting = self.opts.max_connecting self._pending = 0 + self._max_connecting = self.opts.max_connecting self._client_id = client_id if self.enabled_for_cmap: assert self.opts._event_listeners is not None @@ -982,6 +987,21 @@ def remove_stale_sockets(self, reference_generation: int) -> None: self.requests -= 1 self.size_cond.notify() + def _handle_connection_error(self, error: BaseException) -> None: + # Handle system overload condition for non-sdam pools. + # Look for errors of type AutoReconnect and add error labels if appropriate. + if self.is_sdam or type(error) not in (AutoReconnect, NetworkTimeout): + return + assert isinstance(error, AutoReconnect) # Appease type checker. + # If the original error was a DNS, certificate, or SSL error, ignore it. + if isinstance(error.__cause__, (_CertificateError, SSLErrors, socket.gaierror)): + # End of file errors are excluded, because the server may have disconnected + # during the handshake. + if not isinstance(error.__cause__, (ssl.SSLEOFError, ssl.SSLZeroReturnError)): + return + error._add_error_label("SystemOverloadedError") + error._add_error_label("RetryableError") + def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connection: """Connect to Mongo and return a new Connection. @@ -1033,10 +1053,10 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR), error=ConnectionClosedReason.ERROR, ) + self._handle_connection_error(error) if isinstance(error, (IOError, OSError, *SSLErrors)): details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) - raise conn = Connection(networking_interface, self, self.address, conn_id, self.is_sdam) # type: ignore[arg-type] @@ -1045,18 +1065,22 @@ def connect(self, handler: Optional[_MongoClientErrorHandler] = None) -> Connect self.active_contexts.discard(tmp_context) if tmp_context.cancelled: conn.cancel_context.cancel() + completed_hello = False try: if not self.is_sdam: conn.hello() + completed_hello = True self.is_writable = conn.is_writable if handler: handler.contribute_socket(conn, completed_handshake=False) conn.authenticate() # Catch KeyboardInterrupt, CancelledError, etc. and cleanup. - except BaseException: + except BaseException as e: with self.lock: self.active_contexts.discard(conn.cancel_context) + if not completed_hello: + self._handle_connection_error(e) conn.close_conn(ConnectionClosedReason.ERROR) raise @@ -1385,8 +1409,8 @@ def _perished(self, conn: Connection) -> bool: :class:`~pymongo.errors.AutoReconnect` exceptions on server hiccups, etc. We only check if the socket was closed by an external error if it has been > 1 second since the socket was checked into the - pool, to keep performance reasonable - we can't avoid AutoReconnects - completely anyway. + pool to keep performance reasonable - + we can't avoid AutoReconnects completely anyway. """ idle_time_seconds = conn.idle_time_seconds() # If socket is idle, open a new one. @@ -1397,8 +1421,9 @@ def _perished(self, conn: Connection) -> bool: conn.close_conn(ConnectionClosedReason.IDLE) return True - if self._check_interval_seconds is not None and ( - self._check_interval_seconds == 0 or idle_time_seconds > self._check_interval_seconds + check_interval_seconds = self._check_interval_seconds + if check_interval_seconds is not None and ( + check_interval_seconds == 0 or idle_time_seconds > check_interval_seconds ): if conn.conn_closed(): conn.close_conn(ConnectionClosedReason.ERROR) diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index 38e916b1e7..ec1615f0c6 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -911,7 +911,9 @@ def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None: # Clear the pool. server.reset(service_id) elif isinstance(error, ConnectionFailure): - if isinstance(error, WaitQueueTimeoutError): + if isinstance(error, WaitQueueTimeoutError) or ( + error.has_error_label("SystemOverloadedError") + ): return # "Client MUST replace the server's description with type Unknown # ... MUST NOT request an immediate check of the server." diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index 98596b33c0..ca150ca6df 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -652,6 +652,38 @@ async def test_detected_environment_warning(self, mock_get_hosts): with self.assertWarns(UserWarning): self.simple_client(multi_host) + async def test_max_adaptive_retries(self): + # Assert that max adaptive retries defaults to 2. + c = self.simple_client(connect=False) + self.assertEqual(c.options.max_adaptive_retries, 2) + + # Assert that max adaptive retries can be configured through connection or client options. + c = self.simple_client(connect=False, max_adaptive_retries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) + + c = self.simple_client(connect=False, maxAdaptiveRetries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) + + c = self.simple_client(host="mongodb://localhost/?maxAdaptiveRetries=10", connect=False) + self.assertEqual(c.options.max_adaptive_retries, 10) + + async def test_enable_overload_retargeting(self): + # Assert that overload retargeting defaults to false. + c = self.simple_client(connect=False) + self.assertFalse(c.options.enable_overload_retargeting) + + # Assert that overload retargeting can be enabled through connection or client options. + c = self.simple_client(connect=False, enable_overload_retargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client(connect=False, enableOverloadRetargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client( + host="mongodb://localhost/?enableOverloadRetargeting=true", connect=False + ) + self.assertTrue(c.options.enable_overload_retargeting) + class TestClient(AsyncIntegrationTest): def test_multiple_uris(self): diff --git a/test/asynchronous/test_client_backpressure.py b/test/asynchronous/test_client_backpressure.py new file mode 100644 index 0000000000..3e75ed9b0d --- /dev/null +++ b/test/asynchronous/test_client_backpressure.py @@ -0,0 +1,312 @@ +# Copyright 2025-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test Client Backpressure spec.""" +from __future__ import annotations + +import os +import pathlib +import sys +from time import perf_counter +from unittest.mock import patch + +from pymongo.common import MAX_ADAPTIVE_RETRIES + +sys.path[0:0] = [""] + +from test.asynchronous import ( + AsyncIntegrationTest, + async_client_context, + unittest, +) +from test.asynchronous.unified_format import generate_test_classes +from test.utils_shared import EventListener, OvertCommandListener + +from pymongo.errors import OperationFailure, PyMongoError + +_IS_SYNC = False + +# Mock a system overload error. +mock_overload_error = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find", "insert", "update"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, +} + + +def get_mock_overload_error(times: int): + error = mock_overload_error.copy() + error["mode"] = {"times": times} + return error + + +class TestBackpressure(AsyncIntegrationTest): + RUN_ON_LOAD_BALANCER = True + + @async_client_context.require_failCommand_appName + async def test_retry_overload_error_command(self): + await self.db.t.insert_one({"x": 1}) + + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + async with self.fail_point(fail_many): + await self.db.command("find", "t") + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + async with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + await self.db.command("find", "t") + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @async_client_context.require_failCommand_appName + async def test_retry_overload_error_find(self): + await self.db.t.insert_one({"x": 1}) + + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + async with self.fail_point(fail_many): + await self.db.t.find_one() + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + async with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + await self.db.t.find_one() + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @async_client_context.require_failCommand_appName + async def test_retry_overload_error_insert_one(self): + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + async with self.fail_point(fail_many): + await self.db.t.insert_one({"x": 1}) + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + async with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + await self.db.t.insert_one({"x": 1}) + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @async_client_context.require_failCommand_appName + async def test_retry_overload_error_update_many(self): + # Even though update_many is not a retryable write operation, it will + # still be retried via the "RetryableError" error label. + await self.db.t.insert_one({"x": 1}) + + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + async with self.fail_point(fail_many): + await self.db.t.update_many({}, {"$set": {"x": 2}}) + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + async with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + await self.db.t.update_many({}, {"$set": {"x": 2}}) + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @async_client_context.require_failCommand_appName + async def test_retry_overload_error_getMore(self): + coll = self.db.t + await coll.insert_many([{"x": 1} for _ in range(10)]) + + # Ensure command is retried on overload error. + fail_many = { + "configureFailPoint": "failCommand", + "mode": {"times": MAX_ADAPTIVE_RETRIES}, + "data": { + "failCommands": ["getMore"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + cursor = coll.find(batch_size=2) + await cursor.next() + async with self.fail_point(fail_many): + await cursor.to_list() + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = fail_many.copy() + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} + cursor = coll.find(batch_size=2) + await cursor.next() + async with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + await cursor.to_list() + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + +# Prose tests. +class AsyncTestClientBackpressure(AsyncIntegrationTest): + listener: EventListener + + @classmethod + def setUpClass(cls) -> None: + cls.listener = OvertCommandListener() + + @async_client_context.require_connection + async def asyncSetUp(self) -> None: + await super().asyncSetUp() + self.listener.reset() + self.app_name = self.__class__.__name__.lower() + self.client = await self.async_rs_or_single_client( + event_listeners=[self.listener], appName=self.app_name + ) + + @patch("random.random") + @async_client_context.require_failCommand_appName + async def test_01_operation_retry_uses_exponential_backoff(self, random_func): + # Drivers should test that retries do not occur immediately when a SystemOverloadedError is encountered. + + # 1. let `client` be a `MongoClient` + client = self.client + + # 2. let `collection` be a collection + collection = client.test.test + + # 3. Now, run transactions without backoff: + + # a. Configure the random number generator used for jitter to always return `0` -- this effectively disables backoff. + random_func.return_value = 0 + + # b. Configure the following failPoint: + fail_point = dict( + mode="alwaysOn", + data=dict( + failCommands=["insert"], + errorCode=2, + errorLabels=["SystemOverloadedError", "RetryableError"], + appName=self.app_name, + ), + ) + async with self.fail_point(fail_point): + # c. Execute the following command. Expect that the command errors. Measure the duration of the command execution. + start0 = perf_counter() + with self.assertRaises(OperationFailure): + await collection.insert_one({"a": 1}) + end0 = perf_counter() + + # d. Configure the random number generator used for jitter to always return `1`. + random_func.return_value = 1 + + # e. Execute step c again. + start1 = perf_counter() + with self.assertRaises(OperationFailure): + await collection.insert_one({"a": 1}) + end1 = perf_counter() + + # f. Compare the times between the two runs. + # The sum of 2 backoffs is 0.3 seconds. There is a 0.3-second window to account for potential variance between the two + # runs. + self.assertTrue(abs((end1 - start1) - (end0 - start0 + 0.3)) < 0.3) + + @async_client_context.require_failCommand_appName + async def test_03_overload_retries_limited(self): + # Drivers should test that overload errors are retried a maximum of two times. + + # 1. Let `client` be a `MongoClient`. + client = self.client + # 2. Let `coll` be a collection. + coll = client.pymongo_test.coll + + # 3. Configure the following failpoint: + failpoint = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + # 4. Perform a find operation with `coll` that fails. + async with self.fail_point(failpoint): + with self.assertRaises(PyMongoError) as error: + await coll.find_one({}) + + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + # 6. Assert that the total number of started commands is MAX_ADAPTIVE_RETRIES + 1. + self.assertEqual(len(self.listener.started_events), MAX_ADAPTIVE_RETRIES + 1) + + @async_client_context.require_failCommand_appName + async def test_04_overload_retries_limited_configured(self): + # Drivers should test that overload errors are retried a maximum of maxAdaptiveRetries times. + max_retries = 1 + + # 1. Let `client` be a `MongoClient` with `maxAdaptiveRetries=1` and command event monitoring enabled. + client = await self.async_single_client( + maxAdaptiveRetries=max_retries, event_listeners=[self.listener] + ) + # 2. Let `coll` be a collection. + coll = client.pymongo_test.coll + + # 3. Configure the following failpoint: + failpoint = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + # 4. Perform a find operation with `coll` that fails. + async with self.fail_point(failpoint): + with self.assertRaises(PyMongoError) as error: + await coll.find_one({}) + + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + # 6. Assert that the total number of started commands is max_retries + 1. + self.assertEqual(len(self.listener.started_events), max_retries + 1) + + +# Location of JSON test specifications. +if _IS_SYNC: + _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "client-backpressure") +else: + _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "client-backpressure") + +globals().update( + generate_test_classes( + _TEST_PATH, + module=__name__, + ) +) + +if __name__ == "__main__": + unittest.main() diff --git a/test/asynchronous/test_client_metadata.py b/test/asynchronous/test_client_metadata.py index 45c1bd1b3b..d4f887b1fc 100644 --- a/test/asynchronous/test_client_metadata.py +++ b/test/asynchronous/test_client_metadata.py @@ -219,6 +219,19 @@ async def test_duplicate_driver_name_no_op(self): # add same metadata again await self.check_metadata_added(client, "Framework", None, None) + async def test_handshake_documents_include_backpressure(self): + # Create a `MongoClient` that is configured to record all handshake documents sent to the server as a part of + # connection establishment. + client = await self.async_rs_or_single_client("mongodb://" + self.server.address_string) + + # Send a `ping` command to the server and verify that the command succeeds. This ensure that a connection is + # established on all topologies. Note: MockupDB only supports standalone servers. + await client.admin.command("ping") + + # Assert that for every handshake document intercepted: + # the document has a field `backpressure` whose value is `true`. + self.assertEqual(self.handshake_req["backpressure"], True) + if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_discovery_and_monitoring.py b/test/asynchronous/test_discovery_and_monitoring.py index 0bbf471d87..17a90db60f 100644 --- a/test/asynchronous/test_discovery_and_monitoring.py +++ b/test/asynchronous/test_discovery_and_monitoring.py @@ -25,8 +25,10 @@ from pathlib import Path from test.asynchronous.helpers import ConcurrentRunner from test.asynchronous.utils import flaky +from test.utils_shared import delay from pymongo.asynchronous.pool import AsyncConnection +from pymongo.errors import ConnectionFailure from pymongo.operations import _Op from pymongo.server_selectors import writable_server_selector @@ -70,7 +72,12 @@ ) from pymongo.hello import Hello, HelloCompat from pymongo.helpers_shared import _check_command_response, _check_write_command_response -from pymongo.monitoring import ServerHeartbeatFailedEvent, ServerHeartbeatStartedEvent +from pymongo.monitoring import ( + ConnectionCheckOutFailedEvent, + PoolClearedEvent, + ServerHeartbeatFailedEvent, + ServerHeartbeatStartedEvent, +) from pymongo.server_description import SERVER_TYPE, ServerDescription from pymongo.topology_description import TOPOLOGY_TYPE @@ -131,6 +138,9 @@ async def got_app_error(topology, app_error): raise AssertionError except (AutoReconnect, NotPrimaryError, OperationFailure) as e: if when == "beforeHandshakeCompletes": + # The pool would have added the SystemOverloadedError in this case. + if isinstance(e, AutoReconnect): + e._add_error_label("SystemOverloadedError") completed_handshake = False elif when == "afterHandshakeCompletes": completed_handshake = True @@ -439,6 +449,59 @@ async def mock_close(self, reason): AsyncConnection.close_conn = original_close +class TestPoolBackpressure(AsyncIntegrationTest): + @async_client_context.require_version_min(7, 0, 0) + async def test_connection_pool_is_not_cleared(self): + listener = CMAPListener() + + # Create a client that listens to CMAP events, with maxConnecting=100. + client = await self.async_rs_or_single_client(maxConnecting=100, event_listeners=[listener]) + + # Enable the ingress rate limiter. + await client.admin.command( + "setParameter", 1, ingressConnectionEstablishmentRateLimiterEnabled=True + ) + await client.admin.command("setParameter", 1, ingressConnectionEstablishmentRatePerSec=20) + await client.admin.command( + "setParameter", 1, ingressConnectionEstablishmentBurstCapacitySecs=1 + ) + await client.admin.command("setParameter", 1, ingressConnectionEstablishmentMaxQueueDepth=1) + + # Disable the ingress rate limiter on teardown. + # Sleep for 1 second before disabling to avoid the rate limiter. + async def teardown(): + await asyncio.sleep(1) + await client.admin.command( + "setParameter", 1, ingressConnectionEstablishmentRateLimiterEnabled=False + ) + + self.addAsyncCleanup(teardown) + + # Make sure the collection has at least one document. + await client.test.test.delete_many({}) + await client.test.test.insert_one({}) + + # Run a slow operation to tie up the connection. + async def target(): + try: + await client.test.test.find_one({"$where": delay(0.1)}) + except ConnectionFailure: + pass + + # Run 100 parallel operations that contend for connections. + tasks = [] + for _ in range(100): + tasks.append(ConcurrentRunner(target=target)) + for t in tasks: + await t.start() + for t in tasks: + await t.join() + + # Verify there were at least 10 connection checkout failed event but no pool cleared events. + self.assertGreater(len(listener.events_by_type(ConnectionCheckOutFailedEvent)), 10) + self.assertEqual(len(listener.events_by_type(PoolClearedEvent)), 0) + + class TestServerMonitoringMode(AsyncIntegrationTest): @async_client_context.require_no_load_balancer async def asyncSetUp(self): diff --git a/test/asynchronous/test_pooling.py b/test/asynchronous/test_pooling.py index 3193d9e3d5..9db9b5ab3a 100644 --- a/test/asynchronous/test_pooling.py +++ b/test/asynchronous/test_pooling.py @@ -513,6 +513,39 @@ async def test_connection_timeout_message(self): str(error.exception), ) + @async_client_context.require_failCommand_appName + async def test_pool_backpressure_preserves_existing_connections(self): + client = await self.async_rs_or_single_client() + coll = client.pymongo_test.t + pool = await async_get_pool(client) + await coll.insert_many([{"x": 1} for _ in range(10)]) + t = SocketGetter(self.c, pool) + await t.start() + while t.state != "connection": + await asyncio.sleep(0.1) + + assert not t.sock.conn_closed() + + # Mock a session establishment overload. + mock_connection_fail = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "closeConnection": True, + }, + } + + async with self.fail_point(mock_connection_fail): + await coll.find_one({}) + + # Make sure the existing socket was not affected. + assert not t.sock.conn_closed() + + # Cleanup + await t.release_conn() + await t.join() + await pool.close() + class TestPoolMaxSize(_TestPoolingBase): async def test_max_pool_size(self): diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 6adfaaae17..259cd9cff5 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -265,14 +265,17 @@ async def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @async_client_context.require_secondaries_count(1) @async_client_context.require_failCommand_fail_point @async_client_context.require_version_min(4, 4, 0) - async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available_and_overload_retargeting_is_enabled( self ): listener = OvertCommandListener() - # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, `enableOverloadRetargeting=True`, and command event monitoring enabled. client = await self.async_rs_or_single_client( - event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + enableOverloadRetargeting=True, ) # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. @@ -339,6 +342,47 @@ async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_o # 6. Assert that both events occurred the same server. assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + @async_client_context.require_replica_set + @async_client_context.require_secondaries_count(1) + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) + async def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same_replicaset_server_when_one_is_available_and_overload_retargeting_is_disabled( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = await self.async_rs_or_single_client( + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + await async_set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + await client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_retryable_writes.py b/test/asynchronous/test_retryable_writes.py index ddb1d39eb7..6e2072a2ad 100644 --- a/test/asynchronous/test_retryable_writes.py +++ b/test/asynchronous/test_retryable_writes.py @@ -43,14 +43,17 @@ from bson.int64 import Int64 from bson.raw_bson import RawBSONDocument from bson.son import SON +from pymongo import MongoClient from pymongo.errors import ( AutoReconnect, ConnectionFailure, - OperationFailure, + NotPrimaryError, + PyMongoError, ServerSelectionTimeoutError, WriteConcernError, ) from pymongo.monitoring import ( + CommandFailedEvent, CommandSucceededEvent, ConnectionCheckedOutEvent, ConnectionCheckOutFailedEvent, @@ -601,5 +604,186 @@ def raise_connection_err_select_server(*args, **kwargs): self.assertEqual(sent_txn_id, final_txn_id, msg) +class TestErrorPropagationAfterEncounteringMultipleErrors(AsyncIntegrationTest): + # Only run against replica sets as mongos does not propagate the NoWritesPerformed label to the drivers. + @async_client_context.require_replica_set + # Run against server versions 6.0 and above. + @async_client_context.require_version_min(6, 0) # type: ignore[untyped-decorator] + async def asyncSetUp(self) -> None: + await super().asyncSetUp() + self.setup_client = MongoClient(**async_client_context.default_client_options) + self.addCleanup(self.setup_client.close) + + # TODO: After PYTHON-4595 we can use async event handlers and remove this workaround. + def configure_fail_point_sync(self, command_args, off=False) -> None: + cmd = {"configureFailPoint": "failCommand"} + cmd.update(command_args) + if off: + cmd["mode"] = "off" + cmd.pop("data", None) + self.setup_client.admin.command(cmd) + + async def test_01_drivers_return_the_correct_error_when_receiving_only_errors_without_NoWritesPerformed( + self + ) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure a fail point with error code 91 (ShutdownInProgress) with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Via the command monitoring CommandFailedEvent, configure a fail point with error code 10107 (NotWritablePrimary). + command_args_inner = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 10107, + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the 10107 fail point command only if the the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(command_args_inner) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(command_args) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Attempt an insertOne operation on any record for any database and collection. + # Expect the insertOne to fail with a server error. + with self.assertRaises(NotPrimaryError) as exc: + await client.test.test.insert_one({}) + + # Assert that the error code of the server error is 10107. + assert exc.exception.errors["code"] == 10107 # type:ignore[call-overload] + + async def test_02_drivers_return_the_correct_error_when_receiving_only_errors_with_NoWritesPerformed( + self + ) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure a fail point with error code 91 (ShutdownInProgress) with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"], + "errorCode": 91, + }, + } + + # Via the command monitoring CommandFailedEvent, configure a fail point with error code `10107` (NotWritablePrimary) + # and a NoWritesPerformed label. + command_args_inner = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 10107, + "errorLabels": ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + if listener.failed_events: + return + # Configure the 10107 fail point command only if the the failed event is for the 91 error configured in step 2. + assert event.failure["code"] == 91 + self.configure_fail_point_sync(command_args_inner) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(command_args) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Attempt an insertOne operation on any record for any database and collection. + # Expect the insertOne to fail with a server error. + with self.assertRaises(NotPrimaryError) as exc: + await client.test.test.insert_one({}) + + # Assert that the error code of the server error is 91. + assert exc.exception.errors["code"] == 91 # type:ignore[call-overload] + + async def test_03_drivers_return_the_correct_error_when_receiving_some_errors_with_NoWritesPerformed_and_some_without_NoWritesPerformed( + self + ) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (NotWritablePrimary) and the `NoWritesPerformed`, `RetryableError` and `SystemOverloadedError` labels. + command_args_inner = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError` and + # `SystemOverloadedError` error labels but without the `NoWritesPerformed` error label. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorCode": 91, + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(command_args_inner) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(command_args) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Attempt an insertOne operation on any record for any database and collection. + # Expect the insertOne to fail with a server error. + with self.assertRaises(PyMongoError) as exc: + await client.test.test.insert_one({}) + + # Assert that the error code of the server error is 91. + assert exc.exception.errors["code"] == 91 + # Assert that the error does not contain the error label `NoWritesPerformed`. + assert "NoWritesPerformed" not in exc.exception.errors["errorLabels"] + + if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 4fc20fba3b..e17bfb14c0 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -16,9 +16,13 @@ from __future__ import annotations import asyncio +import random import sys +import time from io import BytesIO +from unittest.mock import patch +import pymongo from gridfs.asynchronous.grid_file import AsyncGridFS, AsyncGridFSBucket from pymongo.asynchronous.pool import PoolState from pymongo.server_selectors import writable_server_selector @@ -45,7 +49,9 @@ CollectionInvalid, ConfigurationError, ConnectionFailure, + ExecutionTimeout, InvalidOperation, + NetworkTimeout, OperationFailure, ) from pymongo.operations import IndexModel, InsertOne @@ -434,7 +440,7 @@ async def set_fail_point(self, command_args): await self.configure_fail_point(client, command_args) @async_client_context.require_transactions - async def test_callback_raises_custom_error(self): + async def test_1_callback_raises_custom_error(self): class _MyException(Exception): pass @@ -446,7 +452,7 @@ async def raise_error(_): await s.with_transaction(raise_error) @async_client_context.require_transactions - async def test_callback_returns_value(self): + async def test_2_callback_returns_value(self): async def callback(_): return "Foo" @@ -474,7 +480,7 @@ def callback(_): self.assertEqual(await s.with_transaction(callback), "Foo") @async_client_context.require_transactions - async def test_callback_not_retried_after_timeout(self): + async def test_3_1_callback_not_retried_after_timeout(self): listener = OvertCommandListener() client = await self.async_rs_client(event_listeners=[listener]) coll = client[self.db.name].test @@ -495,14 +501,16 @@ async def callback(session): listener.reset() async with client.start_session() as s: with PatchSessionTimeout(0): - with self.assertRaises(OperationFailure): + with self.assertRaises(NetworkTimeout) as context: await s.with_transaction(callback) self.assertEqual(listener.started_command_names(), ["insert", "abortTransaction"]) + # Assert that the timeout error has the same labels as the error it wraps. + self.assertTrue(context.exception.has_error_label("TransientTransactionError")) @async_client_context.require_test_commands @async_client_context.require_transactions - async def test_callback_not_retried_after_commit_timeout(self): + async def test_3_2_callback_not_retried_after_commit_timeout(self): listener = OvertCommandListener() client = await self.async_rs_client(event_listeners=[listener]) coll = client[self.db.name].test @@ -529,14 +537,16 @@ async def callback(session): async with client.start_session() as s: with PatchSessionTimeout(0): - with self.assertRaises(OperationFailure): + with self.assertRaises(NetworkTimeout) as context: await s.with_transaction(callback) self.assertEqual(listener.started_command_names(), ["insert", "commitTransaction"]) + # Assert that the timeout error has the same labels as the error it wraps. + self.assertTrue(context.exception.has_error_label("TransientTransactionError")) @async_client_context.require_test_commands @async_client_context.require_transactions - async def test_commit_not_retried_after_timeout(self): + async def test_3_3_commit_not_retried_after_timeout(self): listener = OvertCommandListener() client = await self.async_rs_client(event_listeners=[listener]) coll = client[self.db.name].test @@ -560,7 +570,7 @@ async def callback(session): async with client.start_session() as s: with PatchSessionTimeout(0): - with self.assertRaises(ConnectionFailure): + with self.assertRaises(NetworkTimeout) as context: await s.with_transaction(callback) # One insert for the callback and two commits (includes the automatic @@ -568,6 +578,40 @@ async def callback(session): self.assertEqual( listener.started_command_names(), ["insert", "commitTransaction", "commitTransaction"] ) + # Assert that the timeout error has the same labels as the error it wraps. + self.assertTrue(context.exception.has_error_label("UnknownTransactionCommitResult")) + + @async_client_context.require_transactions + async def test_callback_not_retried_after_csot_timeout(self): + listener = OvertCommandListener() + client = await self.async_rs_client(event_listeners=[listener]) + coll = client[self.db.name].test + + async def callback(session): + await coll.insert_one({}, session=session) + err: dict = { + "ok": 0, + "errmsg": "Transaction 7819 has been aborted.", + "code": 251, + "codeName": "NoSuchTransaction", + "errorLabels": ["TransientTransactionError"], + } + raise OperationFailure(err["errmsg"], err["code"], err) + + # Create the collection. + await coll.insert_one({}) + listener.reset() + async with client.start_session() as s: + with pymongo.timeout(1.0): + with self.assertRaises(ExecutionTimeout): + await s.with_transaction(callback) + + # At least two attempts: the original and one or more retries. + inserts = len([x for x in listener.started_command_names() if x == "insert"]) + aborts = len([x for x in listener.started_command_names() if x == "abortTransaction"]) + + self.assertGreaterEqual(inserts, 2) + self.assertGreaterEqual(aborts, 2) # Tested here because this supports Motor's convenient transactions API. @async_client_context.require_transactions @@ -606,6 +650,63 @@ async def callback(session): await s.with_transaction(callback) self.assertFalse(s.in_transaction) + @async_client_context.require_test_commands + @async_client_context.require_transactions + async def test_4_retry_backoff_is_enforced(self): + client = async_client_context.client + coll = client[self.db.name].test + end = start = no_backoff_time = 0 + + # Make random.random always return 0 (no backoff) + with patch.object(random, "random", return_value=0): + # set fail point to trigger transaction failure and trigger backoff + await self.set_fail_point( + { + "configureFailPoint": "failCommand", + "mode": {"times": 13}, + "data": { + "failCommands": ["commitTransaction"], + "errorCode": 251, + }, + } + ) + self.addAsyncCleanup( + self.set_fail_point, {"configureFailPoint": "failCommand", "mode": "off"} + ) + + async def callback(session): + await coll.insert_one({}, session=session) + + start = time.monotonic() + async with self.client.start_session() as s: + await s.with_transaction(callback) + end = time.monotonic() + no_backoff_time = end - start + + # Make random.random always return 1 (max backoff) + with patch.object(random, "random", return_value=1): + # set fail point to trigger transaction failure and trigger backoff + await self.set_fail_point( + { + "configureFailPoint": "failCommand", + "mode": { + "times": 13 + }, # sufficiently high enough such that the time effect of backoff is noticeable + "data": { + "failCommands": ["commitTransaction"], + "errorCode": 251, + }, + } + ) + self.addAsyncCleanup( + self.set_fail_point, {"configureFailPoint": "failCommand", "mode": "off"} + ) + start = time.monotonic() + async with self.client.start_session() as s: + await s.with_transaction(callback) + end = time.monotonic() + self.assertLess(abs(end - start - (no_backoff_time + 2.2)), 1) # sum of 13 backoffs is 2.2 + class TestOptionsInsideTransactionProse(AsyncTransactionsBase): @async_client_context.require_transactions diff --git a/test/client-backpressure/backpressure-connection-checkin.json b/test/client-backpressure/backpressure-connection-checkin.json new file mode 100644 index 0000000000..794951ad5f --- /dev/null +++ b/test/client-backpressure/backpressure-connection-checkin.json @@ -0,0 +1,111 @@ +{ + "description": "tests that connections are returned to the pool on retry attempts for overload errors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ] + } + }, + { + "client": { + "id": "fail_point_client", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "backpressure-connection-checkin" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "coll" + } + } + ], + "tests": [ + { + "description": "overload error retry attempts return connections to the pool", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + } + ] +} diff --git a/test/client-backpressure/backpressure-retry-loop.json b/test/client-backpressure/backpressure-retry-loop.json new file mode 100644 index 0000000000..a0b4877fac --- /dev/null +++ b/test/client-backpressure/backpressure-retry-loop.json @@ -0,0 +1,4553 @@ +{ + "description": "tests that operations respect overload backoff retry loop", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "client": { + "id": "internal_client", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "internal_db", + "client": "internal_client", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "retryable-writes-tests", + "database": "internal_db", + "collectionName": "coll" + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "coll" + } + }, + { + "client": { + "id": "client_retryReads_false", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ], + "uriOptions": { + "retryReads": false + } + } + }, + { + "database": { + "id": "database_retryReads_false", + "client": "client_retryReads_false", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection_retryReads_false", + "database": "database_retryReads_false", + "collectionName": "coll" + } + }, + { + "client": { + "id": "client_retryWrites_false", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ], + "uriOptions": { + "retryWrites": false + } + } + }, + { + "database": { + "id": "database_retryWrites_false", + "client": "client_retryWrites_false", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection_retryWrites_false", + "database": "database_retryWrites_false", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [] + } + ], + "_yamlAnchors": { + "bulWriteInsertNamespace": "retryable-writes-tests.coll" + }, + "tests": [ + { + "description": "client.listDatabases retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listDatabases", + "object": "client", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandSucceededEvent": { + "commandName": "listDatabases" + } + } + ] + } + ] + }, + { + "description": "client.listDatabases (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listDatabases", + "object": "client_retryReads_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + } + ] + } + ] + }, + { + "description": "client.listDatabaseNames retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listDatabaseNames", + "object": "client" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandSucceededEvent": { + "commandName": "listDatabases" + } + } + ] + } + ] + }, + { + "description": "client.listDatabaseNames (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listDatabaseNames", + "object": "client_retryReads_false", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + } + ] + } + ] + }, + { + "description": "client.createChangeStream retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "client", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "client.createChangeStream (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "client_retryReads_false", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "client.clientBulkWrite retries using operation loop", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandFailedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandFailedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "client.clientBulkWrite (write) does not retry if retryWrites=false", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client_retryWrites_false", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandFailedEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "database.aggregate read retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "database", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "database.aggregate (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "database_retryReads_false", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "database.listCollections retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listCollections", + "object": "database", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandSucceededEvent": { + "commandName": "listCollections" + } + } + ] + } + ] + }, + { + "description": "database.listCollections (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listCollections", + "object": "database_retryReads_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + } + ] + } + ] + }, + { + "description": "database.listCollectionNames retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listCollectionNames", + "object": "database", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandSucceededEvent": { + "commandName": "listCollections" + } + } + ] + } + ] + }, + { + "description": "database.listCollectionNames (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listCollectionNames", + "object": "database_retryReads_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + } + ] + } + ] + }, + { + "description": "database.runCommand retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandSucceededEvent": { + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "database.runCommand (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "ping" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "runCommand", + "object": "database_retryReads_false", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "database.runCommand (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "ping" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "runCommand", + "object": "database_retryWrites_false", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "database.createChangeStream retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "database", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "database.createChangeStream (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "database_retryReads_false", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.aggregate read retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.aggregate (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "collection_retryReads_false", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.countDocuments retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.countDocuments (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection_retryReads_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.estimatedDocumentCount retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "count" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandFailedEvent": { + "commandName": "count" + } + }, + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandFailedEvent": { + "commandName": "count" + } + }, + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandSucceededEvent": { + "commandName": "count" + } + } + ] + } + ] + }, + { + "description": "collection.estimatedDocumentCount (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection_retryReads_false", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandFailedEvent": { + "commandName": "count" + } + } + ] + } + ] + }, + { + "description": "collection.distinct retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "distinct", + "object": "collection", + "arguments": { + "fieldName": "x", + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandFailedEvent": { + "commandName": "distinct" + } + }, + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandFailedEvent": { + "commandName": "distinct" + } + }, + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandSucceededEvent": { + "commandName": "distinct" + } + } + ] + } + ] + }, + { + "description": "collection.distinct (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "distinct", + "object": "collection_retryReads_false", + "arguments": { + "fieldName": "x", + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandFailedEvent": { + "commandName": "distinct" + } + } + ] + } + ] + }, + { + "description": "collection.find retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "collection.find (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "find", + "object": "collection_retryReads_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "collection.findOne retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOne", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "collection.findOne (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOne", + "object": "collection_retryReads_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "collection.listIndexes retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listIndexes", + "object": "collection" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "listIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.listIndexes (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listIndexes", + "object": "collection_retryReads_false", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.listIndexNames retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listIndexNames", + "object": "collection" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "listIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.listIndexNames (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listIndexNames", + "object": "collection_retryReads_false", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.createChangeStream retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "collection", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.createChangeStream (read) does not retry if retryReads=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "collection_retryReads_false", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryReads_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.insertOne retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertOne (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "insertOne", + "object": "collection_retryWrites_false", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertMany retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertMany (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "insertMany", + "object": "collection_retryWrites_false", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.deleteOne retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandSucceededEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.deleteOne (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.deleteMany retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandSucceededEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.deleteMany (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.replaceOne retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.replaceOne (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateOne retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateOne (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "updateOne", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateMany retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateMany (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "updateMany", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndDelete retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndDelete (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndReplace retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndReplace (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndUpdate retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandSucceededEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndUpdate (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection_retryWrites_false", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.bulkWrite retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.bulkWrite (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection_retryWrites_false", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.createIndex retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "createIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createIndex", + "object": "collection", + "arguments": { + "keys": { + "x": 11 + }, + "name": "x_11" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "createIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.createIndex (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "createIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createIndex", + "object": "collection_retryWrites_false", + "arguments": { + "keys": { + "x": 11 + }, + "name": "x_11" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "createIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.dropIndex retries using operation loop", + "operations": [ + { + "name": "createIndex", + "object": "retryable-writes-tests", + "arguments": { + "keys": { + "x": 11 + }, + "name": "x_11" + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "dropIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "dropIndex", + "object": "collection", + "arguments": { + "name": "x_11" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "dropIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.dropIndex (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "createIndex", + "object": "retryable-writes-tests", + "arguments": { + "keys": { + "x": 11 + }, + "name": "x_11" + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "dropIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "dropIndex", + "object": "collection_retryWrites_false", + "arguments": { + "name": "x_11" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.dropIndexes retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "dropIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "dropIndexes", + "object": "collection" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "dropIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.dropIndexes (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "dropIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "dropIndexes", + "object": "collection_retryWrites_false", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.aggregate write retries using operation loop", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [ + { + "$out": "output" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.aggregate (write) does not retry if retryWrites=false", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "internal_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "collection_retryWrites_false", + "arguments": { + "pipeline": [ + { + "$out": "output" + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client_retryWrites_false", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + } + ] +} diff --git a/test/client-backpressure/backpressure-retry-max-attempts.json b/test/client-backpressure/backpressure-retry-max-attempts.json new file mode 100644 index 0000000000..de52572765 --- /dev/null +++ b/test/client-backpressure/backpressure-retry-max-attempts.json @@ -0,0 +1,2569 @@ +{ + "description": "tests that operations retry at most maxAttempts=2 times", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "client": { + "id": "fail_point_client", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "coll" + } + } + ], + "_yamlAnchors": { + "bulkWriteInsertNamespace": "retryable-writes-tests.coll" + }, + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "client.listDatabases retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "listDatabases" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listDatabases", + "object": "client", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + } + ] + } + ] + }, + { + "description": "client.listDatabaseNames retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "listDatabases" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listDatabaseNames", + "object": "client", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandStartedEvent": { + "commandName": "listDatabases" + } + }, + { + "commandFailedEvent": { + "commandName": "listDatabases" + } + } + ] + } + ] + }, + { + "description": "client.createChangeStream retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "client", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "client.clientBulkWrite retries at most maxAttempts=2 times", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandFailedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandFailedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandFailedEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "database.aggregate read retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "database", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "database.listCollections retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "listCollections" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listCollections", + "object": "database", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + } + ] + } + ] + }, + { + "description": "database.listCollectionNames retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "listCollections" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listCollectionNames", + "object": "database", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "commandName": "listCollections" + } + }, + { + "commandFailedEvent": { + "commandName": "listCollections" + } + } + ] + } + ] + }, + { + "description": "database.runCommand retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "ping" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "database.createChangeStream retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "database", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.aggregate read retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.countDocuments retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.estimatedDocumentCount retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "count" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandFailedEvent": { + "commandName": "count" + } + }, + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandFailedEvent": { + "commandName": "count" + } + }, + { + "commandStartedEvent": { + "commandName": "count" + } + }, + { + "commandFailedEvent": { + "commandName": "count" + } + } + ] + } + ] + }, + { + "description": "collection.distinct retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "distinct" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "distinct", + "object": "collection", + "arguments": { + "fieldName": "x", + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandFailedEvent": { + "commandName": "distinct" + } + }, + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandFailedEvent": { + "commandName": "distinct" + } + }, + { + "commandStartedEvent": { + "commandName": "distinct" + } + }, + { + "commandFailedEvent": { + "commandName": "distinct" + } + } + ] + } + ] + }, + { + "description": "collection.find retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "collection.findOne retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOne", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "collection.listIndexes retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "listIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listIndexes", + "object": "collection", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.listIndexNames retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "listIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "listIndexNames", + "object": "collection", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "listIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "listIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.createChangeStream retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createChangeStream", + "object": "collection", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "collection.insertOne retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.insertMany retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.deleteOne retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.deleteMany retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + }, + { + "commandStartedEvent": { + "commandName": "delete" + } + }, + { + "commandFailedEvent": { + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "collection.replaceOne retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateOne retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.updateMany retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndDelete retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndReplace retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "x": 22 + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.findOneAndUpdate retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": {}, + "update": { + "$set": { + "x": 22 + } + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify" + } + }, + { + "commandFailedEvent": { + "commandName": "findAndModify" + } + } + ] + } + ] + }, + { + "description": "collection.bulkWrite retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "collection.createIndex retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "createIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "createIndex", + "object": "collection", + "arguments": { + "keys": { + "x": 11 + }, + "name": "x_11" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "createIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "createIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.dropIndex retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "dropIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "dropIndex", + "object": "collection", + "arguments": { + "name": "x_11" + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.dropIndexes retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "dropIndexes" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "dropIndexes", + "object": "collection", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandStartedEvent": { + "commandName": "dropIndexes" + } + }, + { + "commandFailedEvent": { + "commandName": "dropIndexes" + } + } + ] + } + ] + }, + { + "description": "collection.aggregate write retries at most maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "fail_point_client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "aggregate" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [ + { + "$out": "output" + } + ] + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandFailedEvent": { + "commandName": "aggregate" + } + } + ] + } + ] + } + ] +} diff --git a/test/client-backpressure/getMore-retried.json b/test/client-backpressure/getMore-retried.json new file mode 100644 index 0000000000..d7607d694b --- /dev/null +++ b/test/client-backpressure/getMore-retried.json @@ -0,0 +1,253 @@ +{ + "description": "getMore-retried-backpressure", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandFailedEvent", + "commandSucceededEvent" + ] + } + }, + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "db", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "coll", + "database": "db", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "default", + "collectionName": "default", + "documents": [ + { + "a": 1 + }, + { + "a": 2 + }, + { + "a": 3 + } + ] + } + ], + "tests": [ + { + "description": "getMores are retried", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "find", + "object": "coll", + "arguments": { + "batchSize": 2, + "filter": {}, + "sort": { + "a": 1 + } + }, + "expectResult": [ + { + "a": 1 + }, + { + "a": 2 + }, + { + "a": 3 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore" + } + }, + { + "commandSucceededEvent": { + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "getMores are retried maxAttempts=2 times", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "getMore" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 2 + } + } + } + }, + { + "name": "find", + "arguments": { + "batchSize": 2, + "filter": {} + }, + "object": "coll", + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "commandName": "killCursors" + } + }, + { + "commandSucceededEvent": { + "commandName": "killCursors" + } + } + ] + } + ] + } + ] +} diff --git a/test/connection_monitoring/pool-create-min-size-error.json b/test/connection_monitoring/pool-create-min-size-error.json index 5c8ad02dbd..4334ce2571 100644 --- a/test/connection_monitoring/pool-create-min-size-error.json +++ b/test/connection_monitoring/pool-create-min-size-error.json @@ -9,9 +9,7 @@ ], "failPoint": { "configureFailPoint": "failCommand", - "mode": { - "times": 50 - }, + "mode": "alwaysOn", "data": { "failCommands": [ "isMaster", diff --git a/test/discovery_and_monitoring/errors/error_handling_handshake.json b/test/discovery_and_monitoring/errors/error_handling_handshake.json index 65f8ad0a33..c60ee453dd 100644 --- a/test/discovery_and_monitoring/errors/error_handling_handshake.json +++ b/test/discovery_and_monitoring/errors/error_handling_handshake.json @@ -97,14 +97,22 @@ "outcome": { "servers": { "a:27017": { - "type": "Unknown", - "topologyVersion": null, + "type": "RSPrimary", + "setName": "rs", + "topologyVersion": { + "processId": { + "$oid": "000000000000000000000001" + }, + "counter": { + "$numberLong": "1" + } + }, "pool": { - "generation": 1 + "generation": 0 } } }, - "topologyType": "ReplicaSetNoPrimary", + "topologyType": "ReplicaSetWithPrimary", "logicalSessionTimeoutMinutes": null, "setName": "rs" } diff --git a/test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json b/test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json new file mode 100644 index 0000000000..ccaea8d135 --- /dev/null +++ b/test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json @@ -0,0 +1,142 @@ +{ + "description": "backpressure-network-error-fail-replicaset", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "backpressure-network-error-fail", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "apply backpressure on network connection errors during connection establishment", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "serverDescriptionChangedEvent", + "poolClearedEvent" + ], + "uriOptions": { + "retryWrites": false, + "heartbeatFrequencyMS": 1000000, + "serverMonitoringMode": "poll", + "appname": "backpressureNetworkErrorFailTest" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "backpressure-network-error-fail" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": { + "newDescription": { + "type": "RSPrimary" + } + } + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "appName": "backpressureNetworkErrorFailTest", + "closeConnection": true + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "SystemOverloadedError", + "RetryableError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [] + } + ] + } + ] +} diff --git a/test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json b/test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json new file mode 100644 index 0000000000..c1ff67c732 --- /dev/null +++ b/test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json @@ -0,0 +1,142 @@ +{ + "description": "backpressure-network-error-fail-single", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "backpressure-network-error-fail", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "apply backpressure on network connection errors during connection establishment", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "serverDescriptionChangedEvent", + "poolClearedEvent" + ], + "uriOptions": { + "retryWrites": false, + "heartbeatFrequencyMS": 1000000, + "serverMonitoringMode": "poll", + "appname": "backpressureNetworkErrorFailTest" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "backpressure-network-error-fail" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": { + "newDescription": { + "type": "Standalone" + } + } + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "appName": "backpressureNetworkErrorFailTest", + "closeConnection": true + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "SystemOverloadedError", + "RetryableError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [] + } + ] + } + ] +} diff --git a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json new file mode 100644 index 0000000000..35b088f422 --- /dev/null +++ b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json @@ -0,0 +1,145 @@ +{ + "description": "backpressure-network-timeout-error-replicaset", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "backpressure-network-timeout-error", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "apply backpressure on network timeout error during connection establishment", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "serverDescriptionChangedEvent", + "poolClearedEvent" + ], + "uriOptions": { + "retryWrites": false, + "heartbeatFrequencyMS": 1000000, + "appname": "backpressureNetworkTimeoutErrorTest", + "serverMonitoringMode": "poll", + "connectTimeoutMS": 250, + "socketTimeoutMS": 250 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "backpressure-network-timeout-error" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": { + "newDescription": { + "type": "RSPrimary" + } + } + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "blockConnection": true, + "blockTimeMS": 500, + "appName": "backpressureNetworkTimeoutErrorTest" + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "SystemOverloadedError", + "RetryableError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [] + } + ] + } + ] +} diff --git a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json new file mode 100644 index 0000000000..54b11d4d5b --- /dev/null +++ b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json @@ -0,0 +1,145 @@ +{ + "description": "backpressure-network-timeout-error-single", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "backpressure-network-timeout-error", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "apply backpressure on network timeout error during connection establishment", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "serverDescriptionChangedEvent", + "poolClearedEvent" + ], + "uriOptions": { + "retryWrites": false, + "heartbeatFrequencyMS": 1000000, + "appname": "backpressureNetworkTimeoutErrorTest", + "serverMonitoringMode": "poll", + "connectTimeoutMS": 250, + "socketTimeoutMS": 250 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "backpressure-network-timeout-error" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": { + "newDescription": { + "type": "Standalone" + } + } + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "blockConnection": true, + "blockTimeMS": 500, + "appName": "backpressureNetworkTimeoutErrorTest" + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "SystemOverloadedError", + "RetryableError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [] + } + ] + } + ] +} diff --git a/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json new file mode 100644 index 0000000000..f0597124b7 --- /dev/null +++ b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json @@ -0,0 +1,106 @@ +{ + "description": "backpressure-server-description-unchanged-on-min-pool-size-population-error", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "tests": [ + { + "description": "the server description is not changed on handshake error during minPoolSize population", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "skip": 1 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "authErrorTest", + "closeConnection": true + } + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "serverDescriptionChangedEvent", + "connectionClosedEvent" + ], + "uriOptions": { + "appname": "authErrorTest", + "minPoolSize": 5, + "maxConnecting": 1, + "serverMonitoringMode": "poll", + "heartbeatFrequencyMS": 1000000 + } + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionClosedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "events": [ + { + "serverDescriptionChangedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/test/test_client.py b/test/test_client.py index 2bb74e8dc0..75d585fdad 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -645,6 +645,38 @@ def test_detected_environment_warning(self, mock_get_hosts): with self.assertWarns(UserWarning): self.simple_client(multi_host) + def test_max_adaptive_retries(self): + # Assert that max adaptive retries defaults to 2. + c = self.simple_client(connect=False) + self.assertEqual(c.options.max_adaptive_retries, 2) + + # Assert that max adaptive retries can be configured through connection or client options. + c = self.simple_client(connect=False, max_adaptive_retries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) + + c = self.simple_client(connect=False, maxAdaptiveRetries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) + + c = self.simple_client(host="mongodb://localhost/?maxAdaptiveRetries=10", connect=False) + self.assertEqual(c.options.max_adaptive_retries, 10) + + def test_enable_overload_retargeting(self): + # Assert that overload retargeting defaults to false. + c = self.simple_client(connect=False) + self.assertFalse(c.options.enable_overload_retargeting) + + # Assert that overload retargeting can be enabled through connection or client options. + c = self.simple_client(connect=False, enable_overload_retargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client(connect=False, enableOverloadRetargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client( + host="mongodb://localhost/?enableOverloadRetargeting=true", connect=False + ) + self.assertTrue(c.options.enable_overload_retargeting) + class TestClient(IntegrationTest): def test_multiple_uris(self): diff --git a/test/test_client_backpressure.py b/test/test_client_backpressure.py new file mode 100644 index 0000000000..61334a1218 --- /dev/null +++ b/test/test_client_backpressure.py @@ -0,0 +1,310 @@ +# Copyright 2025-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test Client Backpressure spec.""" +from __future__ import annotations + +import os +import pathlib +import sys +from time import perf_counter +from unittest.mock import patch + +from pymongo.common import MAX_ADAPTIVE_RETRIES + +sys.path[0:0] = [""] + +from test import ( + IntegrationTest, + client_context, + unittest, +) +from test.unified_format import generate_test_classes +from test.utils_shared import EventListener, OvertCommandListener + +from pymongo.errors import OperationFailure, PyMongoError + +_IS_SYNC = True + +# Mock a system overload error. +mock_overload_error = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find", "insert", "update"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, +} + + +def get_mock_overload_error(times: int): + error = mock_overload_error.copy() + error["mode"] = {"times": times} + return error + + +class TestBackpressure(IntegrationTest): + RUN_ON_LOAD_BALANCER = True + + @client_context.require_failCommand_appName + def test_retry_overload_error_command(self): + self.db.t.insert_one({"x": 1}) + + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + with self.fail_point(fail_many): + self.db.command("find", "t") + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + self.db.command("find", "t") + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @client_context.require_failCommand_appName + def test_retry_overload_error_find(self): + self.db.t.insert_one({"x": 1}) + + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + with self.fail_point(fail_many): + self.db.t.find_one() + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + self.db.t.find_one() + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @client_context.require_failCommand_appName + def test_retry_overload_error_insert_one(self): + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + with self.fail_point(fail_many): + self.db.t.insert_one({"x": 1}) + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + self.db.t.insert_one({"x": 1}) + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @client_context.require_failCommand_appName + def test_retry_overload_error_update_many(self): + # Even though update_many is not a retryable write operation, it will + # still be retried via the "RetryableError" error label. + self.db.t.insert_one({"x": 1}) + + # Ensure command is retried on overload error. + fail_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES) + with self.fail_point(fail_many): + self.db.t.update_many({}, {"$set": {"x": 2}}) + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = get_mock_overload_error(MAX_ADAPTIVE_RETRIES + 1) + with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + self.db.t.update_many({}, {"$set": {"x": 2}}) + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + @client_context.require_failCommand_appName + def test_retry_overload_error_getMore(self): + coll = self.db.t + coll.insert_many([{"x": 1} for _ in range(10)]) + + # Ensure command is retried on overload error. + fail_many = { + "configureFailPoint": "failCommand", + "mode": {"times": MAX_ADAPTIVE_RETRIES}, + "data": { + "failCommands": ["getMore"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + cursor = coll.find(batch_size=2) + cursor.next() + with self.fail_point(fail_many): + cursor.to_list() + + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. + fail_too_many = fail_many.copy() + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} + cursor = coll.find(batch_size=2) + cursor.next() + with self.fail_point(fail_too_many): + with self.assertRaises(PyMongoError) as error: + cursor.to_list() + + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + +# Prose tests. +class TestClientBackpressure(IntegrationTest): + listener: EventListener + + @classmethod + def setUpClass(cls) -> None: + cls.listener = OvertCommandListener() + + @client_context.require_connection + def setUp(self) -> None: + super().setUp() + self.listener.reset() + self.app_name = self.__class__.__name__.lower() + self.client = self.rs_or_single_client( + event_listeners=[self.listener], appName=self.app_name + ) + + @patch("random.random") + @client_context.require_failCommand_appName + def test_01_operation_retry_uses_exponential_backoff(self, random_func): + # Drivers should test that retries do not occur immediately when a SystemOverloadedError is encountered. + + # 1. let `client` be a `MongoClient` + client = self.client + + # 2. let `collection` be a collection + collection = client.test.test + + # 3. Now, run transactions without backoff: + + # a. Configure the random number generator used for jitter to always return `0` -- this effectively disables backoff. + random_func.return_value = 0 + + # b. Configure the following failPoint: + fail_point = dict( + mode="alwaysOn", + data=dict( + failCommands=["insert"], + errorCode=2, + errorLabels=["SystemOverloadedError", "RetryableError"], + appName=self.app_name, + ), + ) + with self.fail_point(fail_point): + # c. Execute the following command. Expect that the command errors. Measure the duration of the command execution. + start0 = perf_counter() + with self.assertRaises(OperationFailure): + collection.insert_one({"a": 1}) + end0 = perf_counter() + + # d. Configure the random number generator used for jitter to always return `1`. + random_func.return_value = 1 + + # e. Execute step c again. + start1 = perf_counter() + with self.assertRaises(OperationFailure): + collection.insert_one({"a": 1}) + end1 = perf_counter() + + # f. Compare the times between the two runs. + # The sum of 2 backoffs is 0.3 seconds. There is a 0.3-second window to account for potential variance between the two + # runs. + self.assertTrue(abs((end1 - start1) - (end0 - start0 + 0.3)) < 0.3) + + @client_context.require_failCommand_appName + def test_03_overload_retries_limited(self): + # Drivers should test that overload errors are retried a maximum of two times. + + # 1. Let `client` be a `MongoClient`. + client = self.client + # 2. Let `coll` be a collection. + coll = client.pymongo_test.coll + + # 3. Configure the following failpoint: + failpoint = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + # 4. Perform a find operation with `coll` that fails. + with self.fail_point(failpoint): + with self.assertRaises(PyMongoError) as error: + coll.find_one({}) + + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + # 6. Assert that the total number of started commands is MAX_ADAPTIVE_RETRIES + 1. + self.assertEqual(len(self.listener.started_events), MAX_ADAPTIVE_RETRIES + 1) + + @client_context.require_failCommand_appName + def test_04_overload_retries_limited_configured(self): + # Drivers should test that overload errors are retried a maximum of maxAdaptiveRetries times. + max_retries = 1 + + # 1. Let `client` be a `MongoClient` with `maxAdaptiveRetries=1` and command event monitoring enabled. + client = self.single_client(maxAdaptiveRetries=max_retries, event_listeners=[self.listener]) + # 2. Let `coll` be a collection. + coll = client.pymongo_test.coll + + # 3. Configure the following failpoint: + failpoint = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 462, # IngressRequestRateLimitExceeded + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + # 4. Perform a find operation with `coll` that fails. + with self.fail_point(failpoint): + with self.assertRaises(PyMongoError) as error: + coll.find_one({}) + + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. + self.assertIn("RetryableError", str(error.exception)) + self.assertIn("SystemOverloadedError", str(error.exception)) + + # 6. Assert that the total number of started commands is max_retries + 1. + self.assertEqual(len(self.listener.started_events), max_retries + 1) + + +# Location of JSON test specifications. +if _IS_SYNC: + _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "client-backpressure") +else: + _TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent.parent, "client-backpressure") + +globals().update( + generate_test_classes( + _TEST_PATH, + module=__name__, + ) +) + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_client_metadata.py b/test/test_client_metadata.py index 5f103f739a..8cdb728ea2 100644 --- a/test/test_client_metadata.py +++ b/test/test_client_metadata.py @@ -219,6 +219,19 @@ def test_duplicate_driver_name_no_op(self): # add same metadata again self.check_metadata_added(client, "Framework", None, None) + def test_handshake_documents_include_backpressure(self): + # Create a `MongoClient` that is configured to record all handshake documents sent to the server as a part of + # connection establishment. + client = self.rs_or_single_client("mongodb://" + self.server.address_string) + + # Send a `ping` command to the server and verify that the command succeeds. This ensure that a connection is + # established on all topologies. Note: MockupDB only supports standalone servers. + client.admin.command("ping") + + # Assert that for every handshake document intercepted: + # the document has a field `backpressure` whose value is `true`. + self.assertEqual(self.handshake_req["backpressure"], True) + if __name__ == "__main__": unittest.main() diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index 8375d63e97..7fb6f312c5 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -25,7 +25,9 @@ from pathlib import Path from test.helpers import ConcurrentRunner from test.utils import flaky +from test.utils_shared import delay +from pymongo.errors import ConnectionFailure from pymongo.operations import _Op from pymongo.server_selectors import writable_server_selector from pymongo.synchronous.pool import Connection @@ -67,7 +69,12 @@ ) from pymongo.hello import Hello, HelloCompat from pymongo.helpers_shared import _check_command_response, _check_write_command_response -from pymongo.monitoring import ServerHeartbeatFailedEvent, ServerHeartbeatStartedEvent +from pymongo.monitoring import ( + ConnectionCheckOutFailedEvent, + PoolClearedEvent, + ServerHeartbeatFailedEvent, + ServerHeartbeatStartedEvent, +) from pymongo.server_description import SERVER_TYPE, ServerDescription from pymongo.synchronous.settings import TopologySettings from pymongo.synchronous.topology import Topology, _ErrorContext @@ -131,6 +138,9 @@ def got_app_error(topology, app_error): raise AssertionError except (AutoReconnect, NotPrimaryError, OperationFailure) as e: if when == "beforeHandshakeCompletes": + # The pool would have added the SystemOverloadedError in this case. + if isinstance(e, AutoReconnect): + e._add_error_label("SystemOverloadedError") completed_handshake = False elif when == "afterHandshakeCompletes": completed_handshake = True @@ -437,6 +447,57 @@ def mock_close(self, reason): Connection.close_conn = original_close +class TestPoolBackpressure(IntegrationTest): + @client_context.require_version_min(7, 0, 0) + def test_connection_pool_is_not_cleared(self): + listener = CMAPListener() + + # Create a client that listens to CMAP events, with maxConnecting=100. + client = self.rs_or_single_client(maxConnecting=100, event_listeners=[listener]) + + # Enable the ingress rate limiter. + client.admin.command( + "setParameter", 1, ingressConnectionEstablishmentRateLimiterEnabled=True + ) + client.admin.command("setParameter", 1, ingressConnectionEstablishmentRatePerSec=20) + client.admin.command("setParameter", 1, ingressConnectionEstablishmentBurstCapacitySecs=1) + client.admin.command("setParameter", 1, ingressConnectionEstablishmentMaxQueueDepth=1) + + # Disable the ingress rate limiter on teardown. + # Sleep for 1 second before disabling to avoid the rate limiter. + def teardown(): + time.sleep(1) + client.admin.command( + "setParameter", 1, ingressConnectionEstablishmentRateLimiterEnabled=False + ) + + self.addCleanup(teardown) + + # Make sure the collection has at least one document. + client.test.test.delete_many({}) + client.test.test.insert_one({}) + + # Run a slow operation to tie up the connection. + def target(): + try: + client.test.test.find_one({"$where": delay(0.1)}) + except ConnectionFailure: + pass + + # Run 100 parallel operations that contend for connections. + tasks = [] + for _ in range(100): + tasks.append(ConcurrentRunner(target=target)) + for t in tasks: + t.start() + for t in tasks: + t.join() + + # Verify there were at least 10 connection checkout failed event but no pool cleared events. + self.assertGreater(len(listener.events_by_type(ConnectionCheckOutFailedEvent)), 10) + self.assertEqual(len(listener.events_by_type(PoolClearedEvent)), 0) + + class TestServerMonitoringMode(IntegrationTest): @client_context.require_no_load_balancer def setUp(self): diff --git a/test/test_pooling.py b/test/test_pooling.py index cb5b206996..95558d00d5 100644 --- a/test/test_pooling.py +++ b/test/test_pooling.py @@ -511,6 +511,39 @@ def test_connection_timeout_message(self): str(error.exception), ) + @client_context.require_failCommand_appName + def test_pool_backpressure_preserves_existing_connections(self): + client = self.rs_or_single_client() + coll = client.pymongo_test.t + pool = get_pool(client) + coll.insert_many([{"x": 1} for _ in range(10)]) + t = SocketGetter(self.c, pool) + t.start() + while t.state != "connection": + time.sleep(0.1) + + assert not t.sock.conn_closed() + + # Mock a session establishment overload. + mock_connection_fail = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "closeConnection": True, + }, + } + + with self.fail_point(mock_connection_fail): + coll.find_one({}) + + # Make sure the existing socket was not affected. + assert not t.sock.conn_closed() + + # Cleanup + t.release_conn() + t.join() + pool.close() + class TestPoolMaxSize(_TestPoolingBase): def test_max_pool_size(self): diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index 18cd669f1c..9e6aac821c 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -263,14 +263,17 @@ def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @client_context.require_secondaries_count(1) @client_context.require_failCommand_fail_point @client_context.require_version_min(4, 4, 0) - def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available_and_overload_retargeting_is_enabled( self ): listener = OvertCommandListener() - # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, `enableOverloadRetargeting=True`, and command event monitoring enabled. client = self.rs_or_single_client( - event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + enableOverloadRetargeting=True, ) # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. @@ -337,6 +340,47 @@ def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_ # 6. Assert that both events occurred the same server. assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + @client_context.require_replica_set + @client_context.require_secondaries_count(1) + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) + def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same_replicaset_server_when_one_is_available_and_overload_retargeting_is_disabled( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = self.rs_or_single_client( + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() diff --git a/test/test_retryable_writes.py b/test/test_retryable_writes.py index a74a3e8030..5509083162 100644 --- a/test/test_retryable_writes.py +++ b/test/test_retryable_writes.py @@ -43,14 +43,17 @@ from bson.int64 import Int64 from bson.raw_bson import RawBSONDocument from bson.son import SON +from pymongo import MongoClient from pymongo.errors import ( AutoReconnect, ConnectionFailure, - OperationFailure, + NotPrimaryError, + PyMongoError, ServerSelectionTimeoutError, WriteConcernError, ) from pymongo.monitoring import ( + CommandFailedEvent, CommandSucceededEvent, ConnectionCheckedOutEvent, ConnectionCheckOutFailedEvent, @@ -597,5 +600,186 @@ def raise_connection_err_select_server(*args, **kwargs): self.assertEqual(sent_txn_id, final_txn_id, msg) +class TestErrorPropagationAfterEncounteringMultipleErrors(IntegrationTest): + # Only run against replica sets as mongos does not propagate the NoWritesPerformed label to the drivers. + @client_context.require_replica_set + # Run against server versions 6.0 and above. + @client_context.require_version_min(6, 0) # type: ignore[untyped-decorator] + def setUp(self) -> None: + super().setUp() + self.setup_client = MongoClient(**client_context.default_client_options) + self.addCleanup(self.setup_client.close) + + # TODO: After PYTHON-4595 we can use async event handlers and remove this workaround. + def configure_fail_point_sync(self, command_args, off=False) -> None: + cmd = {"configureFailPoint": "failCommand"} + cmd.update(command_args) + if off: + cmd["mode"] = "off" + cmd.pop("data", None) + self.setup_client.admin.command(cmd) + + def test_01_drivers_return_the_correct_error_when_receiving_only_errors_without_NoWritesPerformed( + self + ) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure a fail point with error code 91 (ShutdownInProgress) with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Via the command monitoring CommandFailedEvent, configure a fail point with error code 10107 (NotWritablePrimary). + command_args_inner = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 10107, + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the 10107 fail point command only if the the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(command_args_inner) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(command_args) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Attempt an insertOne operation on any record for any database and collection. + # Expect the insertOne to fail with a server error. + with self.assertRaises(NotPrimaryError) as exc: + client.test.test.insert_one({}) + + # Assert that the error code of the server error is 10107. + assert exc.exception.errors["code"] == 10107 # type:ignore[call-overload] + + def test_02_drivers_return_the_correct_error_when_receiving_only_errors_with_NoWritesPerformed( + self + ) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure a fail point with error code 91 (ShutdownInProgress) with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"], + "errorCode": 91, + }, + } + + # Via the command monitoring CommandFailedEvent, configure a fail point with error code `10107` (NotWritablePrimary) + # and a NoWritesPerformed label. + command_args_inner = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 10107, + "errorLabels": ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + if listener.failed_events: + return + # Configure the 10107 fail point command only if the the failed event is for the 91 error configured in step 2. + assert event.failure["code"] == 91 + self.configure_fail_point_sync(command_args_inner) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(command_args) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Attempt an insertOne operation on any record for any database and collection. + # Expect the insertOne to fail with a server error. + with self.assertRaises(NotPrimaryError) as exc: + client.test.test.insert_one({}) + + # Assert that the error code of the server error is 91. + assert exc.exception.errors["code"] == 91 # type:ignore[call-overload] + + def test_03_drivers_return_the_correct_error_when_receiving_some_errors_with_NoWritesPerformed_and_some_without_NoWritesPerformed( + self + ) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (NotWritablePrimary) and the `NoWritesPerformed`, `RetryableError` and `SystemOverloadedError` labels. + command_args_inner = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError", "NoWritesPerformed"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError` and + # `SystemOverloadedError` error labels but without the `NoWritesPerformed` error label. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorCode": 91, + "errorLabels": ["RetryableError", "SystemOverloadedError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(command_args_inner) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(command_args) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Attempt an insertOne operation on any record for any database and collection. + # Expect the insertOne to fail with a server error. + with self.assertRaises(PyMongoError) as exc: + client.test.test.insert_one({}) + + # Assert that the error code of the server error is 91. + assert exc.exception.errors["code"] == 91 + # Assert that the error does not contain the error label `NoWritesPerformed`. + assert "NoWritesPerformed" not in exc.exception.errors["errorLabels"] + + if __name__ == "__main__": unittest.main() diff --git a/test/test_transactions.py b/test/test_transactions.py index 01b7ba1553..609105ec21 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -16,9 +16,13 @@ from __future__ import annotations import asyncio +import random import sys +import time from io import BytesIO +from unittest.mock import patch +import pymongo from gridfs.synchronous.grid_file import GridFS, GridFSBucket from pymongo.server_selectors import writable_server_selector from pymongo.synchronous.pool import PoolState @@ -40,7 +44,9 @@ CollectionInvalid, ConfigurationError, ConnectionFailure, + ExecutionTimeout, InvalidOperation, + NetworkTimeout, OperationFailure, ) from pymongo.operations import IndexModel, InsertOne @@ -426,7 +432,7 @@ def set_fail_point(self, command_args): self.configure_fail_point(client, command_args) @client_context.require_transactions - def test_callback_raises_custom_error(self): + def test_1_callback_raises_custom_error(self): class _MyException(Exception): pass @@ -438,7 +444,7 @@ def raise_error(_): s.with_transaction(raise_error) @client_context.require_transactions - def test_callback_returns_value(self): + def test_2_callback_returns_value(self): def callback(_): return "Foo" @@ -466,7 +472,7 @@ def callback(_): self.assertEqual(s.with_transaction(callback), "Foo") @client_context.require_transactions - def test_callback_not_retried_after_timeout(self): + def test_3_1_callback_not_retried_after_timeout(self): listener = OvertCommandListener() client = self.rs_client(event_listeners=[listener]) coll = client[self.db.name].test @@ -487,14 +493,16 @@ def callback(session): listener.reset() with client.start_session() as s: with PatchSessionTimeout(0): - with self.assertRaises(OperationFailure): + with self.assertRaises(NetworkTimeout) as context: s.with_transaction(callback) self.assertEqual(listener.started_command_names(), ["insert", "abortTransaction"]) + # Assert that the timeout error has the same labels as the error it wraps. + self.assertTrue(context.exception.has_error_label("TransientTransactionError")) @client_context.require_test_commands @client_context.require_transactions - def test_callback_not_retried_after_commit_timeout(self): + def test_3_2_callback_not_retried_after_commit_timeout(self): listener = OvertCommandListener() client = self.rs_client(event_listeners=[listener]) coll = client[self.db.name].test @@ -519,14 +527,16 @@ def callback(session): with client.start_session() as s: with PatchSessionTimeout(0): - with self.assertRaises(OperationFailure): + with self.assertRaises(NetworkTimeout) as context: s.with_transaction(callback) self.assertEqual(listener.started_command_names(), ["insert", "commitTransaction"]) + # Assert that the timeout error has the same labels as the error it wraps. + self.assertTrue(context.exception.has_error_label("TransientTransactionError")) @client_context.require_test_commands @client_context.require_transactions - def test_commit_not_retried_after_timeout(self): + def test_3_3_commit_not_retried_after_timeout(self): listener = OvertCommandListener() client = self.rs_client(event_listeners=[listener]) coll = client[self.db.name].test @@ -548,7 +558,7 @@ def callback(session): with client.start_session() as s: with PatchSessionTimeout(0): - with self.assertRaises(ConnectionFailure): + with self.assertRaises(NetworkTimeout) as context: s.with_transaction(callback) # One insert for the callback and two commits (includes the automatic @@ -556,6 +566,40 @@ def callback(session): self.assertEqual( listener.started_command_names(), ["insert", "commitTransaction", "commitTransaction"] ) + # Assert that the timeout error has the same labels as the error it wraps. + self.assertTrue(context.exception.has_error_label("UnknownTransactionCommitResult")) + + @client_context.require_transactions + def test_callback_not_retried_after_csot_timeout(self): + listener = OvertCommandListener() + client = self.rs_client(event_listeners=[listener]) + coll = client[self.db.name].test + + def callback(session): + coll.insert_one({}, session=session) + err: dict = { + "ok": 0, + "errmsg": "Transaction 7819 has been aborted.", + "code": 251, + "codeName": "NoSuchTransaction", + "errorLabels": ["TransientTransactionError"], + } + raise OperationFailure(err["errmsg"], err["code"], err) + + # Create the collection. + coll.insert_one({}) + listener.reset() + with client.start_session() as s: + with pymongo.timeout(1.0): + with self.assertRaises(ExecutionTimeout): + s.with_transaction(callback) + + # At least two attempts: the original and one or more retries. + inserts = len([x for x in listener.started_command_names() if x == "insert"]) + aborts = len([x for x in listener.started_command_names() if x == "abortTransaction"]) + + self.assertGreaterEqual(inserts, 2) + self.assertGreaterEqual(aborts, 2) # Tested here because this supports Motor's convenient transactions API. @client_context.require_transactions @@ -594,6 +638,63 @@ def callback(session): s.with_transaction(callback) self.assertFalse(s.in_transaction) + @client_context.require_test_commands + @client_context.require_transactions + def test_4_retry_backoff_is_enforced(self): + client = client_context.client + coll = client[self.db.name].test + end = start = no_backoff_time = 0 + + # Make random.random always return 0 (no backoff) + with patch.object(random, "random", return_value=0): + # set fail point to trigger transaction failure and trigger backoff + self.set_fail_point( + { + "configureFailPoint": "failCommand", + "mode": {"times": 13}, + "data": { + "failCommands": ["commitTransaction"], + "errorCode": 251, + }, + } + ) + self.addCleanup( + self.set_fail_point, {"configureFailPoint": "failCommand", "mode": "off"} + ) + + def callback(session): + coll.insert_one({}, session=session) + + start = time.monotonic() + with self.client.start_session() as s: + s.with_transaction(callback) + end = time.monotonic() + no_backoff_time = end - start + + # Make random.random always return 1 (max backoff) + with patch.object(random, "random", return_value=1): + # set fail point to trigger transaction failure and trigger backoff + self.set_fail_point( + { + "configureFailPoint": "failCommand", + "mode": { + "times": 13 + }, # sufficiently high enough such that the time effect of backoff is noticeable + "data": { + "failCommands": ["commitTransaction"], + "errorCode": 251, + }, + } + ) + self.addCleanup( + self.set_fail_point, {"configureFailPoint": "failCommand", "mode": "off"} + ) + start = time.monotonic() + with self.client.start_session() as s: + s.with_transaction(callback) + end = time.monotonic() + self.assertLess(abs(end - start - (no_backoff_time + 2.2)), 1) # sum of 13 backoffs is 2.2 + class TestOptionsInsideTransactionProse(TransactionsBase): @client_context.require_transactions diff --git a/test/transactions/unified/backpressure-retryable-abort.json b/test/transactions/unified/backpressure-retryable-abort.json new file mode 100644 index 0000000000..3a2a3b4368 --- /dev/null +++ b/test/transactions/unified/backpressure-retryable-abort.json @@ -0,0 +1,342 @@ +{ + "description": "backpressure-retryable-abort", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "abortTransaction retries if backpressure labels are added", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction is retried maxAttempts=2 times if backpressure labels are added", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/test/transactions/unified/backpressure-retryable-commit.json b/test/transactions/unified/backpressure-retryable-commit.json new file mode 100644 index 0000000000..844ed25ab4 --- /dev/null +++ b/test/transactions/unified/backpressure-retryable-commit.json @@ -0,0 +1,359 @@ +{ + "description": "backpressure-retryable-commit", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "sharded", + "replicaset", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction retries if backpressure labels are added", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction is retried maxAttempts=2 times if backpressure labels are added", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/test/transactions/unified/backpressure-retryable-reads.json b/test/transactions/unified/backpressure-retryable-reads.json new file mode 100644 index 0000000000..a859ec4bda --- /dev/null +++ b/test/transactions/unified/backpressure-retryable-reads.json @@ -0,0 +1,313 @@ +{ + "description": "backpressure-retryable-reads", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "reads are retried if backpressure labels are added", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "session": "session0" + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "reads are retried maxAttempts=2 times if backpressure labels are added", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "find" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "session": "session0" + }, + "expectError": { + "isError": true + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + } + ] + } + ] +} diff --git a/test/transactions/unified/backpressure-retryable-writes.json b/test/transactions/unified/backpressure-retryable-writes.json new file mode 100644 index 0000000000..6cbf450e5f --- /dev/null +++ b/test/transactions/unified/backpressure-retryable-writes.json @@ -0,0 +1,439 @@ +{ + "description": "backpressure-retryable-writes", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "writes are retried if backpressure labels are added", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "writes are retried maxAttempts=2 times if backpressure labels are added", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectError": { + "isError": true + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "retry succeeds if backpressure labels are added to the first operation in a transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableError", + "SystemOverloadedError" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "startTransaction": true + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "startTransaction": true + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "startTransaction": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/test/uri_options/client-backpressure-options.json b/test/uri_options/client-backpressure-options.json new file mode 100644 index 0000000000..3e501d1f4c --- /dev/null +++ b/test/uri_options/client-backpressure-options.json @@ -0,0 +1,66 @@ +{ + "tests": [ + { + "description": "maxAdaptiveRetries is parsed correctly", + "uri": "mongodb://example.com/?maxAdaptiveRetries=3", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "maxAdaptiveRetries": 3 + } + }, + { + "description": "maxAdaptiveRetries=0 is parsed correctly", + "uri": "mongodb://example.com/?maxAdaptiveRetries=0", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "maxAdaptiveRetries": 0 + } + }, + { + "description": "maxAdaptiveRetries with invalid value causes a warning", + "uri": "mongodb://example.com/?maxAdaptiveRetries=-5", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "enableOverloadRetargeting is parsed correctly", + "uri": "mongodb://example.com/?enableOverloadRetargeting=true", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "enableOverloadRetargeting": true + } + }, + { + "description": "enableOverloadRetargeting=false is parsed correctly", + "uri": "mongodb://example.com/?enableOverloadRetargeting=false", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "enableOverloadRetargeting": false + } + }, + { + "description": "enableOverloadRetargeting with invalid value causes a warning", + "uri": "mongodb://example.com/?enableOverloadRetargeting=invalid", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + } + ] +} diff --git a/tools/synchro.py b/tools/synchro.py index ee719d7429..ed794c5963 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -213,6 +213,7 @@ def async_only_test(f: str) -> bool: "test_bulk.py", "test_change_stream.py", "test_client.py", + "test_client_backpressure.py", "test_client_bulk_write.py", "test_client_context.py", "test_client_metadata.py", @@ -350,7 +351,7 @@ def translate_async_sleeps(lines: list[str]) -> list[str]: sleeps = [line for line in lines if "asyncio.sleep" in line] for line in sleeps: - res = re.search(r"asyncio.sleep\(([^()]*)\)", line) + res = re.search(r"asyncio\.sleep\(\s*(.*?)\)", line) if res: old = res[0] index = lines.index(line) From a2b0cd85e3a57cd63cbce9bc24500afb0fa062c6 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 14 Apr 2026 15:48:00 -0400 Subject: [PATCH 106/129] PYTHON-5795 Fix absolute link to CONTRIBUTING.md in README.md (#2756) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c28ef713b1..703e000dc5 100644 --- a/README.md +++ b/README.md @@ -216,4 +216,4 @@ pip install -e ".[test]" pytest ``` -For more advanced testing scenarios, see the [contributing guide](./CONTRIBUTING.md#running-tests-locally). +For more advanced testing scenarios, see the [contributing guide](https://github.com/mongodb/mongo-python-driver/blob/master/CONTRIBUTING.md#running-tests-locally). From 49e7a052e2fdf864ec3eb73e308d6cfd11b5f31d Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 14 Apr 2026 16:24:51 -0400 Subject: [PATCH 107/129] PYTHON-5760 Increase _azure_helpers.py coverage (#2747) --- test/test_azure_helpers.py | 155 +++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 test/test_azure_helpers.py diff --git a/test/test_azure_helpers.py b/test/test_azure_helpers.py new file mode 100644 index 0000000000..6fe6451877 --- /dev/null +++ b/test/test_azure_helpers.py @@ -0,0 +1,155 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for _azure_helpers.py. + +These tests mock urlopen to avoid requiring a live Azure IMDS endpoint. +Integration tests that exercise the real endpoint are gated by environment +variables in test_on_demand_csfle.py and test_auth_oidc.py. +""" + +from __future__ import annotations + +import json +import sys +import unittest +from contextlib import contextmanager +from unittest.mock import MagicMock, patch + +sys.path[0:0] = [""] + +from pymongo._azure_helpers import _get_azure_response + + +@contextmanager +def _mock_urlopen(status: int, body: str): + """Context manager that patches ``urllib.request.urlopen`` with a fake response.""" + mock_response = MagicMock() + mock_response.__enter__ = lambda s: s + mock_response.__exit__ = MagicMock(return_value=False) + mock_response.status = status + mock_response.read.return_value = body.encode("utf8") + + with patch("urllib.request.urlopen", return_value=mock_response) as mock_open: + yield mock_open + + +class TestGetAzureResponse(unittest.TestCase): + def _call(self, resource="https://example.com/", client_id=None, timeout=5): + return _get_azure_response(resource, client_id=client_id, timeout=timeout) + + def test_success_without_client_id(self): + body = json.dumps({"access_token": "tok", "expires_in": "3600"}) + with _mock_urlopen(200, body) as mock_open: + result = self._call() + + self.assertEqual(result["access_token"], "tok") + self.assertEqual(result["expires_in"], "3600") + + # Verify client_id was NOT added to the URL + url = mock_open.call_args[0][0].full_url + self.assertNotIn("client_id", url) + + def test_success_with_client_id(self): + body = json.dumps({"access_token": "tok", "expires_in": "3600"}) + with _mock_urlopen(200, body) as mock_open: + result = self._call(client_id="my-client-id") + + self.assertEqual(result["access_token"], "tok") + url = mock_open.call_args[0][0].full_url + self.assertIn("client_id=my-client-id", url) + + def test_url_contains_resource_and_api_version(self): + body = json.dumps({"access_token": "tok", "expires_in": "3600"}) + with _mock_urlopen(200, body) as mock_open: + self._call(resource="https://test-resource.example.com") + + url = mock_open.call_args[0][0].full_url + self.assertIn("api-version=2018-02-01", url) + self.assertIn("resource=https://test-resource.example.com", url) + + def test_request_headers(self): + body = json.dumps({"access_token": "tok", "expires_in": "3600"}) + with _mock_urlopen(200, body) as mock_open: + self._call() + + request = mock_open.call_args[0][0] + self.assertEqual(request.get_header("Metadata"), "true") + self.assertEqual(request.get_header("Accept"), "application/json") + + def test_urlopen_exception_raises_value_error(self): + with patch("urllib.request.urlopen", side_effect=OSError("connection refused")): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("Failed to acquire IMDS access token", str(ctx.exception)) + + def test_non_200_status_raises_value_error(self): + body = json.dumps({"error": "something went wrong"}) + with _mock_urlopen(400, body): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("Failed to acquire IMDS access token", str(ctx.exception)) + + def test_non_json_body_raises_value_error(self): + with _mock_urlopen(200, "not-json"): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("Azure IMDS response must be in JSON format", str(ctx.exception)) + + def test_missing_access_token_raises_value_error(self): + body = json.dumps({"expires_in": "3600"}) + with _mock_urlopen(200, body): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("access_token", str(ctx.exception)) + + def test_missing_expires_in_raises_value_error(self): + body = json.dumps({"access_token": "tok"}) + with _mock_urlopen(200, body): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("expires_in", str(ctx.exception)) + + def test_empty_access_token_raises_value_error(self): + body = json.dumps({"access_token": "", "expires_in": "3600"}) + with _mock_urlopen(200, body): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("access_token", str(ctx.exception)) + + def test_empty_expires_in_raises_value_error(self): + body = json.dumps({"access_token": "tok", "expires_in": ""}) + with _mock_urlopen(200, body): + with self.assertRaises(ValueError) as ctx: + self._call() + + self.assertIn("expires_in", str(ctx.exception)) + + def test_timeout_passed_to_urlopen(self): + body = json.dumps({"access_token": "tok", "expires_in": "3600"}) + with _mock_urlopen(200, body) as mock_open: + self._call(timeout=42) + + _, kwargs = mock_open.call_args + self.assertEqual(kwargs["timeout"], 42) + + +if __name__ == "__main__": + unittest.main() From f41dd5c08b352f2ca885e19e475e366383dd3ec8 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 14 Apr 2026 16:53:35 -0400 Subject: [PATCH 108/129] PYTHON-5772 Increase _gcp_helpers.py coverage (#2749) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/test_gcp_helpers.py | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 test/test_gcp_helpers.py diff --git a/test/test_gcp_helpers.py b/test/test_gcp_helpers.py new file mode 100644 index 0000000000..6d5226a283 --- /dev/null +++ b/test/test_gcp_helpers.py @@ -0,0 +1,116 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for pymongo/_gcp_helpers.py.""" + +from __future__ import annotations + +import sys +import unittest +from contextlib import contextmanager +from unittest.mock import MagicMock, patch + +sys.path[0:0] = [""] + +from pymongo._gcp_helpers import _get_gcp_response + + +@contextmanager +def _mock_urlopen(status: int, body: str): + """Context manager that patches ``urllib.request.urlopen`` with a fake response.""" + mock_response = MagicMock() + mock_response.__enter__ = MagicMock(return_value=mock_response) + mock_response.__exit__ = MagicMock(return_value=False) + mock_response.status = status + mock_response.read.return_value = body.encode("utf8") + + with patch("urllib.request.urlopen", return_value=mock_response) as mock_open: + yield mock_open + + +class TestGetGcpResponse(unittest.TestCase): + """Tests for :func:`pymongo._gcp_helpers._get_gcp_response`.""" + + def test_successful_response_returns_access_token(self): + """A 200 response yields ``{"access_token": }``.""" + token = "ya29.some-gcp-token" + with _mock_urlopen(200, token): + result = _get_gcp_response("https://example.com") + + self.assertEqual(result, {"access_token": token}) + + def test_non_200_status_raises_value_error(self): + """A non-200 HTTP status raises :class:`ValueError`.""" + for status in (400, 401, 403, 500, 503): + with self.subTest(status=status): + with _mock_urlopen(status, "error"): + with self.assertRaises(ValueError) as ctx: + _get_gcp_response("https://example.com") + self.assertIn("Failed to acquire IMDS access token", str(ctx.exception)) + + def test_urlopen_exception_raises_value_error(self): + """An exception from ``urlopen`` is wrapped in :class:`ValueError`.""" + with patch("urllib.request.urlopen", side_effect=OSError("connection refused")): + with self.assertRaises(ValueError) as ctx: + _get_gcp_response("https://example.com") + + self.assertIn("Failed to acquire IMDS access token", str(ctx.exception)) + self.assertIn("connection refused", str(ctx.exception)) + + def test_url_contains_resource_as_audience(self): + """The ``resource`` argument is appended as ``?audience=`` in the URL.""" + resource = "https://my-service.example.com" + with _mock_urlopen(200, "token") as mock_open: + _get_gcp_response(resource) + + request_obj = mock_open.call_args[0][0] + self.assertIn(f"?audience={resource}", request_obj.full_url) + + def test_request_has_metadata_flavor_google_header(self): + """The request must include the ``Metadata-Flavor: Google`` header.""" + with _mock_urlopen(200, "token") as mock_open: + _get_gcp_response("https://example.com") + + request_obj = mock_open.call_args[0][0] + self.assertEqual(request_obj.get_header("Metadata-flavor"), "Google") + + def test_default_timeout_is_five_seconds(self): + """Without an explicit timeout, ``urlopen`` is called with ``timeout=5``.""" + with _mock_urlopen(200, "token") as mock_open: + _get_gcp_response("https://example.com") + + _, kwargs = mock_open.call_args + self.assertEqual(kwargs.get("timeout"), 5) + + def test_custom_timeout_is_forwarded(self): + """An explicit ``timeout`` value is passed through to ``urlopen``.""" + with _mock_urlopen(200, "token") as mock_open: + _get_gcp_response("https://example.com", timeout=30) + + _, kwargs = mock_open.call_args + self.assertEqual(kwargs.get("timeout"), 30) + + def test_urlopen_exception_does_not_chain_original(self): + """The raised ``ValueError`` suppresses the original exception (``from None``).""" + with patch("urllib.request.urlopen", side_effect=RuntimeError("network error")): + with self.assertRaises(ValueError) as ctx: + _get_gcp_response("https://example.com") + + # ``raise ... from None`` sets __cause__ to None and __suppress_context__ to True. + self.assertIs(ctx.exception.__cause__, None) + self.assertIs(ctx.exception.__suppress_context__, True) + + +if __name__ == "__main__": + unittest.main() From 35e51a50f3ecf2020be10b6eae96e3ad0038174a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:59:12 -0400 Subject: [PATCH 109/129] Revert "PYTHON-5768 Add AGENTS.md w/copilot instructions" (#2744) (#2754) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aclark4life <72164+aclark4life@users.noreply.github.com> Co-authored-by: Jib --- .github/copilot-instructions.md | 45 +++++++++++++++++++++++++++++++-- AGENTS.md | 44 -------------------------------- 2 files changed, 43 insertions(+), 46 deletions(-) delete mode 100644 AGENTS.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a8943d11ac..b67cb49aca 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,3 +1,44 @@ -Please see [AGENTS.md](../AGENTS.md). +When reviewing code, focus on: -Follow the repository instructions defined in `AGENTS.md` when working in this codebase. +## Security Critical Issues +- Check for hardcoded secrets, API keys, or credentials. +- Check for instances of potential method call injection, dynamic code execution, symbol injection or other code injection vulnerabilities. + +## Performance Red Flags +- Spot inefficient loops and algorithmic issues. +- Check for memory leaks and resource cleanup. + +## Code Quality Essentials +- Methods should be focused and appropriately sized. If a method is doing too much, suggest refactorings to split it up. +- Use clear, descriptive naming conventions. +- Avoid encapsulation violations and ensure proper separation of concerns. +- All public classes, modules, and methods should have clear documentation in Sphinx format. + +## PyMongo-specific Concerns +- Do not review files within `pymongo/synchronous` or files in `test/` that also have a file of the same name in `test/asynchronous` unless the reviewed changes include a `_IS_SYNC` statement. PyMongo generates these files from `pymongo/asynchronous` and `test/asynchronous` using `tools/synchro.py`. +- All asynchronous functions must not call any blocking I/O. + +## Review Style +- Be specific and actionable in feedback. +- Explain the "why" behind recommendations. +- Acknowledge good patterns when you see them. +- Ask clarifying questions when code intent is unclear. + +Always prioritize security vulnerabilities and performance issues that could impact users. + +Always suggest changes to improve readability and testability. For example, this suggestion seeks to make the code more readable, reusable, and testable: + +```python +# Instead of: +if user.email and "@" in user.email and len(user.email) > 5: + submit_button.enabled = True +else: + submit_button.enabled = False + +# Consider: +def valid_email(email): + return email and "@" in email and len(email) > 5 + + +submit_button.enabled = valid_email(user.email) +``` diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index b67cb49aca..0000000000 --- a/AGENTS.md +++ /dev/null @@ -1,44 +0,0 @@ -When reviewing code, focus on: - -## Security Critical Issues -- Check for hardcoded secrets, API keys, or credentials. -- Check for instances of potential method call injection, dynamic code execution, symbol injection or other code injection vulnerabilities. - -## Performance Red Flags -- Spot inefficient loops and algorithmic issues. -- Check for memory leaks and resource cleanup. - -## Code Quality Essentials -- Methods should be focused and appropriately sized. If a method is doing too much, suggest refactorings to split it up. -- Use clear, descriptive naming conventions. -- Avoid encapsulation violations and ensure proper separation of concerns. -- All public classes, modules, and methods should have clear documentation in Sphinx format. - -## PyMongo-specific Concerns -- Do not review files within `pymongo/synchronous` or files in `test/` that also have a file of the same name in `test/asynchronous` unless the reviewed changes include a `_IS_SYNC` statement. PyMongo generates these files from `pymongo/asynchronous` and `test/asynchronous` using `tools/synchro.py`. -- All asynchronous functions must not call any blocking I/O. - -## Review Style -- Be specific and actionable in feedback. -- Explain the "why" behind recommendations. -- Acknowledge good patterns when you see them. -- Ask clarifying questions when code intent is unclear. - -Always prioritize security vulnerabilities and performance issues that could impact users. - -Always suggest changes to improve readability and testability. For example, this suggestion seeks to make the code more readable, reusable, and testable: - -```python -# Instead of: -if user.email and "@" in user.email and len(user.email) > 5: - submit_button.enabled = True -else: - submit_button.enabled = False - -# Consider: -def valid_email(email): - return email and "@" in email and len(email) > 5 - - -submit_button.enabled = valid_email(user.email) -``` From 5da91837d4db9b2f1d3521f2e39cff9e0ff94334 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 15 Apr 2026 14:18:34 -0400 Subject: [PATCH 110/129] =?UTF-8?q?PYTHON-5794=20-=20Add=20prose=20tests?= =?UTF-8?q?=20to=20verify=20correct=20retry=20behavior=20when=20a=E2=80=A6?= =?UTF-8?q?=20(#2755)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jib --- pymongo/asynchronous/mongo_client.py | 10 +- pymongo/synchronous/mongo_client.py | 10 +- test/asynchronous/test_retryable_reads.py | 130 ++++++++++++++++++++- test/asynchronous/test_retryable_writes.py | 108 +++++++++++++++++ test/test_retryable_reads.py | 130 ++++++++++++++++++++- test/test_retryable_writes.py | 108 +++++++++++++++++ 6 files changed, 490 insertions(+), 6 deletions(-) diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 03e2d6073a..412a13ec70 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -2779,7 +2779,7 @@ def __init__( self._last_error: Optional[Exception] = None self._retrying = False self._always_retryable = False - self._multiple_retries = _csot.get_timeout() is not None + self._max_retries = float("inf") if _csot.get_timeout() is not None else 1 self._client = mongo_client self._retry_policy = mongo_client._retry_policy self._func = func @@ -2852,6 +2852,8 @@ async def run(self) -> T: # ConnectionFailures do not supply a code property exc_code = getattr(exc, "code", None) overloaded = exc.has_error_label("SystemOverloadedError") + if overloaded: + self._max_retries = self._client.options.max_adaptive_retries always_retryable = exc.has_error_label("RetryableError") and overloaded if not self._client.options.retry_reads or ( not always_retryable @@ -2890,6 +2892,8 @@ async def run(self) -> T: exc_to_check = exc.error retryable_write_label = exc_to_check.has_error_label("RetryableWriteError") overloaded = exc_to_check.has_error_label("SystemOverloadedError") + if overloaded: + self._max_retries = self._client.options.max_adaptive_retries always_retryable = exc_to_check.has_error_label("RetryableError") and overloaded # Always retry abortTransaction and commitTransaction up to once @@ -2943,7 +2947,9 @@ async def run(self) -> T: def _is_not_eligible_for_retry(self) -> bool: """Checks if the exchange is not eligible for retry""" - return not self._retryable or (self._is_retrying() and not self._multiple_retries) + return not self._retryable or ( + self._is_retrying() and self._attempt_number >= self._max_retries + ) def _is_retrying(self) -> bool: """Checks if the exchange is currently undergoing a retry""" diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index c049dcaeae..2bd6f31b72 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -2769,7 +2769,7 @@ def __init__( self._last_error: Optional[Exception] = None self._retrying = False self._always_retryable = False - self._multiple_retries = _csot.get_timeout() is not None + self._max_retries = float("inf") if _csot.get_timeout() is not None else 1 self._client = mongo_client self._retry_policy = mongo_client._retry_policy self._func = func @@ -2842,6 +2842,8 @@ def run(self) -> T: # ConnectionFailures do not supply a code property exc_code = getattr(exc, "code", None) overloaded = exc.has_error_label("SystemOverloadedError") + if overloaded: + self._max_retries = self._client.options.max_adaptive_retries always_retryable = exc.has_error_label("RetryableError") and overloaded if not self._client.options.retry_reads or ( not always_retryable @@ -2880,6 +2882,8 @@ def run(self) -> T: exc_to_check = exc.error retryable_write_label = exc_to_check.has_error_label("RetryableWriteError") overloaded = exc_to_check.has_error_label("SystemOverloadedError") + if overloaded: + self._max_retries = self._client.options.max_adaptive_retries always_retryable = exc_to_check.has_error_label("RetryableError") and overloaded # Always retry abortTransaction and commitTransaction up to once @@ -2933,7 +2937,9 @@ def run(self) -> T: def _is_not_eligible_for_retry(self) -> bool: """Checks if the exchange is not eligible for retry""" - return not self._retryable or (self._is_retrying() and not self._multiple_retries) + return not self._retryable or ( + self._is_retrying() and self._attempt_number >= self._max_retries + ) def _is_retrying(self) -> bool: """Checks if the exchange is currently undergoing a retry""" diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 259cd9cff5..c7369db90f 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -20,8 +20,11 @@ import sys import threading from test.asynchronous.utils import async_set_fail_point +from unittest import mock -from pymongo.errors import OperationFailure +from pymongo import MongoClient +from pymongo.common import MAX_ADAPTIVE_RETRIES +from pymongo.errors import OperationFailure, PyMongoError sys.path[0:0] = [""] @@ -38,6 +41,7 @@ ) from pymongo.monitoring import ( + CommandFailedEvent, ConnectionCheckedOutEvent, ConnectionCheckOutFailedEvent, ConnectionCheckOutFailedReason, @@ -145,6 +149,19 @@ async def test_pool_paused_error_is_retryable(self): class TestRetryableReads(AsyncIntegrationTest): + async def asyncSetUp(self) -> None: + await super().asyncSetUp() + self.setup_client = MongoClient(**async_client_context.client_options) + self.addCleanup(self.setup_client.close) + + # TODO: After PYTHON-4595 we can use async event handlers and remove this workaround. + def configure_fail_point_sync(self, command_args, off=False) -> None: + cmd = {"configureFailPoint": "failCommand", **command_args} + if off: + cmd["mode"] = "off" + cmd.pop("data", None) + self.setup_client.admin.command(cmd) + @async_client_context.require_multiple_mongoses @async_client_context.require_failCommand_fail_point async def test_retryable_reads_are_retried_on_a_different_mongos_when_one_is_available(self): @@ -383,6 +400,117 @@ async def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_th # 6. Assert that both events occurred on the same server. assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) # type:ignore[untyped-decorator] + async def test_overload_then_nonoverload_retries_increased_reads(self) -> None: + # Create a client. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 91, + "errorLabels": ["RetryableError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(event_listeners=[listener]) + await client.test.test.insert_one({}) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + with self.assertRaises(PyMongoError): + await client.test.test.find_one() + + started_finds = [e for e in listener.started_events if e.command_name == "find"] + self.assertEqual(len(started_finds), MAX_ADAPTIVE_RETRIES + 1) + + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) # type:ignore[untyped-decorator] + async def test_backoff_is_not_applied_for_non_overload_errors(self): + if _IS_SYNC: + mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff" + else: + mock_target = "pymongo.asynchronous.helpers._RetryPolicy.backoff" + + # Create a client. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 91, + "errorLabels": ["RetryableError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(event_listeners=[listener]) + await client.test.test.insert_one({}) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Perform a findOne operation with coll. Expect the operation to fail. + with mock.patch(mock_target, return_value=0) as mock_backoff: + with self.assertRaises(PyMongoError): + await client.test.test.find_one() + + # Assert that backoff was applied only once for the initial overload error and not for the subsequent non-overload retryable errors. + self.assertEqual(mock_backoff.call_count, 1) + if __name__ == "__main__": unittest.main() diff --git a/test/asynchronous/test_retryable_writes.py b/test/asynchronous/test_retryable_writes.py index 6e2072a2ad..a63aa6783e 100644 --- a/test/asynchronous/test_retryable_writes.py +++ b/test/asynchronous/test_retryable_writes.py @@ -21,6 +21,9 @@ import sys import threading from test.asynchronous.utils import async_set_fail_point, flaky +from unittest import mock + +from pymongo.common import MAX_ADAPTIVE_RETRIES sys.path[0:0] = [""] @@ -784,6 +787,111 @@ def failed(event: CommandFailedEvent) -> None: # Assert that the error does not contain the error label `NoWritesPerformed`. assert "NoWritesPerformed" not in exc.exception.errors["errorLabels"] + async def test_overload_then_nonoverload_retries_increased_writes(self) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError` and `RetryableWriteError` error labels. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 91, + "errorLabels": ["RetryableError", "RetryableWriteError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + with self.assertRaises(PyMongoError): + await client.test.test.insert_one({"x": 1}) + + started_inserts = [e for e in listener.started_events if e.command_name == "insert"] + self.assertEqual(len(started_inserts), MAX_ADAPTIVE_RETRIES + 1) + + async def test_backoff_is_not_applied_for_non_overload_errors(self): + if _IS_SYNC: + mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff" + else: + mock_target = "pymongo.asynchronous.helpers._RetryPolicy.backoff" + + # Create a client. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 91, + "errorLabels": ["RetryableError", "RetryableWriteError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = await self.async_rs_client(event_listeners=[listener]) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Perform a findOne operation with coll. Expect the operation to fail. + with mock.patch(mock_target, return_value=0) as mock_backoff: + with self.assertRaises(PyMongoError): + await client.test.test.insert_one({}) + + # Assert that backoff was applied only once for the initial overload error and not for the subsequent non-overload retryable errors. + self.assertEqual(mock_backoff.call_count, 1) + if __name__ == "__main__": unittest.main() diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index 9e6aac821c..7513194793 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -20,8 +20,11 @@ import sys import threading from test.utils import set_fail_point +from unittest import mock -from pymongo.errors import OperationFailure +from pymongo import MongoClient +from pymongo.common import MAX_ADAPTIVE_RETRIES +from pymongo.errors import OperationFailure, PyMongoError sys.path[0:0] = [""] @@ -38,6 +41,7 @@ ) from pymongo.monitoring import ( + CommandFailedEvent, ConnectionCheckedOutEvent, ConnectionCheckOutFailedEvent, ConnectionCheckOutFailedReason, @@ -145,6 +149,19 @@ def test_pool_paused_error_is_retryable(self): class TestRetryableReads(IntegrationTest): + def setUp(self) -> None: + super().setUp() + self.setup_client = MongoClient(**client_context.client_options) + self.addCleanup(self.setup_client.close) + + # TODO: After PYTHON-4595 we can use async event handlers and remove this workaround. + def configure_fail_point_sync(self, command_args, off=False) -> None: + cmd = {"configureFailPoint": "failCommand", **command_args} + if off: + cmd["mode"] = "off" + cmd.pop("data", None) + self.setup_client.admin.command(cmd) + @client_context.require_multiple_mongoses @client_context.require_failCommand_fail_point def test_retryable_reads_are_retried_on_a_different_mongos_when_one_is_available(self): @@ -381,6 +398,117 @@ def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same # 6. Assert that both events occurred on the same server. assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) # type:ignore[untyped-decorator] + def test_overload_then_nonoverload_retries_increased_reads(self) -> None: + # Create a client. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 91, + "errorLabels": ["RetryableError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(event_listeners=[listener]) + client.test.test.insert_one({}) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + with self.assertRaises(PyMongoError): + client.test.test.find_one() + + started_finds = [e for e in listener.started_events if e.command_name == "find"] + self.assertEqual(len(started_finds), MAX_ADAPTIVE_RETRIES + 1) + + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) # type:ignore[untyped-decorator] + def test_backoff_is_not_applied_for_non_overload_errors(self): + if _IS_SYNC: + mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff" + else: + mock_target = "pymongo.helpers._RetryPolicy.backoff" + + # Create a client. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["find"], + "errorCode": 91, + "errorLabels": ["RetryableError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(event_listeners=[listener]) + client.test.test.insert_one({}) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Perform a findOne operation with coll. Expect the operation to fail. + with mock.patch(mock_target, return_value=0) as mock_backoff: + with self.assertRaises(PyMongoError): + client.test.test.find_one() + + # Assert that backoff was applied only once for the initial overload error and not for the subsequent non-overload retryable errors. + self.assertEqual(mock_backoff.call_count, 1) + if __name__ == "__main__": unittest.main() diff --git a/test/test_retryable_writes.py b/test/test_retryable_writes.py index 5509083162..46ca3a43b8 100644 --- a/test/test_retryable_writes.py +++ b/test/test_retryable_writes.py @@ -21,6 +21,9 @@ import sys import threading from test.utils import flaky, set_fail_point +from unittest import mock + +from pymongo.common import MAX_ADAPTIVE_RETRIES sys.path[0:0] = [""] @@ -780,6 +783,111 @@ def failed(event: CommandFailedEvent) -> None: # Assert that the error does not contain the error label `NoWritesPerformed`. assert "NoWritesPerformed" not in exc.exception.errors["errorLabels"] + def test_overload_then_nonoverload_retries_increased_writes(self) -> None: + # Create a client with retryWrites=true. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with the `RetryableError` and `RetryableWriteError` error labels. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 91, + "errorLabels": ["RetryableError", "RetryableWriteError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(retryWrites=True, event_listeners=[listener]) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + with self.assertRaises(PyMongoError): + client.test.test.insert_one({"x": 1}) + + started_inserts = [e for e in listener.started_events if e.command_name == "insert"] + self.assertEqual(len(started_inserts), MAX_ADAPTIVE_RETRIES + 1) + + def test_backoff_is_not_applied_for_non_overload_errors(self): + if _IS_SYNC: + mock_target = "pymongo.synchronous.helpers._RetryPolicy.backoff" + else: + mock_target = "pymongo.helpers._RetryPolicy.backoff" + + # Create a client. + listener = OvertCommandListener() + + # Configure the client to listen to CommandFailedEvents. In the attached listener, configure a fail point with error + # code `91` (ShutdownInProgress) and `RetryableError` and `SystemOverloadedError` labels. + overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["insert"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 91, + }, + } + + # Configure a fail point with error code `91` (ShutdownInProgress) with only the `RetryableError` error label. + non_overload_fail_point = { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": ["insert"], + "errorCode": 91, + "errorLabels": ["RetryableError", "RetryableWriteError"], + }, + } + + def failed(event: CommandFailedEvent) -> None: + # Configure the fail point command only if the failed event is for the 91 error configured in step 2. + if listener.failed_events: + return + assert event.failure["code"] == 91 + self.configure_fail_point_sync(non_overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + listener.failed_events.append(event) + + listener.failed = failed + + client = self.rs_client(event_listeners=[listener]) + + self.configure_fail_point_sync(overload_fail_point) + self.addCleanup(self.configure_fail_point_sync, {}, off=True) + + # Perform a findOne operation with coll. Expect the operation to fail. + with mock.patch(mock_target, return_value=0) as mock_backoff: + with self.assertRaises(PyMongoError): + client.test.test.insert_one({}) + + # Assert that backoff was applied only once for the initial overload error and not for the subsequent non-overload retryable errors. + self.assertEqual(mock_backoff.call_count, 1) + if __name__ == "__main__": unittest.main() From f31ba09713ff61086f5ebddb43a1e79b419b9802 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 15 Apr 2026 14:42:29 -0400 Subject: [PATCH 111/129] PYTHON-5797 - Add IWM and Overload Error links to changelog (#2757) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index a1178ef3db..7ed86dfb87 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -16,7 +16,7 @@ PyMongo 4.17 brings a number of changes including: See for examples and more information. - Added support for MongoDB's Intelligent Workload Management (IWM) and ingress connection rate limiting features. The driver now gracefully handles write-blocking scenarios and optimizes connection establishment during high-load conditions to maintain application availability. - See and for more information. + See the `IWM `_ or `Overload Errors `_ docs for more information. Changes in Version 4.16.0 (2026/01/07) -------------------------------------- From b4e2c03a927ca275e097342f847eb9705465e975 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 16 Apr 2026 12:25:23 -0400 Subject: [PATCH 112/129] PYTHON-5800 - Simple collation is included in index information (#2761) --- test/asynchronous/test_collation.py | 1 - test/test_collation.py | 1 - 2 files changed, 2 deletions(-) diff --git a/test/asynchronous/test_collation.py b/test/asynchronous/test_collation.py index 05e548c79e..78b28ab3df 100644 --- a/test/asynchronous/test_collation.py +++ b/test/asynchronous/test_collation.py @@ -257,7 +257,6 @@ async def test_indexes_same_keys_different_collations(self): self.assertEqual( ja_collation.document["locale"], indexes["japanese_version"]["collation"]["locale"] ) - self.assertNotIn("collation", indexes["simple"]) await self.db.test.drop_index("fieldname_1") indexes = await self.db.test.index_information() self.assertIn("japanese_version", indexes) diff --git a/test/test_collation.py b/test/test_collation.py index 5425551dc6..0f5efa92dd 100644 --- a/test/test_collation.py +++ b/test/test_collation.py @@ -257,7 +257,6 @@ def test_indexes_same_keys_different_collations(self): self.assertEqual( ja_collation.document["locale"], indexes["japanese_version"]["collation"]["locale"] ) - self.assertNotIn("collation", indexes["simple"]) self.db.test.drop_index("fieldname_1") indexes = self.db.test.index_information() self.assertIn("japanese_version", indexes) From 912ef337f90852b58eaf6318e59f97206b281980 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 16 Apr 2026 13:32:50 -0400 Subject: [PATCH 113/129] =?UTF-8?q?PYTHON-5798=20-=20Overload=20retargetin?= =?UTF-8?q?g=20prose=20tests=20do=20not=20ensure=20that=20sec=E2=80=A6=20(?= =?UTF-8?q?#2760)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/asynchronous/test_retryable_reads.py | 11 ++++++++++- test/test_retryable_reads.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index c7369db90f..361db4ca92 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -19,7 +19,7 @@ import pprint import sys import threading -from test.asynchronous.utils import async_set_fail_point +from test.asynchronous.utils import async_ensure_all_connected, async_set_fail_point from unittest import mock from pymongo import MongoClient @@ -295,6 +295,9 @@ async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_ enableOverloadRetargeting=True, ) + # Ensure the client has discovered all nodes. + await async_ensure_all_connected(client) + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. command_args = { "configureFailPoint": "failCommand", @@ -334,6 +337,9 @@ async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_o event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" ) + # Ensure the client has discovered all nodes. + await async_ensure_all_connected(client) + # 2. Configure a fail point with the RetryableError error label. command_args = { "configureFailPoint": "failCommand", @@ -375,6 +381,9 @@ async def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_th readPreference="primaryPreferred", ) + # Ensure the client has discovered all nodes. + await async_ensure_all_connected(client) + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. command_args = { "configureFailPoint": "failCommand", diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index 7513194793..2da87f8b26 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -19,7 +19,7 @@ import pprint import sys import threading -from test.utils import set_fail_point +from test.utils import ensure_all_connected, set_fail_point from unittest import mock from pymongo import MongoClient @@ -293,6 +293,9 @@ def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_differ enableOverloadRetargeting=True, ) + # Ensure the client has discovered all nodes. + ensure_all_connected(client) + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. command_args = { "configureFailPoint": "failCommand", @@ -332,6 +335,9 @@ def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_ event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" ) + # Ensure the client has discovered all nodes. + ensure_all_connected(client) + # 2. Configure a fail point with the RetryableError error label. command_args = { "configureFailPoint": "failCommand", @@ -373,6 +379,9 @@ def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same readPreference="primaryPreferred", ) + # Ensure the client has discovered all nodes. + ensure_all_connected(client) + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. command_args = { "configureFailPoint": "failCommand", From 3491c08ef6505d42d07b9a17100b5190ed1294fc Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Fri, 17 Apr 2026 14:17:53 -0400 Subject: [PATCH 114/129] PYTHON-5801 - Update changelog for 4.17 release (#2762) --- doc/changelog.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 7ed86dfb87..ed6f23f86d 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -Changes in Version 4.17.0 (2026/XX/XX) +Changes in Version 4.17.0 (2026/04/20) -------------------------------------- PyMongo 4.17 brings a number of changes including: @@ -10,10 +10,9 @@ PyMongo 4.17 brings a number of changes including: been deprecated and will be removed in PyMongo 5.0. These methods were deprecated in favor of the standard dictionary containment operator ``in`` and the ``keys()`` and ``values()`` methods, respectively. - - Added the :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.bind` and :meth:`~pymongo.client_session.ClientSession.bind` methods that allow users to bind a session to all database operations within the scope of a context manager instead of having to explicitly pass the session to each individual operation. - See for examples and more information. + See the `Transactions docs `_ for examples and more information. - Added support for MongoDB's Intelligent Workload Management (IWM) and ingress connection rate limiting features. The driver now gracefully handles write-blocking scenarios and optimizes connection establishment during high-load conditions to maintain application availability. See the `IWM `_ or `Overload Errors `_ docs for more information. From 5406febcd929d277d1cb16994d6225ea132cbca8 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Mon, 20 Apr 2026 16:51:01 -0400 Subject: [PATCH 115/129] Bump version to 4.18.0.dev0 (#2768) --- pymongo/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/_version.py b/pymongo/_version.py index 9e4363eed5..9c67ac76ef 100644 --- a/pymongo/_version.py +++ b/pymongo/_version.py @@ -18,7 +18,7 @@ import re from typing import List, Tuple, Union -__version__ = "4.17.0.dev0" +__version__ = "4.18.0.dev0" def get_version_tuple(version: str) -> Tuple[Union[int, str], ...]: From 8363bf60adce8ad27f6215d57e6d5b3ed1295d9e Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Mon, 20 Apr 2026 16:52:36 -0400 Subject: [PATCH 116/129] PYTHON-5774 Increase daemon.py coverage to 63% (#2759) --- .evergreen/generated_configs/variants.yml | 1 + .evergreen/scripts/generate_config.py | 2 + test/test_daemon.py | 183 ++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 test/test_daemon.py diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 4c9116d628..4dd44b5048 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -615,6 +615,7 @@ buildvariants: - name: test-win64 tasks: - name: .test-standard !.pypy + - name: .test-no-orchestration !.pypy display_name: "* Test Win64" run_on: - windows-2022-latest-small diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index b4760eab97..32b75a82a2 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -97,6 +97,8 @@ def create_standard_nonlinux_variants() -> list[BuildVariant]: tasks = [ f".test-standard !.pypy .server-{version}" for version in get_versions_from("6.0") ] + if host_name == "win64": + tasks.append(".test-no-orchestration !.pypy") host = HOSTS[host_name] tags = ["standard-non-linux"] expansions = dict() diff --git a/test/test_daemon.py b/test/test_daemon.py new file mode 100644 index 0000000000..87886d6abd --- /dev/null +++ b/test/test_daemon.py @@ -0,0 +1,183 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test the pymongo daemon module.""" +from __future__ import annotations + +import subprocess +import sys +import warnings +from unittest.mock import MagicMock, patch + +sys.path[0:0] = [""] + +from test import unittest + +import pymongo.daemon as daemon_module +from pymongo.daemon import _popen_wait, _silence_resource_warning, _spawn_daemon + + +class TestPopenWait(unittest.TestCase): + def test_returns_returncode_on_success(self): + mock_popen = MagicMock() + mock_popen.wait.return_value = 0 + self.assertEqual(0, _popen_wait(mock_popen, timeout=5)) + mock_popen.wait.assert_called_once_with(timeout=5) + + def test_returns_none_on_timeout_expired(self): + mock_popen = MagicMock() + mock_popen.wait.side_effect = subprocess.TimeoutExpired(cmd="foo", timeout=5) + self.assertIsNone(_popen_wait(mock_popen, timeout=5)) + + def test_none_timeout_passes_through(self): + mock_popen = MagicMock() + mock_popen.wait.return_value = 1 + self.assertEqual(1, _popen_wait(mock_popen, timeout=None)) + mock_popen.wait.assert_called_once_with(timeout=None) + + +class TestSilenceResourceWarning(unittest.TestCase): + def test_sets_returncode_to_zero(self): + mock_popen = MagicMock() + mock_popen.returncode = None + _silence_resource_warning(mock_popen) + self.assertEqual(0, mock_popen.returncode) + + def test_no_op_for_none(self): + # Should not raise when popen is None (mongocryptd spawn failed). + _silence_resource_warning(None) + + +@unittest.skipIf(sys.platform == "win32", "Unix only") +class TestSpawnUnix(unittest.TestCase): + def setUp(self): + from pymongo.daemon import _spawn + + self._spawn = _spawn + + def test_returns_popen_on_success(self): + mock_popen = MagicMock() + with patch("subprocess.Popen", return_value=mock_popen): + result = self._spawn(["somecommand"]) + self.assertIs(mock_popen, result) + + def test_filenotfound_warns_and_returns_none(self): + with patch("subprocess.Popen", side_effect=FileNotFoundError("not found")): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + result = self._spawn(["nonexistent_command"]) + self.assertIsNone(result) + self.assertEqual(1, len(w)) + self.assertIs(RuntimeWarning, w[0].category) + self.assertIn("nonexistent_command", str(w[0].message)) + + +@unittest.skipIf(sys.platform == "win32", "Unix only") +class TestSpawnDaemonDoublePopen(unittest.TestCase): + def setUp(self): + from pymongo.daemon import _spawn_daemon_double_popen + + self._spawn_daemon_double_popen = _spawn_daemon_double_popen + + def test_spawns_this_file_as_intermediate(self): + mock_popen = MagicMock() + mock_popen.wait.return_value = 0 + with patch("subprocess.Popen", return_value=mock_popen) as mock_cls: + self._spawn_daemon_double_popen(["somecommand", "--arg"]) + spawner_args = mock_cls.call_args[0][0] + self.assertEqual(sys.executable, spawner_args[0]) + self.assertIn("daemon.py", spawner_args[1]) + self.assertIn("somecommand", spawner_args) + + def test_waits_for_intermediate_process(self): + mock_popen = MagicMock() + with patch("subprocess.Popen", return_value=mock_popen): + self._spawn_daemon_double_popen(["somecommand"]) + mock_popen.wait.assert_called_once_with(timeout=daemon_module._WAIT_TIMEOUT) + + def test_continues_on_timeout(self): + # _popen_wait swallows TimeoutExpired — double Popen must not raise. + mock_popen = MagicMock() + mock_popen.wait.side_effect = subprocess.TimeoutExpired(cmd="foo", timeout=10) + with patch("subprocess.Popen", return_value=mock_popen): + self._spawn_daemon_double_popen(["somecommand"]) # must not raise + + +@unittest.skipIf(sys.platform == "win32", "Unix only") +class TestSpawnDaemonUnix(unittest.TestCase): + def test_uses_double_popen_when_executable_set(self): + with patch("pymongo.daemon._spawn_daemon_double_popen") as mock_double: + _spawn_daemon(["somecommand"]) + mock_double.assert_called_once_with(["somecommand"]) + + def test_fallback_to_spawn_when_no_executable(self): + with patch("pymongo.daemon._spawn") as mock_spawn: + with patch.object(sys, "executable", ""): + _spawn_daemon(["somecommand"]) + mock_spawn.assert_called_once_with(["somecommand"]) + + +@unittest.skipUnless(sys.platform == "win32", "Windows only") +class TestSpawnDaemonWindows(unittest.TestCase): + def test_silences_resource_warning_on_success(self): + mock_popen = MagicMock() + with patch("subprocess.Popen", return_value=mock_popen): + _spawn_daemon(["somecommand"]) + self.assertEqual(0, mock_popen.returncode) + + def test_filenotfound_warns(self): + with patch("subprocess.Popen", side_effect=FileNotFoundError("not found")): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + _spawn_daemon(["nonexistent_command"]) + self.assertEqual(1, len(w)) + self.assertIs(RuntimeWarning, w[0].category) + self.assertIn("nonexistent_command", str(w[0].message)) + + def test_uses_detached_process_flag(self): + # DETACHED_PROCESS must be passed so the child survives parent exit. + mock_popen = MagicMock() + with patch("subprocess.Popen", return_value=mock_popen) as mock_cls: + _spawn_daemon(["somecommand"]) + kwargs = mock_cls.call_args[1] + self.assertEqual(daemon_module._DETACHED_PROCESS, kwargs["creationflags"]) + + def test_uses_devnull_for_stdio(self): + # stdin/stdout/stderr must be redirected to devnull to fully detach. + mock_popen = MagicMock() + with patch("subprocess.Popen", return_value=mock_popen) as mock_cls: + _spawn_daemon(["somecommand"]) + kwargs = mock_cls.call_args[1] + self.assertIsNotNone(kwargs.get("stdin")) + self.assertIsNotNone(kwargs.get("stdout")) + self.assertIsNotNone(kwargs.get("stderr")) + + def test_detached_process_constant_value(self): + # Value must match the Windows DETACHED_PROCESS process creation flag. + self.assertEqual(0x00000008, daemon_module._DETACHED_PROCESS) + + +@unittest.skipIf(sys.platform == "win32", "Unix only") +class TestMainBlock(unittest.TestCase): + def test_exits_with_zero(self): + # Run daemon.py as a script with a no-op subprocess; verify it exits cleanly. + result = subprocess.run( + [sys.executable, "-m", "pymongo.daemon", sys.executable, "-c", "pass"], + timeout=15, + ) + self.assertEqual(0, result.returncode) + + +if __name__ == "__main__": + unittest.main() From a13842f3518e3a35bc55bdb927f4f015b099eb23 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 21 Apr 2026 12:36:48 -0400 Subject: [PATCH 117/129] PYTHON-5778 Add 100% unit test coverage for event_loggers.py (#2769) --- test/test_event_loggers.py | 374 +++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 test/test_event_loggers.py diff --git a/test/test_event_loggers.py b/test/test_event_loggers.py new file mode 100644 index 0000000000..3bb5a3d25d --- /dev/null +++ b/test/test_event_loggers.py @@ -0,0 +1,374 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for pymongo.event_loggers.""" +from __future__ import annotations + +import sys +from unittest.mock import MagicMock, patch + +sys.path[0:0] = [""] + +from test import unittest + +from pymongo.event_loggers import ( + CommandLogger, + ConnectionPoolLogger, + HeartbeatLogger, + ServerLogger, + TopologyLogger, +) + + +class TestCommandLogger(unittest.TestCase): + def setUp(self): + self.logger = CommandLogger() + + def test_started_logs_info(self): + event = MagicMock() + event.command_name = "find" + event.request_id = 42 + event.connection_id = ("localhost", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.started(event) + log = logs.records[0].getMessage() + self.assertIn("find", log) + self.assertIn("42", log) + self.assertIn("started", log) + + def test_succeeded_logs_info(self): + event = MagicMock() + event.command_name = "insert" + event.request_id = 7 + event.connection_id = ("localhost", 27017) + event.duration_micros = 500 + with self.assertLogs(level="INFO") as logs: + self.logger.succeeded(event) + log = logs.records[0].getMessage() + self.assertIn("insert", log) + self.assertIn("7", log) + self.assertIn("500", log) + self.assertIn("microseconds", log) + self.assertIn("succeeded", log) + + def test_failed_logs_info(self): + event = MagicMock() + event.command_name = "delete" + event.request_id = 3 + event.connection_id = ("localhost", 27017) + event.duration_micros = 300 + with self.assertLogs(level="INFO") as logs: + self.logger.failed(event) + log = logs.records[0].getMessage() + self.assertIn("delete", log) + self.assertIn("3", log) + self.assertIn("300", log) + self.assertIn("microseconds", log) + self.assertIn("failed", log) + + +class TestServerLogger(unittest.TestCase): + def setUp(self): + self.logger = ServerLogger() + + def test_opened_logs_info(self): + event = MagicMock() + event.server_address = ("host1", 27017) + event.topology_id = "topology-abc" + with self.assertLogs(level="INFO") as logs: + self.logger.opened(event) + log = logs.records[0].getMessage() + self.assertIn("host1", log) + self.assertIn("topology-abc", log) + + def test_closed_logs_warning(self): + event = MagicMock() + event.server_address = ("host1", 27017) + event.topology_id = "topology-abc" + with self.assertLogs(level="WARNING") as logs: + self.logger.closed(event) + log = logs.records[0].getMessage() + self.assertIn("host1", log) + self.assertIn("topology-abc", log) + + def test_description_changed_logs_when_type_changes(self): + event = MagicMock() + event.server_address = ("host1", 27017) + event.previous_description.server_type = 1 + event.previous_description.server_type_name = "Unknown" + event.new_description.server_type = 2 + event.new_description.server_type_name = "Standalone" + with self.assertLogs(level="INFO") as logs: + self.logger.description_changed(event) + log = logs.records[0].getMessage() + self.assertIn("Unknown", log) + self.assertIn("Standalone", log) + + def test_description_changed_no_log_when_type_same(self): + event = MagicMock() + event.previous_description.server_type = 2 + event.new_description.server_type = 2 + with patch("logging.info") as mock_info: + self.logger.description_changed(event) + mock_info.assert_not_called() + + +class TestHeartbeatLogger(unittest.TestCase): + def setUp(self): + self.logger = HeartbeatLogger() + + def test_started_logs_info(self): + event = MagicMock() + event.connection_id = ("mongo.host", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.started(event) + log = logs.records[0].getMessage() + self.assertIn("mongo.host", log) + + def test_succeeded_logs_info(self): + event = MagicMock() + event.connection_id = ("mongo.host", 27017) + event.reply.document = {"ok": 1, "maxWireVersion": 17} + with self.assertLogs(level="INFO") as logs: + self.logger.succeeded(event) + log = logs.records[0].getMessage() + self.assertIn("mongo.host", log) + self.assertIn("succeeded", log) + self.assertIn("maxWireVersion", log) + + def test_failed_logs_warning(self): + event = MagicMock() + event.connection_id = ("mongo.host", 27017) + event.reply = TimeoutError("timed out") + with self.assertLogs(level="WARNING") as logs: + self.logger.failed(event) + log = logs.records[0].getMessage() + self.assertIn("mongo.host", log) + self.assertIn("failed", log) + self.assertIn("timed out", log) + + +class TestTopologyLogger(unittest.TestCase): + def setUp(self): + self.logger = TopologyLogger() + + def test_opened_logs_info(self): + event = MagicMock() + event.topology_id = "topo-1" + with self.assertLogs(level="INFO") as logs: + self.logger.opened(event) + log = logs.records[0].getMessage() + self.assertIn("topo-1", log) + self.assertIn("opened", log) + + def test_closed_logs_info(self): + event = MagicMock() + event.topology_id = "topo-1" + with self.assertLogs(level="INFO") as logs: + self.logger.closed(event) + log = logs.records[0].getMessage() + self.assertIn("topo-1", log) + self.assertIn("closed", log) + + def test_description_changed_always_logs_update(self): + event = MagicMock() + event.topology_id = "topo-1" + event.previous_description.topology_type = 1 + event.new_description.topology_type = 1 + event.new_description.has_writable_server.return_value = True + event.new_description.has_readable_server.return_value = True + with self.assertLogs(level="INFO") as logs: + self.logger.description_changed(event) + messages = [r.getMessage() for r in logs.records] + self.assertTrue(any("updated" in m for m in messages)) + self.assertTrue(any("topo-1" in m for m in messages)) + + def test_description_changed_logs_type_change(self): + event = MagicMock() + event.topology_id = "topo-2" + event.previous_description.topology_type = 0 + event.previous_description.topology_type_name = "Unknown" + event.new_description.topology_type = 1 + event.new_description.topology_type_name = "Single" + event.new_description.has_writable_server.return_value = True + event.new_description.has_readable_server.return_value = True + with self.assertLogs(level="INFO") as logs: + self.logger.description_changed(event) + messages = [r.getMessage() for r in logs.records] + self.assertTrue(any("Unknown" in m and "Single" in m for m in messages)) + + def test_description_changed_no_type_change_log_when_same(self): + event = MagicMock() + event.topology_id = "topo-1" + event.previous_description.topology_type = 1 + event.new_description.topology_type = 1 + event.new_description.has_writable_server.return_value = True + event.new_description.has_readable_server.return_value = True + with self.assertLogs(level="INFO") as logs: + self.logger.description_changed(event) + messages = [r.getMessage() for r in logs.records] + self.assertFalse(any("changed type" in m for m in messages)) + + def test_description_changed_warns_no_writable_server(self): + event = MagicMock() + event.previous_description.topology_type = 1 + event.new_description.topology_type = 1 + event.new_description.has_writable_server.return_value = False + event.new_description.has_readable_server.return_value = True + with self.assertLogs(level="WARNING") as logs: + self.logger.description_changed(event) + messages = [r.getMessage() for r in logs.records] + self.assertTrue(any("writable" in m for m in messages)) + + def test_description_changed_warns_no_readable_server(self): + event = MagicMock() + event.previous_description.topology_type = 1 + event.new_description.topology_type = 1 + event.new_description.has_writable_server.return_value = True + event.new_description.has_readable_server.return_value = False + with self.assertLogs(level="WARNING") as logs: + self.logger.description_changed(event) + messages = [r.getMessage() for r in logs.records] + self.assertTrue(any("readable" in m for m in messages)) + + def test_description_changed_warns_both_unavailable(self): + event = MagicMock() + event.previous_description.topology_type = 1 + event.new_description.topology_type = 1 + event.new_description.has_writable_server.return_value = False + event.new_description.has_readable_server.return_value = False + with self.assertLogs(level="WARNING") as logs: + self.logger.description_changed(event) + warning_messages = [r.getMessage() for r in logs.records if r.levelname == "WARNING"] + self.assertEqual(len(warning_messages), 2) + + +class TestConnectionPoolLogger(unittest.TestCase): + def setUp(self): + self.logger = ConnectionPoolLogger() + + def test_pool_created(self): + event = MagicMock() + event.address = ("localhost", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.pool_created(event) + log = logs.records[0].getMessage() + self.assertIn("pool created", log) + self.assertIn("localhost", log) + + def test_pool_ready(self): + event = MagicMock() + event.address = ("localhost", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.pool_ready(event) + log = logs.records[0].getMessage() + self.assertIn("pool ready", log) + self.assertIn("localhost", log) + + def test_pool_cleared(self): + event = MagicMock() + event.address = ("localhost", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.pool_cleared(event) + log = logs.records[0].getMessage() + self.assertIn("pool cleared", log) + self.assertIn("localhost", log) + + def test_pool_closed(self): + event = MagicMock() + event.address = ("localhost", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.pool_closed(event) + log = logs.records[0].getMessage() + self.assertIn("pool closed", log) + self.assertIn("localhost", log) + + def test_connection_created(self): + event = MagicMock() + event.address = ("localhost", 27017) + event.connection_id = 5 + with self.assertLogs(level="INFO") as logs: + self.logger.connection_created(event) + log = logs.records[0].getMessage() + self.assertIn("connection created", log) + self.assertIn("5", log) + self.assertIn("localhost", log) + + def test_connection_ready(self): + event = MagicMock() + event.address = ("localhost", 27017) + event.connection_id = 5 + with self.assertLogs(level="INFO") as logs: + self.logger.connection_ready(event) + log = logs.records[0].getMessage() + self.assertIn("connection setup succeeded", log) + self.assertIn("5", log) + + def test_connection_closed(self): + event = MagicMock() + event.address = ("localhost", 27017) + event.connection_id = 5 + event.reason = "stale" + with self.assertLogs(level="INFO") as logs: + self.logger.connection_closed(event) + log = logs.records[0].getMessage() + self.assertIn("connection closed", log) + self.assertIn("5", log) + self.assertIn("stale", log) + + def test_connection_check_out_started(self): + event = MagicMock() + event.address = ("localhost", 27017) + with self.assertLogs(level="INFO") as logs: + self.logger.connection_check_out_started(event) + log = logs.records[0].getMessage() + self.assertIn("check out started", log) + self.assertIn("localhost", log) + + def test_connection_check_out_failed(self): + event = MagicMock() + event.address = ("localhost", 27017) + event.reason = "timeout" + with self.assertLogs(level="INFO") as logs: + self.logger.connection_check_out_failed(event) + log = logs.records[0].getMessage() + self.assertIn("check out failed", log) + self.assertIn("timeout", log) + self.assertIn("localhost", log) + + def test_connection_checked_out(self): + event = MagicMock() + event.address = ("localhost", 27017) + event.connection_id = 3 + with self.assertLogs(level="INFO") as logs: + self.logger.connection_checked_out(event) + log = logs.records[0].getMessage() + self.assertIn("checked out", log) + self.assertIn("3", log) + self.assertIn("localhost", log) + + def test_connection_checked_in(self): + event = MagicMock() + event.address = ("localhost", 27017) + event.connection_id = 3 + with self.assertLogs(level="INFO") as logs: + self.logger.connection_checked_in(event) + log = logs.records[0].getMessage() + self.assertIn("checked into", log) + self.assertIn("3", log) + self.assertIn("localhost", log) + + +if __name__ == "__main__": + unittest.main() From ab44a21b46a2089d1de68e3c84f73e7d5bed9d28 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Fri, 24 Apr 2026 09:04:02 -0400 Subject: [PATCH 118/129] PYTHON-5780 Increase code coverage for pyopenssl_context.py (#2773) --- test/test_pyopenssl_context.py | 271 +++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 test/test_pyopenssl_context.py diff --git a/test/test_pyopenssl_context.py b/test/test_pyopenssl_context.py new file mode 100644 index 0000000000..c3afaf0159 --- /dev/null +++ b/test/test_pyopenssl_context.py @@ -0,0 +1,271 @@ +# Copyright 2026-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for pyopenssl_context.py. + +These tests require PyOpenSSL (install via: pip install pymongo[ocsp]). +Tests are automatically skipped when PyOpenSSL is not available. +""" + +from __future__ import annotations + +import ssl +import sys +from unittest.mock import patch + +sys.path[0:0] = [""] + +from test import unittest + +try: + from pymongo import pyopenssl_context as _ctx_module + from pymongo.pyopenssl_context import ( + PROTOCOL_SSLv23, + SSLContext, + _is_ip_address, + _ragged_eof, + ) + + _HAVE_PYOPENSSL = True +except ImportError: + _HAVE_PYOPENSSL = False + + +# --------------------------------------------------------------------------- +# Pure functions (no SSL context required) +# --------------------------------------------------------------------------- + + +class TestIsIpAddress(unittest.TestCase): + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_ipv4(self): + self.assertTrue(_is_ip_address("192.168.1.1")) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_ipv6(self): + self.assertTrue(_is_ip_address("::1")) + self.assertTrue(_is_ip_address("2001:db8::1")) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_hostname_is_not_ip(self): + self.assertFalse(_is_ip_address("example.com")) + self.assertFalse(_is_ip_address("localhost")) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_invalid_string_returns_false(self): + self.assertFalse(_is_ip_address("not-an-ip")) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_unicode_error_returns_false(self): + # UnicodeError path: some inputs that can't be decoded. + # ip_address raises UnicodeError for byte strings with non-ASCII. + self.assertFalse(_is_ip_address(b"\xff\xfe")) + + +class TestRaggedEof(unittest.TestCase): + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_matching_args_returns_true(self): + from OpenSSL.SSL import SysCallError + + exc = SysCallError(-1, "Unexpected EOF") + self.assertTrue(_ragged_eof(exc)) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_non_matching_args_returns_false(self): + from OpenSSL.SSL import SysCallError + + exc = SysCallError(0, "something else") + self.assertFalse(_ragged_eof(exc)) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_wrong_code_returns_false(self): + from OpenSSL.SSL import SysCallError + + exc = SysCallError(5, "Unexpected EOF") + self.assertFalse(_ragged_eof(exc)) + + +# --------------------------------------------------------------------------- +# SSLContext — construction and properties +# --------------------------------------------------------------------------- + + +class TestSSLContextConstruction(unittest.TestCase): + def _make(self): + return SSLContext(PROTOCOL_SSLv23) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_protocol_property(self): + ctx = self._make() + self.assertEqual(ctx.protocol, PROTOCOL_SSLv23) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_default_check_hostname(self): + ctx = self._make() + self.assertTrue(ctx.check_hostname) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_set_check_hostname_false(self): + ctx = self._make() + ctx.check_hostname = False + self.assertFalse(ctx.check_hostname) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_set_check_hostname_invalid_raises(self): + ctx = self._make() + with self.assertRaises(TypeError): + ctx.check_hostname = "yes" + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_default_check_ocsp_endpoint(self): + ctx = self._make() + self.assertTrue(ctx.check_ocsp_endpoint) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_set_check_ocsp_endpoint_false(self): + ctx = self._make() + ctx.check_ocsp_endpoint = False + self.assertFalse(ctx.check_ocsp_endpoint) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_verify_mode_roundtrip(self): + ctx = self._make() + ctx.verify_mode = ssl.CERT_REQUIRED + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_verify_mode_cert_none(self): + ctx = self._make() + ctx.verify_mode = ssl.CERT_NONE + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_options_setter_and_getter(self): + ctx = self._make() + from pymongo.pyopenssl_context import OP_NO_SSLv3 + + ctx.options = OP_NO_SSLv3 + self.assertTrue(ctx.options & OP_NO_SSLv3) + + +# --------------------------------------------------------------------------- +# SSLContext._load_certifi +# --------------------------------------------------------------------------- + + +class TestLoadCertifi(unittest.TestCase): + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_raises_when_certifi_unavailable(self): + from pymongo.errors import ConfigurationError + + ctx = SSLContext(PROTOCOL_SSLv23) + with patch.object(_ctx_module, "_HAVE_CERTIFI", False): + with self.assertRaises(ConfigurationError) as exc_ctx: + ctx._load_certifi() + self.assertIn("certifi", str(exc_ctx.exception)) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_loads_when_certifi_available(self): + if not _ctx_module._HAVE_CERTIFI: + self.skipTest("certifi not installed") + ctx = SSLContext(PROTOCOL_SSLv23) + ctx.verify_mode = ssl.CERT_NONE + # Should not raise. + ctx._load_certifi() + + +# --------------------------------------------------------------------------- +# SSLContext.load_default_certs — platform branching +# --------------------------------------------------------------------------- + + +class TestLoadDefaultCerts(unittest.TestCase): + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_darwin_calls_load_certifi(self): + with patch.object(_ctx_module._sys, "platform", "darwin"): + with patch.object(SSLContext, "_load_certifi") as mock_certifi: + with patch("OpenSSL.SSL.Context.set_default_verify_paths"): + ctx = SSLContext(PROTOCOL_SSLv23) + ctx.load_default_certs() + mock_certifi.assert_called() + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_win32_calls_load_wincerts(self): + with patch.object(_ctx_module._sys, "platform", "win32"): + with patch.object(SSLContext, "_load_wincerts") as mock_wincerts: + with patch("OpenSSL.SSL.Context.set_default_verify_paths"): + ctx = SSLContext(PROTOCOL_SSLv23) + ctx.load_default_certs() + calls = [call.args[0] for call in mock_wincerts.call_args_list] + self.assertIn("CA", calls) + self.assertIn("ROOT", calls) + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_win32_falls_back_to_certifi_on_exception(self): + with patch.object(_ctx_module._sys, "platform", "win32"): + with patch.object(SSLContext, "_load_wincerts", side_effect=Exception("no certs")): + with patch.object(SSLContext, "_load_certifi") as mock_certifi: + with patch("OpenSSL.SSL.Context.set_default_verify_paths"): + ctx = SSLContext(PROTOCOL_SSLv23) + ctx.load_default_certs() + mock_certifi.assert_called() + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_linux_no_certifi_call(self): + with patch.object(_ctx_module._sys, "platform", "linux"): + with patch.object(SSLContext, "_load_certifi") as mock_certifi: + with patch("OpenSSL.SSL.Context.set_default_verify_paths"): + ctx = SSLContext(PROTOCOL_SSLv23) + ctx.load_default_certs() + mock_certifi.assert_not_called() + + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_calls_set_default_verify_paths(self): + with patch.object(_ctx_module._sys, "platform", "linux"): + ctx = SSLContext(PROTOCOL_SSLv23) + with patch.object(ctx._ctx, "set_default_verify_paths") as mock_sdvp: + ctx.load_default_certs() + mock_sdvp.assert_called_once() + + +# --------------------------------------------------------------------------- +# SSLContext.set_default_verify_paths +# --------------------------------------------------------------------------- + + +class TestSetDefaultVerifyPaths(unittest.TestCase): + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_delegates_to_ctx(self): + ctx = SSLContext(PROTOCOL_SSLv23) + with patch.object(ctx._ctx, "set_default_verify_paths") as mock_sdvp: + ctx.set_default_verify_paths() + mock_sdvp.assert_called_once() + + +# --------------------------------------------------------------------------- +# SSLContext.load_verify_locations +# --------------------------------------------------------------------------- + + +class TestLoadVerifyLocations(unittest.TestCase): + @unittest.skipUnless(_HAVE_PYOPENSSL, "PyOpenSSL is not available.") + def test_delegates_to_ctx(self): + ctx = SSLContext(PROTOCOL_SSLv23) + with patch.object(ctx._ctx, "load_verify_locations") as mock_lvl: + ctx.load_verify_locations(cafile="/tmp/ca.pem") + mock_lvl.assert_called_once_with("/tmp/ca.pem", None) + + +if __name__ == "__main__": + unittest.main() From b3f1c4befba72de19452a98cefc1237824c86855 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Mon, 27 Apr 2026 15:55:18 -0400 Subject: [PATCH 119/129] [Spec Resync] Remove stale spec patches for closed tickets (#2782) --- .evergreen/spec-patch/PYTHON-2673.patch | 64 ------------------------- .evergreen/spec-patch/PYTHON-3712.patch | 14 ------ .evergreen/spec-patch/PYTHON-4261.patch | 61 ----------------------- 3 files changed, 139 deletions(-) delete mode 100644 .evergreen/spec-patch/PYTHON-2673.patch delete mode 100644 .evergreen/spec-patch/PYTHON-3712.patch delete mode 100644 .evergreen/spec-patch/PYTHON-4261.patch diff --git a/.evergreen/spec-patch/PYTHON-2673.patch b/.evergreen/spec-patch/PYTHON-2673.patch deleted file mode 100644 index 868538f7b7..0000000000 --- a/.evergreen/spec-patch/PYTHON-2673.patch +++ /dev/null @@ -1,64 +0,0 @@ -diff --git a/test/load_balancer/cursors.json b/test/load_balancer/cursors.json -index 43e4fbb4f..4e2a55fd4 100644 ---- a/test/load_balancer/cursors.json -+++ b/test/load_balancer/cursors.json -@@ -376,7 +376,7 @@ - ] - }, - { -+ "description": "pinned connections are not returned after an network error during getMore", -- "description": "pinned connections are returned after an network error during getMore", - "operations": [ - { - "name": "failPoint", -@@ -440,7 +440,7 @@ - "object": "testRunner", - "arguments": { - "client": "client0", -+ "connections": 1 -- "connections": 0 - } - }, - { -@@ -659,7 +659,7 @@ - ] - }, - { -+ "description": "pinned connections are not returned to the pool after a non-network error on getMore", -- "description": "pinned connections are returned to the pool after a non-network error on getMore", - "operations": [ - { - "name": "failPoint", -@@ -715,7 +715,7 @@ - "object": "testRunner", - "arguments": { - "client": "client0", -+ "connections": 1 -- "connections": 0 - } - }, - { -diff --git a/test/load_balancer/sdam-error-handling.json b/test/load_balancer/sdam-error-handling.json -index 63aabc04d..462fa0aac 100644 ---- a/test/load_balancer/sdam-error-handling.json -+++ b/test/load_balancer/sdam-error-handling.json -@@ -366,6 +366,9 @@ - { - "connectionCreatedEvent": {} - }, -+ { -+ "poolClearedEvent": {} -+ }, - { - "connectionClosedEvent": { - "reason": "error" -@@ -378,9 +375,6 @@ - "connectionCheckOutFailedEvent": { - "reason": "connectionError" - } -- }, -- { -- "poolClearedEvent": {} - } - ] - } diff --git a/.evergreen/spec-patch/PYTHON-3712.patch b/.evergreen/spec-patch/PYTHON-3712.patch deleted file mode 100644 index b48c05124c..0000000000 --- a/.evergreen/spec-patch/PYTHON-3712.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/test/discovery_and_monitoring/unified/serverMonitoringMode.json b/test/discovery_and_monitoring/unified/serverMonitoringMode.json -index e44fad1b..4b492f7d 100644 ---- a/test/discovery_and_monitoring/unified/serverMonitoringMode.json -+++ b/test/discovery_and_monitoring/unified/serverMonitoringMode.json -@@ -5,7 +5,8 @@ - { - "topologies": [ - "single", -- "sharded" -+ "sharded", -+ "sharded-replicaset" - ], - "serverless": "forbid" - } diff --git a/.evergreen/spec-patch/PYTHON-4261.patch b/.evergreen/spec-patch/PYTHON-4261.patch deleted file mode 100644 index e4ffc5ce9f..0000000000 --- a/.evergreen/spec-patch/PYTHON-4261.patch +++ /dev/null @@ -1,61 +0,0 @@ -diff --git a/test/server_selection_logging/replica-set.json b/test/server_selection_logging/replica-set.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/replica-set.json -+++ b/test/server_selection_logging/replica-set.json -@@ -184,7 +184,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -diff --git a/test/server_selection_logging/standalone.json b/test/server_selection_logging/standalone.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/standalone.json -+++ b/test/server_selection_logging/standalone.json -@@ -191,7 +191,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -diff --git a/test/server_selection_logging/sharded.json b/test/server_selection_logging/sharded.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/sharded.json -+++ b/test/server_selection_logging/sharded.json -@@ -193,7 +193,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -diff --git a/test/server_selection_logging/sharded.json b/test/server_selection_logging/operation-id.json -index 830b1ea51..5eba784bf 100644 ---- a/test/server_selection_logging/operation-id.json -+++ b/test/server_selection_logging/operation-id.json -@@ -197,7 +197,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", -@@ -383,7 +383,7 @@ - } - }, - { -- "level": "debug", -+ "level": "info", - "component": "serverSelection", - "data": { - "message": "Waiting for suitable server to become available", From 64edd22d73e801e61b6a46c0b19e99e61bfdc517 Mon Sep 17 00:00:00 2001 From: "mongodb-drivers-pr-bot[bot]" <147046816+mongodb-drivers-pr-bot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:56:10 -0400 Subject: [PATCH 120/129] [Spec Resync] 04-20-2026 (#2766) Co-authored-by: Cloud User Co-authored-by: Jeffrey 'Alex' Clark --- .evergreen/spec-patch/PYTHON-5517.patch | 31 -- .../change-streams-disambiguatedPaths.json | 85 +++ .../unified/fle2v2-InsertFind-keyAltName.json | 485 ++++++++++++++++++ .../pool-create-min-size-error.json | 4 +- ...ressure-network-error-fail-replicaset.json | 142 ----- ...ackpressure-network-error-fail-single.json | 142 ----- ...ssure-network-timeout-fail-replicaset.json | 145 ------ ...kpressure-network-timeout-fail-single.json | 145 ------ ...ged-on-min-pool-size-population-error.json | 106 ---- 9 files changed, 573 insertions(+), 712 deletions(-) delete mode 100644 .evergreen/spec-patch/PYTHON-5517.patch create mode 100644 test/client-side-encryption/spec/unified/fle2v2-InsertFind-keyAltName.json delete mode 100644 test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json delete mode 100644 test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json delete mode 100644 test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json delete mode 100644 test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json delete mode 100644 test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json diff --git a/.evergreen/spec-patch/PYTHON-5517.patch b/.evergreen/spec-patch/PYTHON-5517.patch deleted file mode 100644 index adf453fd1f..0000000000 --- a/.evergreen/spec-patch/PYTHON-5517.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/test/discovery_and_monitoring/errors/error_handling_handshake.json b/test/discovery_and_monitoring/errors/error_handling_handshake.json -index 56ca7d113..bf83f46f6 100644 ---- a/test/discovery_and_monitoring/errors/error_handling_handshake.json -+++ b/test/discovery_and_monitoring/errors/error_handling_handshake.json -@@ -97,14 +97,22 @@ - "outcome": { - "servers": { - "a:27017": { -- "type": "Unknown", -- "topologyVersion": null, -+ "type": "RSPrimary", -+ "setName": "rs", -+ "topologyVersion": { -+ "processId": { -+ "$oid": "000000000000000000000001" -+ }, -+ "counter": { -+ "$numberLong": "1" -+ } -+ }, - "pool": { -- "generation": 1 -+ "generation": 0 - } - } - }, -- "topologyType": "ReplicaSetNoPrimary", -+ "topologyType": "ReplicaSetWithPrimary", - "logicalSessionTimeoutMinutes": null, - "setName": "rs" - } diff --git a/test/change_streams/unified/change-streams-disambiguatedPaths.json b/test/change_streams/unified/change-streams-disambiguatedPaths.json index a8667b5436..df7422295a 100644 --- a/test/change_streams/unified/change-streams-disambiguatedPaths.json +++ b/test/change_streams/unified/change-streams-disambiguatedPaths.json @@ -42,6 +42,91 @@ } ], "tests": [ + { + "description": "disambiguatedPaths is not present when showExpandedEvents is false/unset", + "runOnRequirements": [ + { + "minServerVersion": "6.1.0", + "maxServerVersion": "8.1.99", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + }, + { + "minServerVersion": "8.2.1", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "a": { + "1": 1 + } + } + } + }, + { + "name": "createChangeStream", + "object": "collection0", + "arguments": { + "pipeline": [] + }, + "saveResultAsEntity": "changeStream0" + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "a.1": 2 + } + } + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "changeStream0", + "expectResult": { + "operationType": "update", + "ns": { + "db": "database0", + "coll": "collection0" + }, + "updateDescription": { + "updatedFields": { + "$$exists": true + }, + "removedFields": { + "$$exists": true + }, + "truncatedArrays": { + "$$exists": true + }, + "disambiguatedPaths": { + "$$exists": false + } + } + } + } + ] + }, { "description": "disambiguatedPaths is present on updateDescription when an ambiguous path is present", "operations": [ diff --git a/test/client-side-encryption/spec/unified/fle2v2-InsertFind-keyAltName.json b/test/client-side-encryption/spec/unified/fle2v2-InsertFind-keyAltName.json new file mode 100644 index 0000000000..ef13a3b2db --- /dev/null +++ b/test/client-side-encryption/spec/unified/fle2v2-InsertFind-keyAltName.json @@ -0,0 +1,485 @@ +{ + "description": "fle2v2-InsertFind-keyAltName", + "schemaVersion": "1.25", + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "csfle": { + "minLibmongocryptVersion": "1.18.0" + } + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + }, + "encryptedFieldsMap": { + "default.default": { + "fields": [ + { + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + }, + "keyAltName": "altname" + } + ] + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "coll", + "database": "db", + "collectionName": "default" + } + }, + { + "client": { + "id": "client_unencrypted", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db_unencrypted", + "client": "client_unencrypted", + "databaseName": "default" + } + }, + { + "collection": { + "id": "coll_unencrypted", + "database": "db_unencrypted", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "default", + "collectionName": "default", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + } + ] + } + } + }, + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + }, + "keyAltNames": [ + "altname" + ] + } + ] + } + ], + "tests": [ + { + "description": "Insert and find FLE2 indexed field", + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedIndexed": "123" + } + }, + "object": "coll" + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedIndexed": "123" + } + }, + "object": "coll", + "expectResult": [ + { + "_id": 1, + "encryptedIndexed": "123" + } + ] + }, + { + "name": "find", + "object": "coll_unencrypted", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "encryptedIndexed": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", + "subType": "00" + } + } + ] + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [] + } + }, + { + "keyAltNames": { + "$in": [ + "altname" + ] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedIndexed": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + } + ] + } + } + } + }, + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "default", + "filter": { + "encryptedIndexed": { + "$eq": { + "$binary": { + "base64": "DIkAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVsACAAAAAApJtKPW4+o9B7gAynNLL26jtlB4+hq5TXResijcYet8USY20AAAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + } + ] + } + } + } + }, + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "Create translates keyAltName", + "operations": [ + { + "name": "dropCollection", + "object": "db", + "arguments": { + "collection": "default" + } + }, + { + "name": "createCollection", + "object": "db", + "arguments": { + "collection": "default" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "enxcol_.default.esc" + }, + "commandName": "drop" + } + }, + { + "commandStartedEvent": { + "command": { + "drop": "enxcol_.default.ecoc" + }, + "commandName": "drop" + } + }, + { + "commandStartedEvent": { + "command": { + "drop": "default" + }, + "commandName": "drop" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "enxcol_.default.esc", + "clusteredIndex": { + "key": { + "_id": 1 + }, + "unique": true + } + }, + "commandName": "create" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "enxcol_.default.ecoc", + "clusteredIndex": { + "key": { + "_id": 1 + }, + "unique": true + } + }, + "commandName": "create" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [] + } + }, + { + "keyAltNames": { + "$in": [ + "altname" + ] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "default", + "encryptedFields": { + "fields": [ + { + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + }, + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + } + ] + } + }, + "commandName": "create" + } + }, + { + "commandStartedEvent": { + "command": { + "createIndexes": "default", + "indexes": [ + { + "name": "__safeContent___1", + "key": { + "__safeContent__": 1 + } + } + ] + }, + "commandName": "createIndexes" + } + } + ] + } + ] + } + ] +} diff --git a/test/connection_monitoring/pool-create-min-size-error.json b/test/connection_monitoring/pool-create-min-size-error.json index 4334ce2571..5c8ad02dbd 100644 --- a/test/connection_monitoring/pool-create-min-size-error.json +++ b/test/connection_monitoring/pool-create-min-size-error.json @@ -9,7 +9,9 @@ ], "failPoint": { "configureFailPoint": "failCommand", - "mode": "alwaysOn", + "mode": { + "times": 50 + }, "data": { "failCommands": [ "isMaster", diff --git a/test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json b/test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json deleted file mode 100644 index ccaea8d135..0000000000 --- a/test/discovery_and_monitoring/unified/backpressure-network-error-fail-replicaset.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "description": "backpressure-network-error-fail-replicaset", - "schemaVersion": "1.17", - "runOnRequirements": [ - { - "minServerVersion": "4.4", - "serverless": "forbid", - "topologies": [ - "replicaset" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "setupClient", - "useMultipleMongoses": false - } - } - ], - "initialData": [ - { - "collectionName": "backpressure-network-error-fail", - "databaseName": "sdam-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "tests": [ - { - "description": "apply backpressure on network connection errors during connection establishment", - "operations": [ - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "serverDescriptionChangedEvent", - "poolClearedEvent" - ], - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 1000000, - "serverMonitoringMode": "poll", - "appname": "backpressureNetworkErrorFailTest" - } - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "sdam-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "backpressure-network-error-fail" - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "RSPrimary" - } - } - }, - "count": 1 - } - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": "alwaysOn", - "data": { - "failCommands": [ - "isMaster", - "hello" - ], - "appName": "backpressureNetworkErrorFailTest", - "closeConnection": true - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - "expectError": { - "isError": true, - "errorLabelsContain": [ - "SystemOverloadedError", - "RetryableError" - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [] - } - ] - } - ] -} diff --git a/test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json b/test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json deleted file mode 100644 index c1ff67c732..0000000000 --- a/test/discovery_and_monitoring/unified/backpressure-network-error-fail-single.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "description": "backpressure-network-error-fail-single", - "schemaVersion": "1.17", - "runOnRequirements": [ - { - "minServerVersion": "4.4", - "serverless": "forbid", - "topologies": [ - "single" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "setupClient", - "useMultipleMongoses": false - } - } - ], - "initialData": [ - { - "collectionName": "backpressure-network-error-fail", - "databaseName": "sdam-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "tests": [ - { - "description": "apply backpressure on network connection errors during connection establishment", - "operations": [ - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "serverDescriptionChangedEvent", - "poolClearedEvent" - ], - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 1000000, - "serverMonitoringMode": "poll", - "appname": "backpressureNetworkErrorFailTest" - } - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "sdam-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "backpressure-network-error-fail" - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "Standalone" - } - } - }, - "count": 1 - } - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": "alwaysOn", - "data": { - "failCommands": [ - "isMaster", - "hello" - ], - "appName": "backpressureNetworkErrorFailTest", - "closeConnection": true - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - "expectError": { - "isError": true, - "errorLabelsContain": [ - "SystemOverloadedError", - "RetryableError" - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [] - } - ] - } - ] -} diff --git a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json deleted file mode 100644 index 35b088f422..0000000000 --- a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-replicaset.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "description": "backpressure-network-timeout-error-replicaset", - "schemaVersion": "1.17", - "runOnRequirements": [ - { - "minServerVersion": "4.4", - "serverless": "forbid", - "topologies": [ - "replicaset" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "setupClient", - "useMultipleMongoses": false - } - } - ], - "initialData": [ - { - "collectionName": "backpressure-network-timeout-error", - "databaseName": "sdam-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "tests": [ - { - "description": "apply backpressure on network timeout error during connection establishment", - "operations": [ - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "serverDescriptionChangedEvent", - "poolClearedEvent" - ], - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 1000000, - "appname": "backpressureNetworkTimeoutErrorTest", - "serverMonitoringMode": "poll", - "connectTimeoutMS": 250, - "socketTimeoutMS": 250 - } - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "sdam-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "backpressure-network-timeout-error" - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "RSPrimary" - } - } - }, - "count": 1 - } - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": "alwaysOn", - "data": { - "failCommands": [ - "isMaster", - "hello" - ], - "blockConnection": true, - "blockTimeMS": 500, - "appName": "backpressureNetworkTimeoutErrorTest" - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - "expectError": { - "isError": true, - "errorLabelsContain": [ - "SystemOverloadedError", - "RetryableError" - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [] - } - ] - } - ] -} diff --git a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json b/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json deleted file mode 100644 index 54b11d4d5b..0000000000 --- a/test/discovery_and_monitoring/unified/backpressure-network-timeout-fail-single.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "description": "backpressure-network-timeout-error-single", - "schemaVersion": "1.17", - "runOnRequirements": [ - { - "minServerVersion": "4.4", - "serverless": "forbid", - "topologies": [ - "single" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "setupClient", - "useMultipleMongoses": false - } - } - ], - "initialData": [ - { - "collectionName": "backpressure-network-timeout-error", - "databaseName": "sdam-tests", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - ], - "tests": [ - { - "description": "apply backpressure on network timeout error during connection establishment", - "operations": [ - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "useMultipleMongoses": false, - "observeEvents": [ - "serverDescriptionChangedEvent", - "poolClearedEvent" - ], - "uriOptions": { - "retryWrites": false, - "heartbeatFrequencyMS": 1000000, - "appname": "backpressureNetworkTimeoutErrorTest", - "serverMonitoringMode": "poll", - "connectTimeoutMS": 250, - "socketTimeoutMS": 250 - } - } - }, - { - "database": { - "id": "database", - "client": "client", - "databaseName": "sdam-tests" - } - }, - { - "collection": { - "id": "collection", - "database": "database", - "collectionName": "backpressure-network-timeout-error" - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": { - "newDescription": { - "type": "Standalone" - } - } - }, - "count": 1 - } - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": "alwaysOn", - "data": { - "failCommands": [ - "isMaster", - "hello" - ], - "blockConnection": true, - "blockTimeMS": 500, - "appName": "backpressureNetworkTimeoutErrorTest" - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - "expectError": { - "isError": true, - "errorLabelsContain": [ - "SystemOverloadedError", - "RetryableError" - ] - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "cmap", - "events": [] - } - ] - } - ] -} diff --git a/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json b/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json deleted file mode 100644 index f0597124b7..0000000000 --- a/test/discovery_and_monitoring/unified/backpressure-server-description-unchanged-on-min-pool-size-population-error.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "description": "backpressure-server-description-unchanged-on-min-pool-size-population-error", - "schemaVersion": "1.17", - "runOnRequirements": [ - { - "minServerVersion": "4.4", - "serverless": "forbid", - "topologies": [ - "single" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "setupClient", - "useMultipleMongoses": false - } - } - ], - "tests": [ - { - "description": "the server description is not changed on handshake error during minPoolSize population", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "setupClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "skip": 1 - }, - "data": { - "failCommands": [ - "hello", - "isMaster" - ], - "appName": "authErrorTest", - "closeConnection": true - } - } - } - }, - { - "name": "createEntities", - "object": "testRunner", - "arguments": { - "entities": [ - { - "client": { - "id": "client", - "observeEvents": [ - "serverDescriptionChangedEvent", - "connectionClosedEvent" - ], - "uriOptions": { - "appname": "authErrorTest", - "minPoolSize": 5, - "maxConnecting": 1, - "serverMonitoringMode": "poll", - "heartbeatFrequencyMS": 1000000 - } - } - } - ] - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "serverDescriptionChangedEvent": {} - }, - "count": 1 - } - }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "client": "client", - "event": { - "connectionClosedEvent": {} - }, - "count": 1 - } - } - ], - "expectEvents": [ - { - "client": "client", - "eventType": "sdam", - "events": [ - { - "serverDescriptionChangedEvent": {} - } - ] - } - ] - } - ] -} From e67931dff778dd57a1282c48b82899544da6a61c Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Mon, 27 Apr 2026 19:45:36 -0400 Subject: [PATCH 121/129] PYTHON-5776 Add documentation comments to justfile recipes (#2784) --- justfile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/justfile b/justfile index 3a8e216dbc..55ff9e74dd 100644 --- a/justfile +++ b/justfile @@ -16,64 +16,78 @@ default: resync: @uv sync --quiet +# Set up the development environment install: bash .evergreen/scripts/setup-dev-env.sh +# Build the HTML documentation [group('docs')] docs: && resync {{docs_run}} sphinx-build -W -b html doc {{doc_build}}/html +# Serve the docs locally with live-reload [group('docs')] docs-serve: && resync {{docs_run}} sphinx-autobuild -W -b html doc --watch ./pymongo --watch ./bson --watch ./gridfs {{doc_build}}/serve +# Check documentation hyperlinks for broken URLs [group('docs')] docs-linkcheck: && resync {{docs_run}} sphinx-build -E -b linkcheck doc {{doc_build}}/linkcheck +# Run mypy and pyright [group('typing')] typing: && resync just typing-mypy just typing-pyright +# Run mypy against the library source and test suite [group('typing')] typing-mypy: && resync {{typing_run}} python -m mypy {{mypy_args}} bson gridfs tools pymongo {{typing_run}} python -m mypy {{mypy_args}} --config-file mypy_test.ini test {{typing_run}} python -m mypy {{mypy_args}} test/test_typing.py test/test_typing_strict.py +# Run pyright against the typing test files [group('typing')] typing-pyright: && resync {{typing_run}} python -m pyright test/test_typing.py test/test_typing_strict.py {{typing_run}} python -m pyright -p strict_pyrightconfig.json test/test_typing_strict.py +# Run all pre-commit hooks across the repository [group('lint')] lint *args="": && resync uvx pre-commit run --all-files {{args}} +# Run shellcheck, doc8, and slotscheck [group('lint')] lint-manual *args="": && resync uvx pre-commit run --all-files --hook-stage manual {{args}} +# Run pytest (e.g. just test test/test_uri_parser.py) [group('test')] test *args="-v --durations=5 --maxfail=10": && resync #!/usr/bin/env bash set -euo pipefail uv run ${USE_ACTIVE_VENV:+--active} --extra test python -m pytest {{args}} +# Run the BSON test suite with numpy [group('test')] test-numpy *args="": && resync just setup-tests numpy {{args}} just run-tests test/test_bson.py +# Run tests via the Evergreen test runner script [group('test')] run-tests *args: && resync bash ./.evergreen/run-tests.sh {{args}} +# Set up the test environment (auth, TLS, etc.) [group('test')] setup-tests *args="": bash .evergreen/scripts/setup-tests.sh {{args}} +# Tear down resources created by setup-tests [group('test')] teardown-tests: bash .evergreen/scripts/teardown-tests.sh @@ -82,25 +96,30 @@ teardown-tests: integration-tests: bash integration_tests/run.sh +# Run the full test suite with coverage [group('test')] test-coverage *args="": just setup-tests --cov just run-tests {{args}} +# Print the coverage summary to the terminal [group('coverage')] coverage-report: uv tool run --with "coverage[toml]" coverage report +# Generate an HTML coverage report in htmlcov/ [group('coverage')] coverage-html: uv tool run --with "coverage[toml]" coverage html @echo "Coverage report generated in htmlcov/index.html" +# Generate an XML coverage report at coverage.xml [group('coverage')] coverage-xml: uv tool run --with "coverage[toml]" coverage xml @echo "Coverage report generated in coverage.xml" +# Start a MongoDB server via drivers-evergreen-tools [group('server')] run-server *args="": bash .evergreen/scripts/run-server.sh {{args}} From c30eff1291252a6ab3321fe0d70677adaa5e5616 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 5 May 2026 11:40:19 -0400 Subject: [PATCH 122/129] =?UTF-8?q?PYTHON-5811=20-=20Change=20stream=20eve?= =?UTF-8?q?nts=20are=20not=20emitted=20for=20timeseries=20as=20=E2=80=A6?= =?UTF-8?q?=20(#2791)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unified/change-streams-nsType.json | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/test/change_streams/unified/change-streams-nsType.json b/test/change_streams/unified/change-streams-nsType.json index 1861c9a5e0..877ca9c007 100644 --- a/test/change_streams/unified/change-streams-nsType.json +++ b/test/change_streams/unified/change-streams-nsType.json @@ -63,47 +63,6 @@ } ] }, - { - "description": "nsType is present when creating timeseries", - "operations": [ - { - "name": "dropCollection", - "object": "database0", - "arguments": { - "collection": "foo" - } - }, - { - "name": "createChangeStream", - "object": "database0", - "arguments": { - "pipeline": [], - "showExpandedEvents": true - }, - "saveResultAsEntity": "changeStream0" - }, - { - "name": "createCollection", - "object": "database0", - "arguments": { - "collection": "foo", - "timeseries": { - "timeField": "time", - "metaField": "meta", - "granularity": "minutes" - } - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "changeStream0", - "expectResult": { - "operationType": "create", - "nsType": "timeseries" - } - } - ] - }, { "description": "nsType is present when creating views", "operations": [ From 575d75f4d3c690bb4b5562e7bd896e5547548f96 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 5 May 2026 13:41:10 -0400 Subject: [PATCH 123/129] =?UTF-8?q?PYTHON-5813=20-=20Skip=20QE=20prefixPre?= =?UTF-8?q?view=20and=20suffixPreview=20tests=20on=20server=E2=80=A6=20(#2?= =?UTF-8?q?792)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/asynchronous/test_encryption.py | 1 + .../spec/unified/QE-Text-cleanupStructuredEncryptionData.json | 1 + .../spec/unified/QE-Text-compactStructuredEncryptionData.json | 1 + .../spec/unified/QE-Text-prefixPreview.json | 1 + .../spec/unified/QE-Text-substringPreview.json | 2 +- .../spec/unified/QE-Text-suffixPreview.json | 1 + test/test_encryption.py | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 9650f7043f..1503f54c76 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -3326,6 +3326,7 @@ async def test_collection_name_collision(self): class TestExplicitTextEncryptionProse(AsyncEncryptionIntegrationTest): @async_client_context.require_no_standalone @async_client_context.require_version_min(8, 2, -1) + @async_client_context.require_version_max(8, 99, 99) @async_client_context.require_libmongocrypt_min(1, 15, 1) @async_client_context.require_pymongocrypt_min(1, 16, 0) async def asyncSetUp(self): diff --git a/test/client-side-encryption/spec/unified/QE-Text-cleanupStructuredEncryptionData.json b/test/client-side-encryption/spec/unified/QE-Text-cleanupStructuredEncryptionData.json index 24f33ab3ec..fd74573ea2 100644 --- a/test/client-side-encryption/spec/unified/QE-Text-cleanupStructuredEncryptionData.json +++ b/test/client-side-encryption/spec/unified/QE-Text-cleanupStructuredEncryptionData.json @@ -4,6 +4,7 @@ "runOnRequirements": [ { "minServerVersion": "8.2.0", + "maxServerVersion": "8.99.99", "topologies": [ "replicaset", "sharded", diff --git a/test/client-side-encryption/spec/unified/QE-Text-compactStructuredEncryptionData.json b/test/client-side-encryption/spec/unified/QE-Text-compactStructuredEncryptionData.json index c7abfe2d4b..a89ab96fc4 100644 --- a/test/client-side-encryption/spec/unified/QE-Text-compactStructuredEncryptionData.json +++ b/test/client-side-encryption/spec/unified/QE-Text-compactStructuredEncryptionData.json @@ -4,6 +4,7 @@ "runOnRequirements": [ { "minServerVersion": "8.2.0", + "maxServerVersion": "8.99.99", "topologies": [ "replicaset", "sharded", diff --git a/test/client-side-encryption/spec/unified/QE-Text-prefixPreview.json b/test/client-side-encryption/spec/unified/QE-Text-prefixPreview.json index 7279385743..c193608e88 100644 --- a/test/client-side-encryption/spec/unified/QE-Text-prefixPreview.json +++ b/test/client-side-encryption/spec/unified/QE-Text-prefixPreview.json @@ -4,6 +4,7 @@ "runOnRequirements": [ { "minServerVersion": "8.2.0", + "maxServerVersion": "8.99.99", "topologies": [ "replicaset", "sharded", diff --git a/test/client-side-encryption/spec/unified/QE-Text-substringPreview.json b/test/client-side-encryption/spec/unified/QE-Text-substringPreview.json index 6a8f133eac..7787194fc6 100644 --- a/test/client-side-encryption/spec/unified/QE-Text-substringPreview.json +++ b/test/client-side-encryption/spec/unified/QE-Text-substringPreview.json @@ -126,7 +126,7 @@ ], "tests": [ { - "description": "Insert QE suffixPreview", + "description": "Insert QE substringPreview", "operations": [ { "name": "insertOne", diff --git a/test/client-side-encryption/spec/unified/QE-Text-suffixPreview.json b/test/client-side-encryption/spec/unified/QE-Text-suffixPreview.json index deec5e63b0..2de5cde4a4 100644 --- a/test/client-side-encryption/spec/unified/QE-Text-suffixPreview.json +++ b/test/client-side-encryption/spec/unified/QE-Text-suffixPreview.json @@ -4,6 +4,7 @@ "runOnRequirements": [ { "minServerVersion": "8.2.0", + "maxServerVersion": "8.99.99", "topologies": [ "replicaset", "sharded", diff --git a/test/test_encryption.py b/test/test_encryption.py index af9f2e3df7..0993362273 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -3308,6 +3308,7 @@ def test_collection_name_collision(self): class TestExplicitTextEncryptionProse(EncryptionIntegrationTest): @client_context.require_no_standalone @client_context.require_version_min(8, 2, -1) + @client_context.require_version_max(8, 99, 99) @client_context.require_libmongocrypt_min(1, 15, 1) @client_context.require_pymongocrypt_min(1, 16, 0) def setUp(self): From 900d9c79103190b414e8983fc53e195b1224dc43 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 6 May 2026 13:10:13 -0400 Subject: [PATCH 124/129] =?UTF-8?q?PYTHON-5436=20-=20Always=20include=20se?= =?UTF-8?q?ssion=20on=20getMores=20if=20the=20initial=20curso=E2=80=A6=20(?= =?UTF-8?q?#2794)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pymongo/asynchronous/client_session.py | 6 +++- pymongo/synchronous/client_session.py | 6 +++- test/asynchronous/test_session.py | 38 +++++++++++++++++++++++--- test/test_session.py | 38 +++++++++++++++++++++++--- 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index 31e6ceb386..9fbd10c8e9 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -1101,7 +1101,11 @@ def _apply_to( read_preference: _ServerMode, conn: AsyncConnection, ) -> None: - if not conn.supports_sessions: + # getMores must be sent with a session if the cursor was opened with one + operation = next(iter(command)) + if not conn.supports_sessions and ( + isinstance(self._server_session, _EmptyServerSession) or operation != "getMore" + ): if not self._implicit: raise ConfigurationError("Sessions are not supported by this MongoDB deployment") return diff --git a/pymongo/synchronous/client_session.py b/pymongo/synchronous/client_session.py index 3165dd52b7..7563850843 100644 --- a/pymongo/synchronous/client_session.py +++ b/pymongo/synchronous/client_session.py @@ -1097,7 +1097,11 @@ def _apply_to( read_preference: _ServerMode, conn: Connection, ) -> None: - if not conn.supports_sessions: + # getMores must be sent with a session if the cursor was opened with one + operation = next(iter(command)) + if not conn.supports_sessions and ( + isinstance(self._server_session, _EmptyServerSession) or operation != "getMore" + ): if not self._implicit: raise ConfigurationError("Sessions are not supported by this MongoDB deployment") return diff --git a/test/asynchronous/test_session.py b/test/asynchronous/test_session.py index 404a69fdee..13ce578671 100644 --- a/test/asynchronous/test_session.py +++ b/test/asynchronous/test_session.py @@ -15,7 +15,6 @@ """Test the client_session module.""" from __future__ import annotations -import asyncio import copy import sys import time @@ -24,8 +23,6 @@ from test.asynchronous.helpers import ExceptionCatchingTask from typing import Any, Callable, List, Set, Tuple -from pymongo.synchronous.mongo_client import MongoClient - sys.path[0:0] = [""] from test.asynchronous import ( @@ -45,7 +42,7 @@ from bson import DBRef from gridfs.asynchronous.grid_file import AsyncGridFS, AsyncGridFSBucket -from pymongo import ASCENDING, AsyncMongoClient, _csot, monitoring +from pymongo import ASCENDING, AsyncMongoClient, monitoring from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.cursor import AsyncCursor from pymongo.asynchronous.helpers import anext @@ -938,6 +935,39 @@ async def test_session_binding_end_session(self): await s2.end_session() + async def test_getmore_preserves_lsid_after_session_support_lost(self): + listener = OvertCommandListener() + client = await self.async_rs_or_single_client(event_listeners=[listener], maxPoolSize=1) + coll = client.pymongo_test.test + await coll.drop() + await coll.insert_many([{"x": i} for i in range(10)]) + self.addAsyncCleanup(coll.drop) + + async with client.start_session() as s: + cursor = coll.find({}, batch_size=2, session=s) + await anext(cursor) + + find_event = next(e for e in listener.started_events if e.command_name == "find") + lsid = find_event.command["lsid"] + + # Simulate a node stepping down: mark idle connections as not supporting sessions. + for server in client._topology._servers.values(): + for conn in server.pool.conns: + conn.supports_sessions = False + + listener.reset() + await cursor.to_list() + + getmore_events = [e for e in listener.started_events if e.command_name == "getMore"] + self.assertGreater(len(getmore_events), 0, "expected at least one getMore command") + for event in getmore_events: + self.assertIn( + "lsid", event.command, "getMore must include lsid when session is materialized" + ) + self.assertEqual( + lsid, event.command["lsid"], "getMore lsid must match the session lsid from find" + ) + class TestCausalConsistency(AsyncUnitTest): listener: SessionTestListener diff --git a/test/test_session.py b/test/test_session.py index 3963f88da0..cf071df49a 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -15,7 +15,6 @@ """Test the client_session module.""" from __future__ import annotations -import asyncio import copy import sys import time @@ -24,8 +23,6 @@ from test.helpers import ExceptionCatchingTask from typing import Any, Callable, List, Set, Tuple -from pymongo.synchronous.mongo_client import MongoClient - sys.path[0:0] = [""] from test import ( @@ -45,7 +42,7 @@ from bson import DBRef from gridfs.synchronous.grid_file import GridFS, GridFSBucket -from pymongo import ASCENDING, MongoClient, _csot, monitoring +from pymongo import ASCENDING, MongoClient, monitoring from pymongo.common import _MAX_END_SESSIONS from pymongo.errors import ConfigurationError, InvalidOperation, OperationFailure from pymongo.operations import IndexModel, InsertOne, UpdateOne @@ -938,6 +935,39 @@ def test_session_binding_end_session(self): s2.end_session() + def test_getmore_preserves_lsid_after_session_support_lost(self): + listener = OvertCommandListener() + client = self.rs_or_single_client(event_listeners=[listener], maxPoolSize=1) + coll = client.pymongo_test.test + coll.drop() + coll.insert_many([{"x": i} for i in range(10)]) + self.addCleanup(coll.drop) + + with client.start_session() as s: + cursor = coll.find({}, batch_size=2, session=s) + next(cursor) + + find_event = next(e for e in listener.started_events if e.command_name == "find") + lsid = find_event.command["lsid"] + + # Simulate a node stepping down: mark idle connections as not supporting sessions. + for server in client._topology._servers.values(): + for conn in server.pool.conns: + conn.supports_sessions = False + + listener.reset() + cursor.to_list() + + getmore_events = [e for e in listener.started_events if e.command_name == "getMore"] + self.assertGreater(len(getmore_events), 0, "expected at least one getMore command") + for event in getmore_events: + self.assertIn( + "lsid", event.command, "getMore must include lsid when session is materialized" + ) + self.assertEqual( + lsid, event.command["lsid"], "getMore lsid must match the session lsid from find" + ) + class TestCausalConsistency(UnitTest): listener: SessionTestListener From f4219bdca2cfa816f90edb9f5463dbfc2666e609 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 6 May 2026 13:28:36 -0400 Subject: [PATCH 125/129] PYTHON-5817 - Add "Project Structure and Asyncio Considerations" section to CONTRIBUTING.md (#2788) Co-authored-by: Jib --- CONTRIBUTING.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77888eb087..773c9ec0d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -505,13 +505,20 @@ python3 ./.evergreen/scripts/resync-all-specs.py Follow the [Python Driver Release Process Wiki](https://wiki.corp.mongodb.com/display/DRIVERS/Python+Driver+Release+Process). -## Asyncio considerations +## Project Structure and Asyncio Considerations -PyMongo adds asyncio capability by modifying the source files in `*/asynchronous` to `*/synchronous` using -[unasync](https://github.com/python-trio/unasync/) and some custom transforms. +This section describes the layout of the `pymongo/` package. -Where possible, edit the code in `*/asynchronous/*.py` and not the synchronous files. -You can run `pre-commit run --all-files synchro` before running tests if you are testing synchronous code. +Within `pymongo/`, the code is further divided into the `pymongo/asynchronous` and `pymongo/synchronous` subdirectories. +Files in `pymongo/synchronous` are generated from `pymongo/asynchronous` using the `synchro` pre-commit hook, which uses [unasync](https://github.com/python-trio/unasync/) and some custom transforms. + +As a result, **all modifications** within `pymongo` must be made in either the top-level `pymongo` directory when they have to exhibit differing behavior between sync and async contexts or the `pymongo/asynchronous` directory, not `pymongo/synchronous`. +Any changes made directly to files in the `pymongo/synchronous` directory will be overwritten by the `synchro` hook when it is run, which happens automatically on commit. + +Some top-level files (e.g. `pymongo/collection.py`) are re-export files for existing import compatibility and should not be modified directly. +The other top-level files (e.g. `pymongo/network_layer.py`, `pymongo/pool_shared.py`) contain either shared code used in both the asynchronous and synchronous APIs, or code that is very different between the two APIs and therefore cannot be generated from the async version using `synchro`. + +Run `pre-commit run --all-files synchro` before running tests to generate the latest version of the synchronous code. To prevent the `synchro` hook from accidentally overwriting code, it first checks to see whether a sync version of a file is changing and not its async counterpart, and will fail. From 8dc7efade2e5b9c7dac669bc186e74a277367b6a Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 7 May 2026 12:28:15 -0400 Subject: [PATCH 126/129] PYTHON-5821 - Fix ordering issue between event publish and logging for Pool monitoring tests (#2796) --- pymongo/asynchronous/pool.py | 37 ++++++++++++++++++------------------ pymongo/synchronous/pool.py | 37 ++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index 3c1a85246e..a5d5b28990 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -760,11 +760,7 @@ def __init__( self._pending = 0 self._max_connecting = self.opts.max_connecting self._client_id = client_id - if self.enabled_for_cmap: - assert self.opts._event_listeners is not None - self.opts._event_listeners.publish_pool_created( - self.address, self.opts.non_default_options - ) + # Log before publishing event to prevent potential listener preemption in tests if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -774,6 +770,11 @@ def __init__( serverPort=self.address[1], **self.opts.non_default_options, ) + if self.enabled_for_cmap: + assert self.opts._event_listeners is not None + self.opts._event_listeners.publish_pool_created( + self.address, self.opts.non_default_options + ) # Similar to active_sockets but includes threads in the wait queue. self.operation_count: int = 0 # Retain references to pinned connections to prevent the CPython GC @@ -788,9 +789,6 @@ async def ready(self) -> None: async with self.lock: if self.state != PoolState.READY: self.state = PoolState.READY - if self.enabled_for_cmap: - assert self.opts._event_listeners is not None - self.opts._event_listeners.publish_pool_ready(self.address) if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -799,6 +797,9 @@ async def ready(self) -> None: serverHost=self.address[0], serverPort=self.address[1], ) + if self.enabled_for_cmap: + assert self.opts._event_listeners is not None + self.opts._event_listeners.publish_pool_ready(self.address) @property def closed(self) -> bool: @@ -859,9 +860,6 @@ async def _reset( else: for conn in sockets: await conn.close_conn(ConnectionClosedReason.POOL_CLOSED) - if self.enabled_for_cmap: - assert listeners is not None - listeners.publish_pool_closed(self.address) if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -870,15 +868,11 @@ async def _reset( serverHost=self.address[0], serverPort=self.address[1], ) + if self.enabled_for_cmap: + assert listeners is not None + listeners.publish_pool_closed(self.address) else: if old_state != PoolState.PAUSED: - if self.enabled_for_cmap: - assert listeners is not None - listeners.publish_pool_cleared( - self.address, - service_id=service_id, - interrupt_connections=interrupt_connections, - ) if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -888,6 +882,13 @@ async def _reset( serverPort=self.address[1], serviceId=service_id, ) + if self.enabled_for_cmap: + assert listeners is not None + listeners.publish_pool_cleared( + self.address, + service_id=service_id, + interrupt_connections=interrupt_connections, + ) if not _IS_SYNC: await asyncio.gather( *[conn.close_conn(ConnectionClosedReason.STALE) for conn in sockets], # type: ignore[func-returns-value] diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index d33cb59a98..25f2d08fe7 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -758,11 +758,7 @@ def __init__( self._pending = 0 self._max_connecting = self.opts.max_connecting self._client_id = client_id - if self.enabled_for_cmap: - assert self.opts._event_listeners is not None - self.opts._event_listeners.publish_pool_created( - self.address, self.opts.non_default_options - ) + # Log before publishing event to prevent potential listener preemption in tests if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -772,6 +768,11 @@ def __init__( serverPort=self.address[1], **self.opts.non_default_options, ) + if self.enabled_for_cmap: + assert self.opts._event_listeners is not None + self.opts._event_listeners.publish_pool_created( + self.address, self.opts.non_default_options + ) # Similar to active_sockets but includes threads in the wait queue. self.operation_count: int = 0 # Retain references to pinned connections to prevent the CPython GC @@ -786,9 +787,6 @@ def ready(self) -> None: with self.lock: if self.state != PoolState.READY: self.state = PoolState.READY - if self.enabled_for_cmap: - assert self.opts._event_listeners is not None - self.opts._event_listeners.publish_pool_ready(self.address) if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -797,6 +795,9 @@ def ready(self) -> None: serverHost=self.address[0], serverPort=self.address[1], ) + if self.enabled_for_cmap: + assert self.opts._event_listeners is not None + self.opts._event_listeners.publish_pool_ready(self.address) @property def closed(self) -> bool: @@ -857,9 +858,6 @@ def _reset( else: for conn in sockets: conn.close_conn(ConnectionClosedReason.POOL_CLOSED) - if self.enabled_for_cmap: - assert listeners is not None - listeners.publish_pool_closed(self.address) if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -868,15 +866,11 @@ def _reset( serverHost=self.address[0], serverPort=self.address[1], ) + if self.enabled_for_cmap: + assert listeners is not None + listeners.publish_pool_closed(self.address) else: if old_state != PoolState.PAUSED: - if self.enabled_for_cmap: - assert listeners is not None - listeners.publish_pool_cleared( - self.address, - service_id=service_id, - interrupt_connections=interrupt_connections, - ) if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG): _debug_log( _CONNECTION_LOGGER, @@ -886,6 +880,13 @@ def _reset( serverPort=self.address[1], serviceId=service_id, ) + if self.enabled_for_cmap: + assert listeners is not None + listeners.publish_pool_cleared( + self.address, + service_id=service_id, + interrupt_connections=interrupt_connections, + ) if not _IS_SYNC: asyncio.gather( *[conn.close_conn(ConnectionClosedReason.STALE) for conn in sockets], # type: ignore[func-returns-value] From b6bac45c7e9a14e583491a3574df2dec31bb2935 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 7 May 2026 14:52:19 -0400 Subject: [PATCH 127/129] =?UTF-8?q?PYTHON-5032=20-=20Use=20PyErr=5FGetRais?= =?UTF-8?q?edException=20instead=20of=20deprecated=20PyEr=E2=80=A6=20(#279?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bson/_cbsonmodule.c | 215 +++++++++++++++++++++++++++++--------------- 1 file changed, 144 insertions(+), 71 deletions(-) diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c index 034490f558..330a52a734 100644 --- a/bson/_cbsonmodule.c +++ b/bson/_cbsonmodule.c @@ -109,6 +109,7 @@ struct module_state { #define DATETIME_CLAMP 2 #define DATETIME_MS 3 #define DATETIME_AUTO 4 +#define PYTHON_3_12 0x030C0000 /* Converts integer to its string representation in decimal notation. */ extern int cbson_long_long_to_str(long long num, char* str, size_t size) { @@ -249,6 +250,67 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, */ static int write_raw_doc(buffer_t buffer, PyObject* raw, PyObject* _raw); +#if PY_VERSION_HEX >= PYTHON_3_12 +/* Transfer traceback from old_exc to new_exc. + * Steals reference to old_exc. */ +static PyObject* _transfer_traceback(PyObject *old_exc, PyObject *new_exc) { + PyObject *tb = PyException_GetTraceback(old_exc); + if (tb) { + PyException_SetTraceback(new_exc, tb); + Py_DECREF(tb); + } + Py_DECREF(old_exc); + return new_exc; +} +#endif + +/* Rewrap the current exception as InvalidBSON(str(e)) if it is not already an InvalidBSON error. */ +static void _rewrap_as_invalid_bson(void) { +#if PY_VERSION_HEX >= PYTHON_3_12 + PyObject *exc = PyErr_GetRaisedException(); + if (exc && PyErr_GivenExceptionMatches(exc, PyExc_Exception)) { + PyObject *InvalidBSON = _error("InvalidBSON"); + if (InvalidBSON) { + if (!PyErr_GivenExceptionMatches(exc, InvalidBSON)) { + PyObject *err_msg = PyObject_Str(exc); + if (err_msg) { + PyObject *new_exc = PyObject_CallOneArg(InvalidBSON, err_msg); + if (new_exc) { + exc = _transfer_traceback(exc, new_exc); + } + } + Py_XDECREF(err_msg); + } + Py_DECREF(InvalidBSON); + } + } + /* Steals reference to exc. */ + PyErr_SetRaisedException(exc); +#else + PyObject *etype = NULL, *evalue = NULL, *etrace = NULL; + PyObject *InvalidBSON = NULL; + PyErr_Fetch(&etype, &evalue, &etrace); + if (PyErr_GivenExceptionMatches(etype, PyExc_Exception)) { + InvalidBSON = _error("InvalidBSON"); + if (InvalidBSON) { + if (!PyErr_GivenExceptionMatches(etype, InvalidBSON)) { + Py_DECREF(etype); + etype = InvalidBSON; + if (evalue) { + PyObject *msg = PyObject_Str(evalue); + Py_DECREF(evalue); + evalue = msg; + } + PyErr_NormalizeException(&etype, &evalue, &etrace); + } else { + Py_DECREF(InvalidBSON); + } + } + } + PyErr_Restore(etype, evalue, etrace); +#endif +} + /* Date stuff */ static PyObject* datetime_from_millis(long long millis) { /* To encode a datetime instance like datetime(9999, 12, 31, 23, 59, 59, 999999) @@ -294,34 +356,57 @@ static PyObject* datetime_from_millis(long long millis) { timeinfo.tm_sec, microseconds); if(!datetime) { - PyObject *etype = NULL, *evalue = NULL, *etrace = NULL; + #if PY_VERSION_HEX >= PYTHON_3_12 + PyObject *exc = PyErr_GetRaisedException(); - /* - * Calling _error clears the error state, so fetch it first. - */ - PyErr_Fetch(&etype, &evalue, &etrace); - - /* Only add addition error message on ValueError exceptions. */ - if (PyErr_GivenExceptionMatches(etype, PyExc_ValueError)) { - if (evalue) { - PyObject* err_msg = PyObject_Str(evalue); + /* Only add additional error message on ValueError exceptions. */ + if (exc && PyErr_GivenExceptionMatches(exc, PyExc_ValueError)) { + PyObject* err_msg = PyObject_Str(exc); if (err_msg) { PyObject* appendage = PyUnicode_FromString(" (Consider Using CodecOptions(datetime_conversion=DATETIME_AUTO) or MongoClient(datetime_conversion='DATETIME_AUTO')). See: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/data-formats/dates-and-times/#handling-out-of-range-datetimes"); if (appendage) { PyObject* msg = PyUnicode_Concat(err_msg, appendage); if (msg) { - Py_DECREF(evalue); - evalue = msg; + PyObject* new_exc = PyObject_CallOneArg(PyExc_ValueError, msg); + if (new_exc) { + exc = _transfer_traceback(exc, new_exc); + } + Py_DECREF(msg); } } Py_XDECREF(appendage); } Py_XDECREF(err_msg); } - PyErr_NormalizeException(&etype, &evalue, &etrace); - } - /* Steals references to args. */ - PyErr_Restore(etype, evalue, etrace); + /* Steals reference to exc. */ + PyErr_SetRaisedException(exc); + #else + /* Calling _error clears the error state, so fetch it first.*/ + PyObject *etype = NULL, *evalue = NULL, *etrace = NULL; + PyErr_Fetch(&etype, &evalue, &etrace); + + /* Only add additional error message on ValueError exceptions. */ + if (PyErr_GivenExceptionMatches(etype, PyExc_ValueError)) { + if (evalue) { + PyObject* err_msg = PyObject_Str(evalue); + if (err_msg) { + PyObject* appendage = PyUnicode_FromString(" (Consider Using CodecOptions(datetime_conversion=DATETIME_AUTO) or MongoClient(datetime_conversion='DATETIME_AUTO')). See: https://www.mongodb.com/docs/languages/python/pymongo-driver/current/data-formats/dates-and-times/#handling-out-of-range-datetimes"); + if (appendage) { + PyObject* msg = PyUnicode_Concat(err_msg, appendage); + if (msg) { + Py_DECREF(evalue); + evalue = msg; + } + } + Py_XDECREF(appendage); + } + Py_XDECREF(err_msg); + } + PyErr_NormalizeException(&etype, &evalue, &etrace); + } + /* Steals references to args. */ + PyErr_Restore(etype, evalue, etrace); + #endif } return datetime; } @@ -1681,6 +1766,46 @@ static int write_raw_doc(buffer_t buffer, PyObject* raw, PyObject* _raw_str) { /* Update Invalid Document error to include doc as a property. */ void handle_invalid_doc_error(PyObject* dict) { +#if PY_VERSION_HEX >= PYTHON_3_12 + PyObject *exc = PyErr_GetRaisedException(); + PyObject *msg = NULL, *new_msg = NULL; + PyObject *InvalidDocument = NULL; + + if (exc == NULL) { + return; + } + + InvalidDocument = _error("InvalidDocument"); + if (InvalidDocument == NULL) { + goto cleanup; + } + + if (PyErr_GivenExceptionMatches(exc, InvalidDocument)) { + msg = PyObject_Str(exc); + if (msg) { + const char *msg_utf8 = PyUnicode_AsUTF8(msg); + if (msg_utf8 == NULL) { + goto cleanup; + } + new_msg = PyUnicode_FromFormat("Invalid document: %s", msg_utf8); + if (new_msg == NULL) { + goto cleanup; + } + /* Add doc to the error instance as a property. */ + PyObject* exc_args[2] = {new_msg, dict}; + PyObject* new_exc = PyObject_Vectorcall(InvalidDocument, exc_args, 2, NULL); + if (new_exc) { + exc = _transfer_traceback(exc, new_exc); + } + } + } +cleanup: + /* Steals reference to exc. */ + PyErr_SetRaisedException(exc); + Py_XDECREF(msg); + Py_XDECREF(InvalidDocument); + Py_XDECREF(new_msg); +#else PyObject *etype = NULL, *evalue = NULL, *etrace = NULL; PyObject *msg = NULL, *new_msg = NULL, *new_evalue = NULL; PyErr_Fetch(&etype, &evalue, &etrace); @@ -1723,6 +1848,7 @@ void handle_invalid_doc_error(PyObject* dict) { Py_XDECREF(InvalidDocument); Py_XDECREF(new_evalue); Py_XDECREF(new_msg); +#endif } @@ -2654,42 +2780,7 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, * Wrap any non-InvalidBSON errors in InvalidBSON. */ if (PyErr_Occurred()) { - PyObject *etype = NULL, *evalue = NULL, *etrace = NULL; - PyObject *InvalidBSON = NULL; - - /* - * Calling _error clears the error state, so fetch it first. - */ - PyErr_Fetch(&etype, &evalue, &etrace); - - /* Dont reraise anything but PyExc_Exceptions as InvalidBSON. */ - if (PyErr_GivenExceptionMatches(etype, PyExc_Exception)) { - InvalidBSON = _error("InvalidBSON"); - if (InvalidBSON) { - if (!PyErr_GivenExceptionMatches(etype, InvalidBSON)) { - /* - * Raise InvalidBSON(str(e)). - */ - Py_DECREF(etype); - etype = InvalidBSON; - - if (evalue) { - PyObject *msg = PyObject_Str(evalue); - Py_DECREF(evalue); - evalue = msg; - } - PyErr_NormalizeException(&etype, &evalue, &etrace); - } else { - /* - * The current exception matches InvalidBSON, so we don't - * need this reference after all. - */ - Py_DECREF(InvalidBSON); - } - } - } - /* Steals references to args. */ - PyErr_Restore(etype, evalue, etrace); + _rewrap_as_invalid_bson(); } else { PyObject *InvalidBSON = _error("InvalidBSON"); if (InvalidBSON) { @@ -2727,25 +2818,7 @@ static int _element_to_dict(PyObject* self, const char* string, if (!*name) { /* If NULL is returned then wrap the UnicodeDecodeError in an InvalidBSON error */ - PyObject *etype = NULL, *evalue = NULL, *etrace = NULL; - PyObject *InvalidBSON = NULL; - - PyErr_Fetch(&etype, &evalue, &etrace); - if (PyErr_GivenExceptionMatches(etype, PyExc_Exception)) { - InvalidBSON = _error("InvalidBSON"); - if (InvalidBSON) { - Py_DECREF(etype); - etype = InvalidBSON; - - if (evalue) { - PyObject *msg = PyObject_Str(evalue); - Py_DECREF(evalue); - evalue = msg; - } - PyErr_NormalizeException(&etype, &evalue, &etrace); - } - } - PyErr_Restore(etype, evalue, etrace); + _rewrap_as_invalid_bson(); return -1; } position += (unsigned)name_length + 1; From f145c7db940f72cb9ad76b99793267f619e63217 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Thu, 7 May 2026 15:23:00 -0400 Subject: [PATCH 128/129] PYTHON-5756 - Fix BSON Binary type length bug (#2790) --- bson/_cbsonmodule.c | 2 +- test/test_bson.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c index 330a52a734..366129936c 100644 --- a/bson/_cbsonmodule.c +++ b/bson/_cbsonmodule.c @@ -2281,7 +2281,7 @@ static PyObject* get_value(PyObject* self, PyObject* name, const char* buffer, } memcpy(&length, buffer + *position, 4); length = BSON_UINT32_FROM_LE(length); - if (max < length) { + if (max - 5 < length) { // Account for 5-byte header. max >= 5 guaranteed above goto invalid; } diff --git a/test/test_bson.py b/test/test_bson.py index ffc02965fb..ae1807e5fc 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -1269,6 +1269,22 @@ def __repr__(self): encode(doc) self.assertEqual(cm.exception.document, doc) + def test_binary_length_accounts_for_header(self): + size = 20 + binary_length = 12 # 5 more than the actual 7 bytes + + payload = b"" + payload += struct.pack(" Date: Thu, 7 May 2026 15:23:07 -0400 Subject: [PATCH 129/129] PYTHON-5708 - Unskip large encryption tests on mongocryptd (#2793) --- test/asynchronous/test_encryption.py | 4 ---- test/test_encryption.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 1503f54c76..455b1940c4 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -876,8 +876,6 @@ async def test_views_are_prohibited(self): class TestCorpus(AsyncEncryptionIntegrationTest): - # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. - @async_client_context.require_version_max(6, 99) @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") async def asyncSetUp(self): await super().asyncSetUp() @@ -1054,8 +1052,6 @@ class TestBsonSizeBatches(AsyncEncryptionIntegrationTest): client_encrypted: AsyncMongoClient listener: OvertCommandListener - # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. - @async_client_context.require_version_max(6, 99) async def asyncSetUp(self): await super().asyncSetUp() db = async_client_context.client.db diff --git a/test/test_encryption.py b/test/test_encryption.py index 0993362273..7df9e7ac38 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -872,8 +872,6 @@ def test_views_are_prohibited(self): class TestCorpus(EncryptionIntegrationTest): - # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. - @client_context.require_version_max(6, 99) @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") def setUp(self): super().setUp() @@ -1050,8 +1048,6 @@ class TestBsonSizeBatches(EncryptionIntegrationTest): client_encrypted: MongoClient listener: OvertCommandListener - # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. - @client_context.require_version_max(6, 99) def setUp(self): super().setUp() db = client_context.client.db