From 25486962a6caa42b73acbbc264b14e32c620553d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 24 Jun 2026 16:55:18 -0500 Subject: [PATCH 01/28] PYTHON-5808 Drop support for MongoDB 4.2 Bump minimum supported server version to MongoDB 4.4 (wire version 9). Remove dead code paths that only applied to wire version < 8 servers, including the legacy OP_GET_MORE protocol, the use_command dispatch mechanism, and the from_command response field. --- .evergreen/generated_configs/tasks.yml | 1052 ++++++++--------- .evergreen/generated_configs/variants.yml | 9 - .evergreen/scripts/generate_config_utils.py | 2 +- README.md | 2 +- doc/changelog.rst | 1 + pymongo/asynchronous/aggregation.py | 10 +- pymongo/asynchronous/bulk.py | 4 - pymongo/asynchronous/collection.py | 12 - pymongo/asynchronous/mongo_client.py | 8 - pymongo/asynchronous/server.py | 10 +- pymongo/asynchronous/topology.py | 4 +- pymongo/common.py | 4 +- pymongo/message.py | 123 +- pymongo/response.py | 14 +- pymongo/synchronous/aggregation.py | 10 +- pymongo/synchronous/bulk.py | 4 - pymongo/synchronous/collection.py | 12 - pymongo/synchronous/mongo_client.py | 8 - pymongo/synchronous/server.py | 10 +- pymongo/synchronous/topology.py | 4 +- test/__init__.py | 3 - test/asynchronous/__init__.py | 3 - test/asynchronous/test_bulk.py | 2 - test/asynchronous/test_change_stream.py | 13 - ...nnections_survive_primary_stepdown_spec.py | 4 - test/asynchronous/test_custom_types.py | 2 - test/asynchronous/test_encryption.py | 1 - test/asynchronous/test_on_demand_csfle.py | 2 - test/asynchronous/test_transactions.py | 1 - test/test_bulk.py | 2 - test/test_change_stream.py | 13 - ...nnections_survive_primary_stepdown_spec.py | 4 - test/test_custom_types.py | 2 - test/test_encryption.py | 1 - test/test_on_demand_csfle.py | 2 - test/test_transactions.py | 1 - 36 files changed, 497 insertions(+), 862 deletions(-) diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index f2987d890f..e54e6c5f1f 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -3321,505 +3321,437 @@ tasks: - free-threaded # Standard tests - - name: test-standard-v4.2-python3.11-sync-noauth-ssl-replica-set + - name: test-standard-v4.4-python3.12-sync-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.2" - TOOLCHAIN_VERSION: "3.11" + VERSION: "4.4" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - test-standard - - server-4.2 - - python-3.11 + - server-4.4 + - python-3.12 - replica_set-noauth-ssl - sync - - name: test-standard-v4.2-python3.14-sync-noauth-ssl-replica-set + - name: test-standard-v4.4-python3.13-async-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.2" - TOOLCHAIN_VERSION: "3.14" - TEST_NAME: default_sync + VERSION: "4.4" + TOOLCHAIN_VERSION: "3.13" + TEST_NAME: default_async tags: - test-standard - - server-4.2 - - python-3.14 + - server-4.4 + - python-3.13 - replica_set-noauth-ssl - - sync - - name: test-standard-v4.2-python3.12-sync-auth-ssl-sharded-cluster + - async + - name: test-standard-v4.4-python3.14-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" - TOOLCHAIN_VERSION: "3.12" - TEST_NAME: default_sync + VERSION: "4.4" + TOOLCHAIN_VERSION: "3.14" + TEST_NAME: default_async tags: - test-standard - - server-4.2 - - python-3.12 + - server-4.4 + - python-3.14 - sharded_cluster-auth-ssl - - sync - - name: test-standard-v4.2-pypy3.11-sync-auth-ssl-sharded-cluster + - async + - name: test-standard-v4.4-python3.14t-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" - TOOLCHAIN_VERSION: pypy3.11 + VERSION: "4.4" + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - test-standard - - server-4.2 - - python-pypy3.11 + - server-4.4 + - python-3.14t - sharded_cluster-auth-ssl - sync - - pypy - - name: test-standard-v4.2-python3.10-sync-noauth-nossl-standalone-min-deps + - free-threaded + - name: test-standard-v4.4-python3.10-sync-noauth-nossl-standalone-min-deps commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - test-standard - - server-4.2 + - server-4.4 - python-3.10 - standalone-noauth-nossl - sync - - name: test-standard-v4.2-python3.14t-sync-noauth-nossl-standalone + - name: test-standard-v4.4-python3.11-async-noauth-nossl-standalone commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" - TOOLCHAIN_VERSION: 3.14t - TEST_NAME: default_sync + VERSION: "4.4" + TOOLCHAIN_VERSION: "3.11" + TEST_NAME: default_async tags: - test-standard - - server-4.2 - - python-3.14t + - server-4.4 + - python-3.11 - standalone-noauth-nossl - - sync - - free-threaded - - name: test-standard-v4.4-python3.11-async-noauth-ssl-replica-set + - async + - name: test-standard-v5.0-python3.11-sync-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" TOOLCHAIN_VERSION: "3.11" - TEST_NAME: default_async + TEST_NAME: default_sync tags: - test-standard - - server-4.4 + - server-5.0 - python-3.11 - replica_set-noauth-ssl - - async - - name: test-standard-v4.4-python3.14-async-noauth-ssl-replica-set + - sync + - name: test-standard-v5.0-python3.12-async-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" - TOOLCHAIN_VERSION: "3.14" + VERSION: "5.0" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - test-standard - - server-4.4 - - python-3.14 + - server-5.0 + - python-3.12 - replica_set-noauth-ssl - async - - name: test-standard-v4.4-python3.12-async-auth-ssl-sharded-cluster + - name: test-standard-v5.0-python3.13-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.4" - TOOLCHAIN_VERSION: "3.12" - TEST_NAME: default_async + VERSION: "5.0" + TOOLCHAIN_VERSION: "3.13" + TEST_NAME: default_sync tags: - test-standard - - server-4.4 - - python-3.12 + - server-5.0 + - python-3.13 - sharded_cluster-auth-ssl - - async - - name: test-standard-v4.4-pypy3.11-async-auth-ssl-sharded-cluster + - sync + - name: test-standard-v5.0-python3.14t-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.4" - TOOLCHAIN_VERSION: pypy3.11 + VERSION: "5.0" + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - test-standard - - server-4.4 - - python-pypy3.11 + - server-5.0 + - python-3.14t - sharded_cluster-auth-ssl - async - - pypy - - name: test-standard-v4.4-python3.10-async-noauth-nossl-standalone-min-deps + - free-threaded + - name: test-standard-v5.0-python3.10-async-noauth-nossl-standalone-min-deps commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.4" + VERSION: "5.0" TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.4" + VERSION: "5.0" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - test-standard - - server-4.4 + - server-5.0 - python-3.10 - standalone-noauth-nossl - async - - name: test-standard-v4.4-python3.14t-async-noauth-nossl-standalone + - name: test-standard-v5.0-pypy3.11-sync-noauth-nossl-standalone commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.4" - TOOLCHAIN_VERSION: 3.14t - TEST_NAME: default_async + VERSION: "5.0" + TOOLCHAIN_VERSION: pypy3.11 + TEST_NAME: default_sync tags: - test-standard - - server-4.4 - - python-3.14t + - server-5.0 + - python-pypy3.11 - standalone-noauth-nossl - - async - - free-threaded - - name: test-standard-v5.0-python3.10-sync-noauth-ssl-replica-set-min-deps + - sync + - pypy + - name: test-standard-v6.0-python3.10-sync-noauth-ssl-replica-set-min-deps commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "5.0" + VERSION: "6.0" TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "5.0" + VERSION: "6.0" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: - test-standard - - server-5.0 + - server-6.0 - python-3.10 - replica_set-noauth-ssl - sync - - name: test-standard-v5.0-python3.14t-sync-noauth-ssl-replica-set + - name: test-standard-v6.0-python3.11-async-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "5.0" - TOOLCHAIN_VERSION: 3.14t - TEST_NAME: default_sync + VERSION: "6.0" + TOOLCHAIN_VERSION: "3.11" + TEST_NAME: default_async tags: - test-standard - - server-5.0 - - python-3.14t + - server-6.0 + - python-3.11 - replica_set-noauth-ssl - - sync - - free-threaded - - name: test-standard-v5.0-python3.11-sync-auth-ssl-sharded-cluster + - async + - name: test-standard-v6.0-python3.12-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" - TOOLCHAIN_VERSION: "3.11" + VERSION: "6.0" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - test-standard - - server-5.0 - - python-3.11 + - server-6.0 + - python-3.12 - sharded_cluster-auth-ssl - sync - - name: test-standard-v5.0-python3.14-sync-auth-ssl-sharded-cluster + - name: test-standard-v6.0-python3.13-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" - TOOLCHAIN_VERSION: "3.14" - TEST_NAME: default_sync + VERSION: "6.0" + TOOLCHAIN_VERSION: "3.13" + TEST_NAME: default_async tags: - test-standard - - server-5.0 - - python-3.14 + - server-6.0 + - python-3.13 - sharded_cluster-auth-ssl - - sync - - name: test-standard-v5.0-python3.13-sync-noauth-nossl-standalone + - async + - name: test-standard-v6.0-python3.14-sync-noauth-nossl-standalone commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "5.0" - TOOLCHAIN_VERSION: "3.13" + VERSION: "6.0" + TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: - test-standard - - server-5.0 - - python-3.13 + - server-6.0 + - python-3.14 - standalone-noauth-nossl - sync - - name: test-standard-v6.0-python3.10-async-noauth-ssl-replica-set-min-deps + - name: test-standard-v6.0-pypy3.11-async-noauth-nossl-standalone commands: - func: run server vars: AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set + SSL: nossl + TOPOLOGY: standalone VERSION: "6.0" - TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set + SSL: nossl + TOPOLOGY: standalone VERSION: "6.0" - TEST_MIN_DEPS: "1" - TOOLCHAIN_VERSION: "3.10" + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard - server-6.0 - - python-3.10 - - replica_set-noauth-ssl + - python-pypy3.11 + - standalone-noauth-nossl - async - - name: test-standard-v6.0-python3.14t-async-noauth-ssl-replica-set + - pypy + - name: test-standard-v7.0-python3.10-async-noauth-ssl-replica-set-min-deps commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "6.0" + VERSION: "7.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "6.0" - TOOLCHAIN_VERSION: 3.14t + VERSION: "7.0" + TEST_MIN_DEPS: "1" + TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: - test-standard - - server-6.0 - - python-3.14t + - server-7.0 + - python-3.10 - replica_set-noauth-ssl - async - - free-threaded - - name: test-standard-v6.0-python3.11-async-auth-ssl-sharded-cluster - commands: - - func: run server - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "6.0" - - func: run tests - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "6.0" - TOOLCHAIN_VERSION: "3.11" - TEST_NAME: default_async - tags: - - test-standard - - server-6.0 - - python-3.11 - - sharded_cluster-auth-ssl - - async - - name: test-standard-v6.0-python3.14-async-auth-ssl-sharded-cluster - commands: - - func: run server - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "6.0" - - func: run tests - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "6.0" - TOOLCHAIN_VERSION: "3.14" - TEST_NAME: default_async - tags: - - test-standard - - server-6.0 - - python-3.14 - - sharded_cluster-auth-ssl - - async - - name: test-standard-v6.0-python3.13-async-noauth-nossl-standalone - commands: - - func: run server - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - VERSION: "6.0" - - func: run tests - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - VERSION: "6.0" - TOOLCHAIN_VERSION: "3.13" - TEST_NAME: default_async - tags: - - test-standard - - server-6.0 - - python-3.13 - - standalone-noauth-nossl - - async - - name: test-standard-v7.0-python3.13-sync-noauth-ssl-replica-set + - name: test-standard-v7.0-pypy3.11-sync-noauth-ssl-replica-set commands: - func: run server vars: @@ -3833,15 +3765,16 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "7.0" - TOOLCHAIN_VERSION: "3.13" + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard - server-7.0 - - python-3.13 + - python-pypy3.11 - replica_set-noauth-ssl - sync - - name: test-standard-v7.0-python3.10-sync-auth-ssl-sharded-cluster-min-deps + - pypy + - name: test-standard-v7.0-python3.11-sync-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -3849,23 +3782,21 @@ 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" + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: - test-standard - server-7.0 - - python-3.10 + - python-3.11 - sharded_cluster-auth-ssl - sync - - name: test-standard-v7.0-python3.14t-sync-auth-ssl-sharded-cluster + - name: test-standard-v7.0-python3.12-async-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -3879,16 +3810,15 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - TOOLCHAIN_VERSION: 3.14t - TEST_NAME: default_sync + TOOLCHAIN_VERSION: "3.12" + TEST_NAME: default_async tags: - test-standard - server-7.0 - - python-3.14t + - python-3.12 - sharded_cluster-auth-ssl - - sync - - free-threaded - - name: test-standard-v7.0-python3.12-sync-noauth-nossl-standalone + - async + - name: test-standard-v7.0-python3.14-async-noauth-nossl-standalone commands: - func: run server vars: @@ -3902,15 +3832,15 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" - TOOLCHAIN_VERSION: "3.12" - TEST_NAME: default_sync + TOOLCHAIN_VERSION: "3.14" + TEST_NAME: default_async tags: - test-standard - server-7.0 - - python-3.12 + - python-3.14 - standalone-noauth-nossl - - sync - - name: test-standard-v7.0-pypy3.11-sync-noauth-nossl-standalone + - async + - name: test-standard-v7.0-python3.14t-sync-noauth-nossl-standalone commands: - func: run server vars: @@ -3924,16 +3854,16 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "7.0" - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - test-standard - server-7.0 - - python-pypy3.11 + - python-3.14t - standalone-noauth-nossl - sync - - pypy - - name: test-standard-v8.0-python3.13-async-noauth-ssl-replica-set + - free-threaded + - name: test-standard-v8.0-python3.14-sync-noauth-ssl-replica-set commands: - func: run server vars: @@ -3947,15 +3877,38 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "8.0" - TOOLCHAIN_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.14" + TEST_NAME: default_sync + tags: + - test-standard + - server-8.0 + - python-3.14 + - replica_set-noauth-ssl + - sync + - name: test-standard-v8.0-pypy3.11-async-noauth-ssl-replica-set + commands: + - func: run server + vars: + AUTH: noauth + SSL: ssl + TOPOLOGY: replica_set + VERSION: "8.0" + - func: run tests + vars: + AUTH: noauth + SSL: ssl + TOPOLOGY: replica_set + VERSION: "8.0" + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard - server-8.0 - - python-3.13 + - python-pypy3.11 - replica_set-noauth-ssl - async - - name: test-standard-v8.0-python3.10-async-auth-ssl-sharded-cluster-min-deps + - pypy + - name: test-standard-v8.0-python3.10-sync-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -3972,14 +3925,14 @@ tasks: VERSION: "8.0" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" - TEST_NAME: default_async + TEST_NAME: default_sync tags: - test-standard - server-8.0 - python-3.10 - sharded_cluster-auth-ssl - - async - - name: test-standard-v8.0-python3.14t-async-auth-ssl-sharded-cluster + - sync + - name: test-standard-v8.0-python3.11-async-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -3993,16 +3946,15 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - TOOLCHAIN_VERSION: 3.14t + TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: - test-standard - server-8.0 - - python-3.14t + - python-3.11 - sharded_cluster-auth-ssl - async - - free-threaded - - name: test-standard-v8.0-python3.12-async-noauth-nossl-standalone + - name: test-standard-v8.0-python3.13-sync-noauth-nossl-standalone commands: - func: run server vars: @@ -4016,15 +3968,15 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - TOOLCHAIN_VERSION: "3.12" - TEST_NAME: default_async + TOOLCHAIN_VERSION: "3.13" + TEST_NAME: default_sync tags: - test-standard - server-8.0 - - python-3.12 + - python-3.13 - standalone-noauth-nossl - - async - - name: test-standard-v8.0-pypy3.11-async-noauth-nossl-standalone + - sync + - name: test-standard-v8.0-python3.14t-async-noauth-nossl-standalone commands: - func: run server vars: @@ -4038,16 +3990,16 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - test-standard - server-8.0 - - python-pypy3.11 + - python-3.14t - standalone-noauth-nossl - async - - pypy - - name: test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov + - free-threaded + - name: test-standard-latest-python3.13-sync-noauth-ssl-replica-set-cov commands: - func: run server vars: @@ -4063,16 +4015,16 @@ tasks: TOPOLOGY: replica_set VERSION: latest COVERAGE: "1" - TOOLCHAIN_VERSION: "3.12" - TEST_NAME: default_async + TOOLCHAIN_VERSION: "3.13" + TEST_NAME: default_sync tags: - test-standard - server-latest - - python-3.12 + - python-3.13 - replica_set-noauth-ssl - - async + - sync - pr - - name: test-standard-latest-pypy3.11-async-noauth-ssl-replica-set + - name: test-standard-latest-python3.14t-async-noauth-ssl-replica-set-cov commands: - func: run server vars: @@ -4080,22 +4032,25 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: latest - TOOLCHAIN_VERSION: pypy3.11 + COVERAGE: "1" + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_async tags: - test-standard - server-latest - - python-pypy3.11 + - python-3.14t - replica_set-noauth-ssl - async - - pypy - - name: test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov + - free-threaded + - pr + - name: test-standard-latest-python3.14-sync-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -4111,16 +4066,39 @@ tasks: TOPOLOGY: sharded_cluster VERSION: latest COVERAGE: "1" - TOOLCHAIN_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.14" + TEST_NAME: default_sync + tags: + - test-standard + - server-latest + - python-3.14 + - sharded_cluster-auth-ssl + - sync + - pr + - name: test-standard-latest-pypy3.11-async-auth-ssl-sharded-cluster + commands: + - func: run server + vars: + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + VERSION: latest + - func: run tests + vars: + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + VERSION: latest + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_async tags: - test-standard - server-latest - - python-3.13 + - python-pypy3.11 - sharded_cluster-auth-ssl - async - - pr - - name: test-standard-latest-python3.11-async-noauth-nossl-standalone-cov + - pypy + - name: test-standard-latest-python3.11-sync-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4137,15 +4115,15 @@ tasks: VERSION: latest COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" - TEST_NAME: default_async + TEST_NAME: default_sync tags: - test-standard - server-latest - python-3.11 - standalone-noauth-nossl - - async + - sync - pr - - name: test-standard-latest-python3.14-async-noauth-nossl-standalone-cov + - name: test-standard-latest-python3.12-async-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4161,16 +4139,16 @@ tasks: TOPOLOGY: standalone VERSION: latest COVERAGE: "1" - TOOLCHAIN_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: - test-standard - server-latest - - python-3.14 + - python-3.12 - standalone-noauth-nossl - async - pr - - name: test-standard-rapid-python3.12-sync-noauth-ssl-replica-set + - name: test-standard-rapid-python3.14-async-noauth-ssl-replica-set commands: - func: run server vars: @@ -4184,15 +4162,15 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - TOOLCHAIN_VERSION: "3.12" - TEST_NAME: default_sync + TOOLCHAIN_VERSION: "3.14" + TEST_NAME: default_async tags: - test-standard - server-rapid - - python-3.12 + - python-3.14 - replica_set-noauth-ssl - - sync - - name: test-standard-rapid-pypy3.11-sync-noauth-ssl-replica-set + - async + - name: test-standard-rapid-python3.14t-sync-noauth-ssl-replica-set commands: - func: run server vars: @@ -4206,16 +4184,16 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: 3.14t TEST_NAME: default_sync tags: - test-standard - server-rapid - - python-pypy3.11 + - python-3.14t - replica_set-noauth-ssl - sync - - pypy - - name: test-standard-rapid-python3.13-sync-auth-ssl-sharded-cluster + - free-threaded + - name: test-standard-rapid-python3.10-async-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -4223,21 +4201,46 @@ 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 - TOOLCHAIN_VERSION: "3.13" + TEST_MIN_DEPS: "1" + TOOLCHAIN_VERSION: "3.10" + TEST_NAME: default_async + tags: + - test-standard + - server-rapid + - python-3.10 + - sharded_cluster-auth-ssl + - async + - name: test-standard-rapid-pypy3.11-sync-auth-ssl-sharded-cluster + commands: + - func: run server + vars: + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + VERSION: rapid + - func: run tests + vars: + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster + VERSION: rapid + TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: - test-standard - server-rapid - - python-3.13 + - python-pypy3.11 - sharded_cluster-auth-ssl - sync - - name: test-standard-rapid-python3.11-sync-noauth-nossl-standalone + - pypy + - name: test-standard-rapid-python3.12-sync-noauth-nossl-standalone commands: - func: run server vars: @@ -4251,15 +4254,15 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - TOOLCHAIN_VERSION: "3.11" + TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: - test-standard - server-rapid - - python-3.11 + - python-3.12 - standalone-noauth-nossl - sync - - name: test-standard-rapid-python3.14-sync-noauth-nossl-standalone + - name: test-standard-rapid-python3.13-async-noauth-nossl-standalone commands: - func: run server vars: @@ -4273,363 +4276,277 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - TOOLCHAIN_VERSION: "3.14" - TEST_NAME: default_sync + TOOLCHAIN_VERSION: "3.13" + TEST_NAME: default_async tags: - test-standard - server-rapid - - python-3.14 + - python-3.13 - standalone-noauth-nossl - - sync + - async # Test non standard tests - - name: test-non-standard-v4.2-python3.11-noauth-ssl-replica-set + - name: test-non-standard-v4.4-python3.11-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.2" + VERSION: "4.4" TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - - server-4.2 + - server-4.4 - python-3.11 - replica_set-noauth-ssl - noauth - - name: test-non-standard-v4.2-python3.12-auth-ssl-sharded-cluster + - name: test-non-standard-v4.4-python3.12-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" + VERSION: "4.4" TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - - server-4.2 + - server-4.4 - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v4.2-python3.10-noauth-nossl-standalone-min-deps + - name: test-non-standard-v4.4-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - - server-4.2 + - server-4.4 - python-3.10 - standalone-noauth-nossl - noauth - - name: test-non-standard-v4.2-pypy3.11-noauth-nossl-standalone + - name: test-non-standard-v4.4-pypy3.11-noauth-nossl-standalone commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.2" + VERSION: "4.4" TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - - server-4.2 + - server-4.4 - python-pypy3.11 - standalone-noauth-nossl - noauth - pypy - - name: test-non-standard-v4.4-python3.14t-noauth-ssl-replica-set + - name: test-non-standard-v5.0-python3.14t-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - - server-4.4 + - server-5.0 - python-3.14t - replica_set-noauth-ssl - noauth - free-threaded - - name: test-non-standard-v4.4-pypy3.11-noauth-ssl-replica-set + - name: test-non-standard-v5.0-pypy3.11-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "4.4" + VERSION: "5.0" TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - - server-4.4 + - server-5.0 - python-pypy3.11 - replica_set-noauth-ssl - noauth - pypy - - name: test-non-standard-v4.4-python3.14-auth-ssl-sharded-cluster + - name: test-non-standard-v5.0-python3.14-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.4" + VERSION: "5.0" TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - - server-4.4 + - server-5.0 - python-3.14 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v4.4-python3.13-noauth-nossl-standalone + - name: test-non-standard-v5.0-python3.13-noauth-nossl-standalone commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.4" + VERSION: "5.0" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "4.4" + VERSION: "5.0" TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - - server-4.4 + - server-5.0 - python-3.13 - standalone-noauth-nossl - noauth - - name: test-non-standard-v5.0-python3.11-noauth-ssl-replica-set + - name: test-non-standard-v6.0-python3.11-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - VERSION: "5.0" + VERSION: "6.0" TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - - server-5.0 + - server-6.0 - python-3.11 - replica_set-noauth-ssl - noauth - - name: test-non-standard-v5.0-python3.12-auth-ssl-sharded-cluster + - name: test-non-standard-v6.0-python3.12-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" + VERSION: "6.0" TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - - server-5.0 + - server-6.0 - python-3.12 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-v5.0-pypy3.11-auth-ssl-sharded-cluster + - name: test-non-standard-v6.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" + VERSION: "6.0" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "5.0" + VERSION: "6.0" TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - - server-5.0 + - server-6.0 - python-pypy3.11 - sharded_cluster-auth-ssl - auth - pypy - - name: test-non-standard-v5.0-python3.10-noauth-nossl-standalone-min-deps + - name: test-non-standard-v6.0-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "5.0" + VERSION: "6.0" TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone - VERSION: "5.0" + VERSION: "6.0" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" - tags: - - test-non-standard - - server-5.0 - - python-3.10 - - standalone-noauth-nossl - - noauth - - name: test-non-standard-v6.0-python3.14t-noauth-ssl-replica-set - commands: - - func: run server - vars: - AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set - VERSION: "6.0" - - func: run tests - vars: - AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set - VERSION: "6.0" - TOOLCHAIN_VERSION: 3.14t - tags: - - test-non-standard - - server-6.0 - - python-3.14t - - replica_set-noauth-ssl - - noauth - - free-threaded - - name: test-non-standard-v6.0-python3.14-auth-ssl-sharded-cluster - commands: - - func: run server - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "6.0" - - func: run tests - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "6.0" - TOOLCHAIN_VERSION: "3.14" - tags: - - test-non-standard - - server-6.0 - - python-3.14 - - sharded_cluster-auth-ssl - - auth - - name: test-non-standard-v6.0-python3.13-noauth-nossl-standalone - commands: - - func: run server - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - VERSION: "6.0" - - func: run tests - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - VERSION: "6.0" - TOOLCHAIN_VERSION: "3.13" - tags: - - test-non-standard - - server-6.0 - - python-3.13 - - standalone-noauth-nossl - - noauth - - name: test-non-standard-v6.0-pypy3.11-noauth-nossl-standalone - commands: - - func: run server - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - VERSION: "6.0" - - func: run tests - vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone - VERSION: "6.0" - TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-6.0 - - python-pypy3.11 + - python-3.10 - standalone-noauth-nossl - noauth - - pypy - - name: test-non-standard-v7.0-python3.11-noauth-ssl-replica-set + - name: test-non-standard-v7.0-python3.14t-noauth-ssl-replica-set commands: - func: run server vars: @@ -4643,57 +4560,57 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "7.0" - TOOLCHAIN_VERSION: "3.11" + TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - server-7.0 - - python-3.11 + - python-3.14t - replica_set-noauth-ssl - noauth - - name: test-non-standard-v7.0-pypy3.11-noauth-ssl-replica-set + - free-threaded + - name: test-non-standard-v7.0-python3.14-auth-ssl-sharded-cluster commands: - func: run server vars: - AUTH: noauth + AUTH: auth SSL: ssl - TOPOLOGY: replica_set + TOPOLOGY: sharded_cluster VERSION: "7.0" - func: run tests vars: - AUTH: noauth + AUTH: auth SSL: ssl - TOPOLOGY: replica_set + TOPOLOGY: sharded_cluster VERSION: "7.0" - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - server-7.0 - - python-pypy3.11 - - replica_set-noauth-ssl - - noauth - - pypy - - name: test-non-standard-v7.0-python3.12-auth-ssl-sharded-cluster + - python-3.14 + - sharded_cluster-auth-ssl + - auth + - name: test-non-standard-v7.0-python3.13-noauth-nossl-standalone commands: - func: run server vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone VERSION: "7.0" - func: run tests vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone VERSION: "7.0" - TOOLCHAIN_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - server-7.0 - - python-3.12 - - sharded_cluster-auth-ssl - - auth - - name: test-non-standard-v7.0-python3.10-noauth-nossl-standalone-min-deps + - python-3.13 + - standalone-noauth-nossl + - noauth + - name: test-non-standard-v7.0-pypy3.11-noauth-nossl-standalone commands: - func: run server vars: @@ -4701,22 +4618,21 @@ 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" + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-7.0 - - python-3.10 + - python-pypy3.11 - standalone-noauth-nossl - noauth - - name: test-non-standard-v8.0-python3.14t-noauth-ssl-replica-set + - pypy + - name: test-non-standard-v8.0-python3.11-noauth-ssl-replica-set commands: - func: run server vars: @@ -4730,36 +4646,36 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: "8.0" - TOOLCHAIN_VERSION: 3.14t + TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - server-8.0 - - python-3.14t + - python-3.11 - replica_set-noauth-ssl - noauth - - free-threaded - - name: test-non-standard-v8.0-python3.14-auth-ssl-sharded-cluster + - name: test-non-standard-v8.0-pypy3.11-noauth-ssl-replica-set commands: - func: run server vars: - AUTH: auth + AUTH: noauth SSL: ssl - TOPOLOGY: sharded_cluster + TOPOLOGY: replica_set VERSION: "8.0" - func: run tests vars: - AUTH: auth + AUTH: noauth SSL: ssl - TOPOLOGY: sharded_cluster + TOPOLOGY: replica_set VERSION: "8.0" - TOOLCHAIN_VERSION: "3.14" + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-8.0 - - python-3.14 - - sharded_cluster-auth-ssl - - auth - - name: test-non-standard-v8.0-pypy3.11-auth-ssl-sharded-cluster + - python-pypy3.11 + - replica_set-noauth-ssl + - noauth + - pypy + - name: test-non-standard-v8.0-python3.12-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4773,15 +4689,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - server-8.0 - - python-pypy3.11 + - python-3.12 - sharded_cluster-auth-ssl - auth - - pypy - - name: test-non-standard-v8.0-python3.13-noauth-nossl-standalone + - name: test-non-standard-v8.0-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -4789,42 +4704,22 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: "8.0" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: "8.0" - TOOLCHAIN_VERSION: "3.13" + TEST_MIN_DEPS: "1" + TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - server-8.0 - - python-3.13 + - python-3.10 - standalone-noauth-nossl - noauth - - name: test-non-standard-latest-python3.14t-noauth-ssl-replica-set - commands: - - func: run server - vars: - AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set - VERSION: latest - - func: run tests - vars: - AUTH: noauth - SSL: ssl - TOPOLOGY: replica_set - VERSION: latest - TOOLCHAIN_VERSION: 3.14t - tags: - - test-non-standard - - server-latest - - python-3.14t - - replica_set-noauth-ssl - - noauth - - free-threaded - - name: test-non-standard-latest-pypy3.11-noauth-ssl-replica-set + - name: test-non-standard-latest-python3.11-noauth-ssl-replica-set commands: - func: run server vars: @@ -4838,15 +4733,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: "3.11" tags: - test-non-standard - server-latest - - python-pypy3.11 + - python-3.11 - replica_set-noauth-ssl - noauth - - pypy - - name: test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov + - name: test-non-standard-latest-python3.12-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -4862,16 +4756,16 @@ tasks: TOPOLOGY: sharded_cluster VERSION: latest COVERAGE: "1" - TOOLCHAIN_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard - server-latest - - python-3.14 + - python-3.12 - sharded_cluster-auth-ssl - auth - pr - cov - - name: test-non-standard-latest-python3.14-auth-ssl-sharded-cluster + - name: test-non-standard-latest-python3.12-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4885,15 +4779,15 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - TOOLCHAIN_VERSION: "3.14" + TOOLCHAIN_VERSION: "3.12" tags: - test-non-standard-no-cov - server-latest - - python-3.14 + - python-3.12 - sharded_cluster-auth-ssl - auth - pr - - name: test-non-standard-latest-python3.13-noauth-nossl-standalone + - name: test-non-standard-latest-python3.10-noauth-nossl-standalone-min-deps commands: - func: run server vars: @@ -4901,20 +4795,44 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest - TOOLCHAIN_VERSION: "3.13" + TEST_MIN_DEPS: "1" + TOOLCHAIN_VERSION: "3.10" tags: - test-non-standard - server-latest - - python-3.13 + - python-3.10 + - standalone-noauth-nossl + - noauth + - name: test-non-standard-latest-pypy3.11-noauth-nossl-standalone + commands: + - func: run server + vars: + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone + VERSION: latest + - func: run tests + vars: + AUTH: noauth + SSL: nossl + TOPOLOGY: standalone + VERSION: latest + TOOLCHAIN_VERSION: pypy3.11 + tags: + - test-non-standard + - server-latest + - python-pypy3.11 - standalone-noauth-nossl - noauth - - name: test-non-standard-rapid-python3.11-noauth-ssl-replica-set + - pypy + - name: test-non-standard-rapid-python3.14t-noauth-ssl-replica-set commands: - func: run server vars: @@ -4928,14 +4846,15 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: rapid - TOOLCHAIN_VERSION: "3.11" + TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard - server-rapid - - python-3.11 + - python-3.14t - replica_set-noauth-ssl - noauth - - name: test-non-standard-rapid-python3.12-auth-ssl-sharded-cluster + - free-threaded + - name: test-non-standard-rapid-python3.14-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -4949,37 +4868,36 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: rapid - TOOLCHAIN_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard - server-rapid - - python-3.12 + - python-3.14 - sharded_cluster-auth-ssl - auth - - name: test-non-standard-rapid-python3.10-noauth-nossl-standalone-min-deps + - name: test-non-standard-rapid-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster VERSION: rapid - TEST_MIN_DEPS: "1" - func: run tests vars: - AUTH: noauth - SSL: nossl - TOPOLOGY: standalone + AUTH: auth + SSL: ssl + TOPOLOGY: sharded_cluster VERSION: rapid - TEST_MIN_DEPS: "1" - TOOLCHAIN_VERSION: "3.10" + TOOLCHAIN_VERSION: pypy3.11 tags: - test-non-standard - server-rapid - - python-3.10 - - standalone-noauth-nossl - - noauth - - name: test-non-standard-rapid-pypy3.11-noauth-nossl-standalone + - python-pypy3.11 + - sharded_cluster-auth-ssl + - auth + - pypy + - name: test-non-standard-rapid-python3.13-noauth-nossl-standalone commands: - func: run server vars: @@ -4993,14 +4911,13 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: rapid - TOOLCHAIN_VERSION: pypy3.11 + TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard - server-rapid - - python-pypy3.11 + - python-3.13 - standalone-noauth-nossl - noauth - - pypy # Test numpy tests - name: test-numpy-python3.10-python3.10 @@ -5027,70 +4944,27 @@ tasks: - pr # Test standard auth tests - - name: test-standard-auth-v4.2-python3.10-auth-ssl-sharded-cluster-min-deps + - name: test-standard-auth-v4.4-python3.10-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" + VERSION: "4.4" TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - VERSION: "4.2" + VERSION: "4.4" TEST_MIN_DEPS: "1" TOOLCHAIN_VERSION: "3.10" - tags: - - test-standard-auth - - server-4.2 - - python-3.10 - - sharded_cluster-auth-ssl - - auth - - name: test-standard-auth-v4.2-pypy3.11-auth-ssl-sharded-cluster - commands: - - func: run server - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "4.2" - - func: run tests - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "4.2" - TOOLCHAIN_VERSION: pypy3.11 - tags: - - test-standard-auth - - server-4.2 - - python-pypy3.11 - - sharded_cluster-auth-ssl - - auth - - pypy - - name: test-standard-auth-v4.4-python3.11-auth-ssl-sharded-cluster - commands: - - func: run server - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "4.4" - - func: run tests - vars: - AUTH: auth - SSL: ssl - TOPOLOGY: sharded_cluster - VERSION: "4.4" - TOOLCHAIN_VERSION: "3.11" tags: - test-standard-auth - server-4.4 - - python-3.11 + - python-3.10 - sharded_cluster-auth-ssl - auth - name: test-standard-auth-v4.4-pypy3.11-auth-ssl-sharded-cluster @@ -5115,7 +4989,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-v5.0-python3.12-auth-ssl-sharded-cluster + - name: test-standard-auth-v5.0-python3.11-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5129,11 +5003,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "5.0" - TOOLCHAIN_VERSION: "3.12" + TOOLCHAIN_VERSION: "3.11" tags: - test-standard-auth - server-5.0 - - python-3.12 + - python-3.11 - sharded_cluster-auth-ssl - auth - name: test-standard-auth-v5.0-pypy3.11-auth-ssl-sharded-cluster @@ -5158,7 +5032,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-v6.0-python3.13-auth-ssl-sharded-cluster + - name: test-standard-auth-v6.0-python3.12-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5172,11 +5046,11 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "6.0" - TOOLCHAIN_VERSION: "3.13" + TOOLCHAIN_VERSION: "3.12" tags: - test-standard-auth - server-6.0 - - python-3.13 + - python-3.12 - sharded_cluster-auth-ssl - auth - name: test-standard-auth-v6.0-pypy3.11-auth-ssl-sharded-cluster @@ -5201,7 +5075,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-v7.0-python3.14t-auth-ssl-sharded-cluster + - name: test-standard-auth-v7.0-python3.13-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5215,14 +5089,13 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "7.0" - TOOLCHAIN_VERSION: 3.14t + TOOLCHAIN_VERSION: "3.13" tags: - test-standard-auth - server-7.0 - - python-3.14t + - python-3.13 - sharded_cluster-auth-ssl - auth - - free-threaded - name: test-standard-auth-v7.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server @@ -5245,7 +5118,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-v8.0-python3.14-auth-ssl-sharded-cluster + - name: test-standard-auth-v8.0-python3.14t-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5259,13 +5132,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: "8.0" - TOOLCHAIN_VERSION: "3.14" + TOOLCHAIN_VERSION: 3.14t tags: - test-standard-auth - server-8.0 - - python-3.14 + - python-3.14t - sharded_cluster-auth-ssl - auth + - free-threaded - name: test-standard-auth-v8.0-pypy3.11-auth-ssl-sharded-cluster commands: - func: run server @@ -5288,7 +5162,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster-cov + - name: test-standard-auth-latest-python3.10-auth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -5296,19 +5170,19 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - COVERAGE: "1" + TEST_MIN_DEPS: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest - COVERAGE: "1" - TOOLCHAIN_VERSION: "3.11" + TEST_MIN_DEPS: "1" + TOOLCHAIN_VERSION: "3.10" tags: - test-standard-auth - server-latest - - python-3.11 + - python-3.10 - sharded_cluster-auth-ssl - auth - pr @@ -5334,7 +5208,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-rapid-python3.10-auth-ssl-sharded-cluster-min-deps + - name: test-standard-auth-rapid-python3.14-auth-ssl-sharded-cluster commands: - func: run server vars: @@ -5342,19 +5216,17 @@ 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" + TOOLCHAIN_VERSION: "3.14" tags: - test-standard-auth - server-rapid - - python-3.10 + - python-3.14 - sharded_cluster-auth-ssl - auth - name: test-standard-auth-rapid-pypy3.11-auth-ssl-sharded-cluster diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index b167508539..1442c2557a 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -491,15 +491,6 @@ buildvariants: - rhel87-small # Server version tests - - name: mongodb-v4.2 - tasks: - - name: .server-version - display_name: "* MongoDB v4.2" - run_on: - - rhel87-small - expansions: - VERSION: "4.2" - tags: [coverage_tag] - name: mongodb-v4.4 tasks: - name: .server-version diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 50fd8526aa..78bd3527cf 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -21,7 +21,7 @@ # Globals ############## -ALL_VERSIONS = ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"] +ALL_VERSIONS = ["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"] diff --git a/README.md b/README.md index 703e000dc5..125ceb8d46 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ a native Python driver for MongoDB, offering both synchronous and asynchronous A [gridfs](https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.md/) implementation on top of `pymongo`. -PyMongo supports MongoDB 4.0, 4.2, 4.4, 5.0, 6.0, 7.0, and 8.0. PyMongo follows [semantic versioning](https://semver.org/spec/v2.0.0.html) for its releases. +PyMongo supports MongoDB 4.4, 5.0, 6.0, 7.0, and 8.0. PyMongo follows [semantic versioning](https://semver.org/spec/v2.0.0.html) for its releases. ## Documentation diff --git a/doc/changelog.rst b/doc/changelog.rst index 585ea045c8..9f80729a5d 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -4,6 +4,7 @@ Changelog Changes in Version 4.18.0 ------------------------- +- Dropped support for MongoDB 4.2. PyMongo now requires MongoDB 4.4 or later. - Improved TLS connection performance by reusing TLS sessions across connections to the same server, avoiding a full handshake on each new connection. Session resumption is supported on all Python versions for synchronous clients diff --git a/pymongo/asynchronous/aggregation.py b/pymongo/asynchronous/aggregation.py index f1f77acc73..fc8597b273 100644 --- a/pymongo/asynchronous/aggregation.py +++ b/pymongo/asynchronous/aggregation.py @@ -140,13 +140,9 @@ async def get_cursor( cmd = {"aggregate": self._aggregation_target, "pipeline": self._pipeline} cmd.update(self._options) - # Apply this target's read concern if: - # readConcern has not been specified as a kwarg and either - # - server version is >= 4.2 or - # - server version is >= 3.2 and pipeline doesn't use $out - if ("readConcern" not in cmd) and ( - not self._performs_write or (conn.max_wire_version >= 8) - ): + # Apply this target's read concern if readConcern has not been specified as a kwarg. + # $out/$merge pipelines also support readConcern on all supported server versions (4.4+). + if "readConcern" not in cmd: read_concern = self._target.read_concern else: read_concern = None diff --git a/pymongo/asynchronous/bulk.py b/pymongo/asynchronous/bulk.py index 3075afa2b3..c97395db31 100644 --- a/pymongo/asynchronous/bulk.py +++ b/pymongo/asynchronous/bulk.py @@ -576,10 +576,6 @@ async def execute_no_results( raise ConfigurationError( "Must be connected to MongoDB 4.4+ to use hint on unacknowledged delete commands." ) - if unack and self.uses_hint_update and conn.max_wire_version < 8: - raise ConfigurationError( - "Must be connected to MongoDB 4.2+ to use hint on unacknowledged update commands." - ) if unack and self.uses_sort and conn.max_wire_version < 25: raise ConfigurationError( "Must be connected to MongoDB 8.0+ to use sort on unacknowledged update commands." diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index ddffc46632..70de079725 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -1010,10 +1010,6 @@ async def _update( else: update_doc["arrayFilters"] = array_filters if hint is not None: - if not acknowledged and conn.max_wire_version < 8: - raise ConfigurationError( - "Must be connected to MongoDB 4.2+ to use hint on unacknowledged update commands." - ) if not isinstance(hint, str): hint = helpers_shared._index_document(hint) update_doc["hint"] = hint @@ -3295,14 +3291,6 @@ async def _find_and_modify_helper( ) cmd["arrayFilters"] = list(array_filters) if hint is not None: - if conn.max_wire_version < 8: - raise ConfigurationError( - "Must be connected to MongoDB 4.2+ to use hint on find and modify commands." - ) - elif not acknowledged and conn.max_wire_version < 9: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use hint on unacknowledged find and modify commands." - ) cmd["hint"] = hint out = await self._command( conn, diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 6aeea53f4c..b2a1864ff7 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -1804,14 +1804,6 @@ async def _checkout( ): session._pin(server, conn) err_handler.contribute_socket(conn) - if ( - self._encrypter - and not self._encrypter._bypass_auto_encryption - and conn.max_wire_version < 8 - ): - raise ConfigurationError( - "Auto-encryption requires a minimum MongoDB version of 4.2" - ) yield conn async def _select_server( diff --git a/pymongo/asynchronous/server.py b/pymongo/asynchronous/server.py index 9a6984f486..af77238e20 100644 --- a/pymongo/asynchronous/server.py +++ b/pymongo/asynchronous/server.py @@ -157,18 +157,18 @@ async def run_operation( assert listeners is not None start = datetime.now() - use_cmd = operation.use_command(conn) + conn.validate_session(operation.client, operation.session) # type: ignore[arg-type] more_to_come = bool(operation.conn_mgr and operation.conn_mgr.more_to_come) - cmd, dbn = await self.operation_to_command(operation, conn, use_cmd) + cmd, dbn = await self.operation_to_command(operation, conn, True) if more_to_come: request_id = 0 data = b"" max_doc_size = 0 else: - message = operation.get_message(read_preference, conn, use_cmd) + message = operation.get_message(read_preference, conn) request_id, data, max_doc_size = self._split_message(message) - user_fields = _CURSOR_DOC_FIELDS if use_cmd else None + user_fields = _CURSOR_DOC_FIELDS docs, reply, duration = await run_cursor_command( conn, @@ -211,7 +211,6 @@ async def run_operation( conn=conn, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] more_to_come=more_to_come, ) @@ -221,7 +220,6 @@ async def run_operation( address=self._description.address, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] ) diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index 6af075cdc2..bc0af2a2b2 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -899,10 +899,10 @@ async def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None err_code = error.details.get("code", default) # type: ignore[union-attr] if err_code in helpers_shared._NOT_PRIMARY_CODES: is_shutting_down = err_code in helpers_shared._SHUTDOWN_CODES - # Mark server Unknown, clear the pool, and request check. + # Mark server Unknown and request check. Clear the pool only on shutdown. if not self._settings.load_balanced: await self._process_change(ServerDescription(address, error=error)) - if is_shutting_down or (err_ctx.max_wire_version <= 7): + if is_shutting_down: # Clear the pool. await server.reset(service_id) server.request_check() diff --git a/pymongo/common.py b/pymongo/common.py index 08da34c7bf..8350302e28 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -64,8 +64,8 @@ MAX_WRITE_BATCH_SIZE = 100000 # What this version of PyMongo supports. -MIN_SUPPORTED_SERVER_VERSION = "4.2" -MIN_SUPPORTED_WIRE_VERSION = 8 +MIN_SUPPORTED_SERVER_VERSION = "4.4" +MIN_SUPPORTED_WIRE_VERSION = 9 # MongoDB 9.0 MAX_SUPPORTED_WIRE_VERSION = 29 diff --git a/pymongo/message.py b/pymongo/message.py index bcd2810895..aaa56cb0b0 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -53,7 +53,6 @@ except ImportError: _use_c = False from pymongo.errors import ( - ConfigurationError, DocumentTooLarge, InvalidOperation, ProtocolError, @@ -379,54 +378,6 @@ def _op_msg( command[identifier] = docs -_pack_long_long = struct.Struct(" bytes: - """Get an OP_GET_MORE message.""" - return b"".join( - [ - _ZERO_32, - bson._make_c_string(collection_name), - _pack_int(num_to_return), - _pack_long_long(cursor_id), - ] - ) - - -def _get_more_compressed( - collection_name: str, - num_to_return: int, - cursor_id: int, - ctx: Union[SnappyContext, ZlibContext, ZstdContext], -) -> tuple[int, bytes]: - """Internal compressed getMore message helper.""" - return _compress(2005, _get_more_impl(collection_name, num_to_return, cursor_id), ctx) - - -def _get_more_uncompressed( - collection_name: str, num_to_return: int, cursor_id: int -) -> tuple[int, bytes]: - """Internal getMore message helper.""" - return __pack_message(2005, _get_more_impl(collection_name, num_to_return, cursor_id)) - - -if _use_c: - _get_more_uncompressed = _cmessage._get_more_message - - -def _get_more( - collection_name: str, - num_to_return: int, - cursor_id: int, - ctx: Union[SnappyContext, ZlibContext, ZstdContext, None] = None, -) -> tuple[int, bytes]: - """Get a **getMore** message.""" - if ctx: - return _get_more_compressed(collection_name, num_to_return, cursor_id, ctx) - return _get_more_uncompressed(collection_name, num_to_return, cursor_id) - - # OP_MSG ------------------------------------------------------------- @@ -1295,22 +1246,6 @@ def reset(self) -> None: def namespace(self) -> str: return f"{self.db}.{self.coll}" - def use_command(self, conn: _AgnosticConnection) -> bool: - use_find_cmd = False - if not self.exhaust: - use_find_cmd = True - elif conn.max_wire_version >= 8: - # OP_MSG supports exhaust on MongoDB 4.2+ - use_find_cmd = True - elif not self.read_concern.ok_for_legacy: - raise ConfigurationError( - f"read concern level of {self.read_concern.level} is not valid " - f"with a max wire version of {conn.max_wire_version}." - ) - - conn.validate_session(self.client, self.session) # type: ignore[arg-type] - return use_find_cmd - def update_command(self, cmd: dict[str, Any]) -> None: self._as_command = cmd, self.db @@ -1354,7 +1289,7 @@ def as_command( return self._as_command def get_message( - self, read_preference: _ServerMode, conn: _AgnosticConnection, use_cmd: bool = False + self, read_preference: _ServerMode, conn: _AgnosticConnection ) -> tuple[int, bytes, int]: """Get a query message""" # Use the read_preference decided by _socket_from_server. @@ -1428,17 +1363,6 @@ def reset(self) -> None: def namespace(self) -> str: return f"{self.db}.{self.coll}" - def use_command(self, conn: _AgnosticConnection) -> bool: - use_cmd = False - if not self.exhaust: - use_cmd = True - elif conn.max_wire_version >= 8: - # OP_MSG supports exhaust on MongoDB 4.2+ - use_cmd = True - - conn.validate_session(self.client, self.session) # type: ignore[arg-type] - return use_cmd - def update_command(self, cmd: dict[str, Any]) -> None: self._as_command = cmd, self.db @@ -1468,49 +1392,22 @@ def as_command( self._as_command = cmd, self.db return self._as_command - def get_message( - self, dummy0: Any, conn: _AgnosticConnection, use_cmd: bool = False - ) -> Union[tuple[int, bytes, int], tuple[int, bytes]]: + def get_message(self, dummy0: Any, conn: _AgnosticConnection) -> tuple[int, bytes, int]: """Get a getmore message.""" - ns = self.namespace() - ctx = conn.compression_context - - if use_cmd: - spec = self.as_command(conn)[0] - if self.conn_mgr and self.exhaust: - flags = _OpMsg.EXHAUST_ALLOWED - else: - flags = 0 - request_id, msg, size, _ = _op_msg( - flags, spec, self.db, None, self.codec_options, ctx=conn.compression_context - ) - return request_id, msg, size - - return _get_more(ns, self.ntoreturn, self.cursor_id, ctx) + spec = self.as_command(conn)[0] + flags = _OpMsg.EXHAUST_ALLOWED if (self.conn_mgr and self.exhaust) else 0 + request_id, msg, size, _ = _op_msg( + flags, spec, self.db, None, self.codec_options, ctx=conn.compression_context + ) + return request_id, msg, size class _RawBatchQuery(_Query): - def use_command(self, conn: _AgnosticConnection) -> bool: - # Compatibility checks. - super().use_command(conn) - if conn.max_wire_version >= 8: - # MongoDB 4.2+ supports exhaust over OP_MSG - return True - elif not self.exhaust: - return True - return False + pass class _RawBatchGetMore(_GetMore): - def use_command(self, conn: _AgnosticConnection) -> bool: - # Compatibility checks. - super().use_command(conn) - if conn.max_wire_version >= 8: - # MongoDB 4.2+ supports exhaust over OP_MSG - return True - elif not self.exhaust: - return True - return False + pass class _CursorAddress(tuple[Any, ...]): diff --git a/pymongo/response.py b/pymongo/response.py index 69cba81200..b832036708 100644 --- a/pymongo/response.py +++ b/pymongo/response.py @@ -27,7 +27,7 @@ class Response: - __slots__ = ("_address", "_data", "_docs", "_duration", "_from_command", "_request_id") + __slots__ = ("_address", "_data", "_docs", "_duration", "_request_id") def __init__( self, @@ -35,7 +35,6 @@ def __init__( address: _Address, request_id: int, duration: Optional[timedelta], - from_command: bool, docs: Sequence[Mapping[str, Any]], ): """Represent a response from the server. @@ -44,13 +43,11 @@ def __init__( :param address: (host, port) of the source server. :param request_id: The request id of this operation. :param duration: The duration of the operation. - :param from_command: if the response is the result of a db command. """ self._data = data self._address = address self._request_id = request_id self._duration = duration - self._from_command = from_command self._docs = docs @property @@ -73,11 +70,6 @@ def duration(self) -> Optional[timedelta]: """The duration of the operation.""" return self._duration - @property - def from_command(self) -> bool: - """If the response is a result from a db command.""" - return self._from_command - @property def docs(self) -> Sequence[Mapping[str, Any]]: """The decoded document(s).""" @@ -94,7 +86,6 @@ def __init__( conn: _AgnosticConnection, request_id: int, duration: Optional[timedelta], - from_command: bool, docs: list[_DocumentOut], more_to_come: bool, ): @@ -105,12 +96,11 @@ def __init__( :param conn: The AsyncConnection/Connection used for the initial query. :param request_id: The request id of this operation. :param duration: The duration of the operation. - :param from_command: If the response is the result of a db command. :param docs: List of documents. :param more_to_come: Bool indicating whether cursor is ready to be exhausted. """ - super().__init__(data, address, request_id, duration, from_command, docs) + super().__init__(data, address, request_id, duration, docs) self._conn = conn self._more_to_come = more_to_come diff --git a/pymongo/synchronous/aggregation.py b/pymongo/synchronous/aggregation.py index d540484fa8..050a4766b7 100644 --- a/pymongo/synchronous/aggregation.py +++ b/pymongo/synchronous/aggregation.py @@ -140,13 +140,9 @@ def get_cursor( cmd = {"aggregate": self._aggregation_target, "pipeline": self._pipeline} cmd.update(self._options) - # Apply this target's read concern if: - # readConcern has not been specified as a kwarg and either - # - server version is >= 4.2 or - # - server version is >= 3.2 and pipeline doesn't use $out - if ("readConcern" not in cmd) and ( - not self._performs_write or (conn.max_wire_version >= 8) - ): + # Apply this target's read concern if readConcern has not been specified as a kwarg. + # $out/$merge pipelines also support readConcern on all supported server versions (4.4+). + if "readConcern" not in cmd: read_concern = self._target.read_concern else: read_concern = None diff --git a/pymongo/synchronous/bulk.py b/pymongo/synchronous/bulk.py index 36081fe222..41ac25c9a2 100644 --- a/pymongo/synchronous/bulk.py +++ b/pymongo/synchronous/bulk.py @@ -574,10 +574,6 @@ def execute_no_results( raise ConfigurationError( "Must be connected to MongoDB 4.4+ to use hint on unacknowledged delete commands." ) - if unack and self.uses_hint_update and conn.max_wire_version < 8: - raise ConfigurationError( - "Must be connected to MongoDB 4.2+ to use hint on unacknowledged update commands." - ) if unack and self.uses_sort and conn.max_wire_version < 25: raise ConfigurationError( "Must be connected to MongoDB 8.0+ to use sort on unacknowledged update commands." diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index 15a38b2f3e..7fc211cabb 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -1010,10 +1010,6 @@ def _update( else: update_doc["arrayFilters"] = array_filters if hint is not None: - if not acknowledged and conn.max_wire_version < 8: - raise ConfigurationError( - "Must be connected to MongoDB 4.2+ to use hint on unacknowledged update commands." - ) if not isinstance(hint, str): hint = helpers_shared._index_document(hint) update_doc["hint"] = hint @@ -3289,14 +3285,6 @@ def _find_and_modify_helper( ) cmd["arrayFilters"] = list(array_filters) if hint is not None: - if conn.max_wire_version < 8: - raise ConfigurationError( - "Must be connected to MongoDB 4.2+ to use hint on find and modify commands." - ) - elif not acknowledged and conn.max_wire_version < 9: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use hint on unacknowledged find and modify commands." - ) cmd["hint"] = hint out = self._command( conn, diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 6b7c5d9c98..a228884ca6 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -1801,14 +1801,6 @@ def _checkout( ): session._pin(server, conn) err_handler.contribute_socket(conn) - if ( - self._encrypter - and not self._encrypter._bypass_auto_encryption - and conn.max_wire_version < 8 - ): - raise ConfigurationError( - "Auto-encryption requires a minimum MongoDB version of 4.2" - ) yield conn def _select_server( diff --git a/pymongo/synchronous/server.py b/pymongo/synchronous/server.py index 7aa017134a..9d85236bc6 100644 --- a/pymongo/synchronous/server.py +++ b/pymongo/synchronous/server.py @@ -157,18 +157,18 @@ def run_operation( assert listeners is not None start = datetime.now() - use_cmd = operation.use_command(conn) + conn.validate_session(operation.client, operation.session) # type: ignore[arg-type] more_to_come = bool(operation.conn_mgr and operation.conn_mgr.more_to_come) - cmd, dbn = self.operation_to_command(operation, conn, use_cmd) + cmd, dbn = self.operation_to_command(operation, conn, True) if more_to_come: request_id = 0 data = b"" max_doc_size = 0 else: - message = operation.get_message(read_preference, conn, use_cmd) + message = operation.get_message(read_preference, conn) request_id, data, max_doc_size = self._split_message(message) - user_fields = _CURSOR_DOC_FIELDS if use_cmd else None + user_fields = _CURSOR_DOC_FIELDS docs, reply, duration = run_cursor_command( conn, @@ -211,7 +211,6 @@ def run_operation( conn=conn, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] more_to_come=more_to_come, ) @@ -221,7 +220,6 @@ def run_operation( address=self._description.address, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] ) diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index b419833256..d4be0f945e 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -897,10 +897,10 @@ def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None: err_code = error.details.get("code", default) # type: ignore[union-attr] if err_code in helpers_shared._NOT_PRIMARY_CODES: is_shutting_down = err_code in helpers_shared._SHUTDOWN_CODES - # Mark server Unknown, clear the pool, and request check. + # Mark server Unknown and request check. Clear the pool only on shutdown. if not self._settings.load_balanced: self._process_change(ServerDescription(address, error=error)) - if is_shutting_down or (err_ctx.max_wire_version <= 7): + if is_shutting_down: # Clear the pool. server.reset(service_id) server.request_check() diff --git a/test/__init__.py b/test/__init__.py index f4ae7fe948..a6bbd42a3b 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -247,9 +247,6 @@ def _init_client(self): self.cmd_line = self.client.admin.command("getCmdLineOpts") self.server_status = self.client.admin.command("serverStatus") - if self.storage_engine == "mmapv1": - # MMAPv1 does not support retryWrites=True. - self.default_client_options["retryWrites"] = False hello = self.hello self.sessions_enabled = "logicalSessionTimeoutMinutes" in hello diff --git a/test/asynchronous/__init__.py b/test/asynchronous/__init__.py index 0699451d7e..b19bbc84d3 100644 --- a/test/asynchronous/__init__.py +++ b/test/asynchronous/__init__.py @@ -247,9 +247,6 @@ async def _init_client(self): self.cmd_line = await self.client.admin.command("getCmdLineOpts") self.server_status = await self.client.admin.command("serverStatus") - if self.storage_engine == "mmapv1": - # MMAPv1 does not support retryWrites=True. - self.default_client_options["retryWrites"] = False hello = await self.hello self.sessions_enabled = "logicalSessionTimeoutMinutes" in hello diff --git a/test/asynchronous/test_bulk.py b/test/asynchronous/test_bulk.py index b98983df9a..0052218bd4 100644 --- a/test/asynchronous/test_bulk.py +++ b/test/asynchronous/test_bulk.py @@ -165,7 +165,6 @@ async def _test_update_many(self, update): async def test_update_many(self): await self._test_update_many({"$set": {"foo": "bar"}}) - @async_client_context.require_version_min(4, 2, 0) async def test_update_many_pipeline(self): await self._test_update_many([{"$set": {"foo": "bar"}}]) @@ -206,7 +205,6 @@ async def _test_update_one(self, update): async def test_update_one(self): await self._test_update_one({"$set": {"foo": "bar"}}) - @async_client_context.require_version_min(4, 2, 0) async def test_update_one_pipeline(self): await self._test_update_one([{"$set": {"foo": "bar"}}]) diff --git a/test/asynchronous/test_change_stream.py b/test/asynchronous/test_change_stream.py index 508a42502a..809e8c6e9d 100644 --- a/test/asynchronous/test_change_stream.py +++ b/test/asynchronous/test_change_stream.py @@ -267,7 +267,6 @@ async def test_batch_size_is_honored(self): # $changeStream.startAtOperationTime was added in 4.0.0. @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_start_at_operation_time(self): optime = await self.get_start_at_operation_time() @@ -436,7 +435,6 @@ async def test_change_operations(self): await self._test_get_invalidate_event(change_stream) @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_start_after(self): resume_token = await self.get_resume_token(invalidate=True) @@ -452,7 +450,6 @@ async def test_start_after(self): self.assertEqual(change["fullDocument"], {"_id": 2}) @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_start_after_resume_process_with_changes(self): resume_token = await self.get_resume_token(invalidate=True) @@ -473,7 +470,6 @@ async def test_start_after_resume_process_with_changes(self): self.assertEqual(change["fullDocument"], {"_id": 3}) @no_type_check - @async_client_context.require_version_min(4, 2) async def test_start_after_resume_process_without_changes(self): resume_token = await self.get_resume_token(invalidate=True) @@ -563,12 +559,10 @@ async def _test_update_resume_token(self, expected_rt_getter): ) # Prose test no. 1 - @async_client_context.require_version_min(4, 2, 0) async def test_update_resume_token(self): await self._test_update_resume_token(self._get_expected_resume_token) # Prose test no. 2 - @async_client_context.require_version_min(4, 2, 0) async def test_raises_error_on_missing_id_418plus(self): # Server returns an error on 4.1.8+ await self._test_raises_error_on_missing_id(OperationFailure) @@ -636,7 +630,6 @@ def raise_error(): # Prose test no. 11 @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_resumetoken_empty_batch(self): client, listener = await self._client_with_listener("getMore") async with await self.change_stream_with_client(client) as change_stream: @@ -648,7 +641,6 @@ async def test_resumetoken_empty_batch(self): # Prose test no. 11 @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_resumetoken_exhausted_batch(self): client, listener = await self._client_with_listener("getMore") async with await self.change_stream_with_client(client) as change_stream: @@ -699,13 +691,11 @@ async def test_resumetoken_uniterated_nonempty_batch_resumeafter(self): # Prose test no. 14 @no_type_check @async_client_context.require_no_mongos - @async_client_context.require_version_min(4, 2, 0) async def test_resumetoken_uniterated_nonempty_batch_startafter(self): await self._test_resumetoken_uniterated_nonempty_batch("start_after") # Prose test no. 17 @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_startafter_resume_uses_startafter_after_empty_getMore(self): # Resume should use startAfter after no changes have been returned. resume_point = await self.get_resume_token() @@ -725,7 +715,6 @@ async def test_startafter_resume_uses_startafter_after_empty_getMore(self): # Prose test no. 18 @no_type_check - @async_client_context.require_version_min(4, 2, 0) async def test_startafter_resume_uses_resumeafter_after_nonempty_getMore(self): # Resume should use resumeAfter after some changes have been returned. resume_point = await self.get_resume_token() @@ -772,7 +761,6 @@ async def test_split_large_change(self): class TestClusterAsyncChangeStream(TestAsyncChangeStreamBase, APITestsMixin): dbs: list - @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() @@ -832,7 +820,6 @@ async def test_full_pipeline(self): class TestAsyncDatabaseAsyncChangeStream(TestAsyncChangeStreamBase, APITestsMixin): - @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_connections_survive_primary_stepdown_spec.py b/test/asynchronous/test_connections_survive_primary_stepdown_spec.py index 7ce93ab360..f3b729316e 100644 --- a/test/asynchronous/test_connections_survive_primary_stepdown_spec.py +++ b/test/asynchronous/test_connections_survive_primary_stepdown_spec.py @@ -74,7 +74,6 @@ def verify_pool_cleared(self): def verify_pool_not_cleared(self): self.assertEqual(self.listener.event_count(monitoring.PoolClearedEvent), 0) - @async_client_context.require_version_min(4, 2, -1) async def test_get_more_iteration(self): # Insert 5 documents with WC majority. await self.coll.insert_many([{"data": k} for k in range(5)]) @@ -118,17 +117,14 @@ async def run_scenario(self, error_code, retry, pool_status_checker): # Always retry here to ensure discovery of new primary. await self.coll.insert_one({"test": 1}) - @async_client_context.require_version_min(4, 2, -1) @async_client_context.require_test_commands async def test_not_primary_keep_connection_pool(self): await self.run_scenario(10107, True, self.verify_pool_not_cleared) - @async_client_context.require_version_min(4, 2, 0) @async_client_context.require_test_commands async def test_shutdown_in_progress(self): await self.run_scenario(91, False, self.verify_pool_cleared) - @async_client_context.require_version_min(4, 2, 0) @async_client_context.require_test_commands async def test_interrupted_at_shutdown(self): await self.run_scenario(11600, False, self.verify_pool_cleared) diff --git a/test/asynchronous/test_custom_types.py b/test/asynchronous/test_custom_types.py index c0564dd876..613a5083db 100644 --- a/test/asynchronous/test_custom_types.py +++ b/test/asynchronous/test_custom_types.py @@ -932,7 +932,6 @@ async def create_targets(self, *args, **kwargs): class TestDatabaseChangeStreamsWCustomTypes( AsyncIntegrationTest, ChangeStreamsWCustomTypesTestMixin ): - @async_client_context.require_version_min(4, 2, 0) @async_client_context.require_change_streams async def asyncSetUp(self): await super().asyncSetUp() @@ -952,7 +951,6 @@ async def create_targets(self, *args, **kwargs): class TestClusterChangeStreamsWCustomTypes( AsyncIntegrationTest, ChangeStreamsWCustomTypesTestMixin ): - @async_client_context.require_version_min(4, 2, 0) @async_client_context.require_change_streams async def asyncSetUp(self): await super().asyncSetUp() diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 57acf5824e..579216720d 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -233,7 +233,6 @@ 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) # type:ignore[untyped-decorator] async def asyncSetUp(self) -> None: await super().asyncSetUp() diff --git a/test/asynchronous/test_on_demand_csfle.py b/test/asynchronous/test_on_demand_csfle.py index cd1592318e..b69674b729 100644 --- a/test/asynchronous/test_on_demand_csfle.py +++ b/test/asynchronous/test_on_demand_csfle.py @@ -39,7 +39,6 @@ class TestonDemandGCPCredentials(AsyncIntegrationTest): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - @async_client_context.require_version_min(4, 2, -1) async def asyncSetUp(self): await super().asyncSetUp() self.master_key = { @@ -77,7 +76,6 @@ async def test_02_success(self): class TestonDemandAzureCredentials(AsyncIntegrationTest): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - @async_client_context.require_version_min(4, 2, -1) async def asyncSetUp(self): await super().asyncSetUp() self.master_key = { diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 7dae183338..2db73b72ee 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -316,7 +316,6 @@ async def gridfs_open_upload_stream(*args, **kwargs): await op(*args, session=s) # type: ignore # Require 4.2+ for large (16MB+) transactions. - @async_client_context.require_version_min(4, 2) @async_client_context.require_transactions @unittest.skipIf(sys.platform == "win32", "Our Windows machines are too slow to pass this test") async def test_transaction_starts_with_batched_write(self): diff --git a/test/test_bulk.py b/test/test_bulk.py index baefb1ad30..113c905a9b 100644 --- a/test/test_bulk.py +++ b/test/test_bulk.py @@ -165,7 +165,6 @@ def _test_update_many(self, update): def test_update_many(self): self._test_update_many({"$set": {"foo": "bar"}}) - @client_context.require_version_min(4, 2, 0) def test_update_many_pipeline(self): self._test_update_many([{"$set": {"foo": "bar"}}]) @@ -206,7 +205,6 @@ def _test_update_one(self, update): def test_update_one(self): self._test_update_one({"$set": {"foo": "bar"}}) - @client_context.require_version_min(4, 2, 0) def test_update_one_pipeline(self): self._test_update_one([{"$set": {"foo": "bar"}}]) diff --git a/test/test_change_stream.py b/test/test_change_stream.py index 01f73edd9a..13a709a109 100644 --- a/test/test_change_stream.py +++ b/test/test_change_stream.py @@ -263,7 +263,6 @@ def test_batch_size_is_honored(self): # $changeStream.startAtOperationTime was added in 4.0.0. @no_type_check - @client_context.require_version_min(4, 2, 0) def test_start_at_operation_time(self): optime = self.get_start_at_operation_time() @@ -432,7 +431,6 @@ def test_change_operations(self): self._test_get_invalidate_event(change_stream) @no_type_check - @client_context.require_version_min(4, 2, 0) def test_start_after(self): resume_token = self.get_resume_token(invalidate=True) @@ -448,7 +446,6 @@ def test_start_after(self): self.assertEqual(change["fullDocument"], {"_id": 2}) @no_type_check - @client_context.require_version_min(4, 2, 0) def test_start_after_resume_process_with_changes(self): resume_token = self.get_resume_token(invalidate=True) @@ -467,7 +464,6 @@ def test_start_after_resume_process_with_changes(self): self.assertEqual(change["fullDocument"], {"_id": 3}) @no_type_check - @client_context.require_version_min(4, 2) def test_start_after_resume_process_without_changes(self): resume_token = self.get_resume_token(invalidate=True) @@ -553,12 +549,10 @@ def _test_update_resume_token(self, expected_rt_getter): ) # Prose test no. 1 - @client_context.require_version_min(4, 2, 0) def test_update_resume_token(self): self._test_update_resume_token(self._get_expected_resume_token) # Prose test no. 2 - @client_context.require_version_min(4, 2, 0) def test_raises_error_on_missing_id_418plus(self): # Server returns an error on 4.1.8+ self._test_raises_error_on_missing_id(OperationFailure) @@ -626,7 +620,6 @@ def raise_error(): # Prose test no. 11 @no_type_check - @client_context.require_version_min(4, 2, 0) def test_resumetoken_empty_batch(self): client, listener = self._client_with_listener("getMore") with self.change_stream_with_client(client) as change_stream: @@ -638,7 +631,6 @@ def test_resumetoken_empty_batch(self): # Prose test no. 11 @no_type_check - @client_context.require_version_min(4, 2, 0) def test_resumetoken_exhausted_batch(self): client, listener = self._client_with_listener("getMore") with self.change_stream_with_client(client) as change_stream: @@ -689,13 +681,11 @@ def test_resumetoken_uniterated_nonempty_batch_resumeafter(self): # Prose test no. 14 @no_type_check @client_context.require_no_mongos - @client_context.require_version_min(4, 2, 0) def test_resumetoken_uniterated_nonempty_batch_startafter(self): self._test_resumetoken_uniterated_nonempty_batch("start_after") # Prose test no. 17 @no_type_check - @client_context.require_version_min(4, 2, 0) def test_startafter_resume_uses_startafter_after_empty_getMore(self): # Resume should use startAfter after no changes have been returned. resume_point = self.get_resume_token() @@ -713,7 +703,6 @@ def test_startafter_resume_uses_startafter_after_empty_getMore(self): # Prose test no. 18 @no_type_check - @client_context.require_version_min(4, 2, 0) def test_startafter_resume_uses_resumeafter_after_nonempty_getMore(self): # Resume should use resumeAfter after some changes have been returned. resume_point = self.get_resume_token() @@ -758,7 +747,6 @@ def test_split_large_change(self): class TestClusterChangeStream(TestChangeStreamBase, APITestsMixin): dbs: list - @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() @@ -818,7 +806,6 @@ def test_full_pipeline(self): class TestDatabaseChangeStream(TestChangeStreamBase, APITestsMixin): - @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_connections_survive_primary_stepdown_spec.py b/test/test_connections_survive_primary_stepdown_spec.py index 51d37b7c57..dfcec79322 100644 --- a/test/test_connections_survive_primary_stepdown_spec.py +++ b/test/test_connections_survive_primary_stepdown_spec.py @@ -74,7 +74,6 @@ def verify_pool_cleared(self): def verify_pool_not_cleared(self): self.assertEqual(self.listener.event_count(monitoring.PoolClearedEvent), 0) - @client_context.require_version_min(4, 2, -1) def test_get_more_iteration(self): # Insert 5 documents with WC majority. self.coll.insert_many([{"data": k} for k in range(5)]) @@ -118,17 +117,14 @@ def run_scenario(self, error_code, retry, pool_status_checker): # Always retry here to ensure discovery of new primary. self.coll.insert_one({"test": 1}) - @client_context.require_version_min(4, 2, -1) @client_context.require_test_commands def test_not_primary_keep_connection_pool(self): self.run_scenario(10107, True, self.verify_pool_not_cleared) - @client_context.require_version_min(4, 2, 0) @client_context.require_test_commands def test_shutdown_in_progress(self): self.run_scenario(91, False, self.verify_pool_cleared) - @client_context.require_version_min(4, 2, 0) @client_context.require_test_commands def test_interrupted_at_shutdown(self): self.run_scenario(11600, False, self.verify_pool_cleared) diff --git a/test/test_custom_types.py b/test/test_custom_types.py index b55eb64d9d..b15449c2f5 100644 --- a/test/test_custom_types.py +++ b/test/test_custom_types.py @@ -928,7 +928,6 @@ def create_targets(self, *args, **kwargs): class TestDatabaseChangeStreamsWCustomTypes(IntegrationTest, ChangeStreamsWCustomTypesTestMixin): - @client_context.require_version_min(4, 2, 0) @client_context.require_change_streams def setUp(self): super().setUp() @@ -946,7 +945,6 @@ def create_targets(self, *args, **kwargs): class TestClusterChangeStreamsWCustomTypes(IntegrationTest, ChangeStreamsWCustomTypesTestMixin): - @client_context.require_version_min(4, 2, 0) @client_context.require_change_streams def setUp(self): super().setUp() diff --git a/test/test_encryption.py b/test/test_encryption.py index 3bb6e767b4..975a091117 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -233,7 +233,6 @@ 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) # type:ignore[untyped-decorator] def setUp(self) -> None: super().setUp() diff --git a/test/test_on_demand_csfle.py b/test/test_on_demand_csfle.py index 24a0f917e2..607752524d 100644 --- a/test/test_on_demand_csfle.py +++ b/test/test_on_demand_csfle.py @@ -39,7 +39,6 @@ class TestonDemandGCPCredentials(IntegrationTest): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - @client_context.require_version_min(4, 2, -1) def setUp(self): super().setUp() self.master_key = { @@ -77,7 +76,6 @@ def test_02_success(self): class TestonDemandAzureCredentials(IntegrationTest): @unittest.skipUnless(_HAVE_PYMONGOCRYPT, "pymongocrypt is not installed") - @client_context.require_version_min(4, 2, -1) def setUp(self): super().setUp() self.master_key = { diff --git a/test/test_transactions.py b/test/test_transactions.py index 10386c3873..bd3760c568 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -308,7 +308,6 @@ def gridfs_open_upload_stream(*args, **kwargs): op(*args, session=s) # type: ignore # Require 4.2+ for large (16MB+) transactions. - @client_context.require_version_min(4, 2) @client_context.require_transactions @unittest.skipIf(sys.platform == "win32", "Our Windows machines are too slow to pass this test") def test_transaction_starts_with_batched_write(self): From b2b6c5bbe5cf7730adc56df57c360beba04ca2de Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 24 Jun 2026 18:47:32 -0500 Subject: [PATCH 02/28] PYTHON-5808 Fix CI failures after dropping MongoDB 4.2 support Update post-42-*.json SDAM error fixtures and test_wire_version to use wire version 9 (MongoDB 4.4), which is now the minimum supported version. --- .../errors/post-42-InterruptedAtShutdown.json | 4 ++-- .../errors/post-42-InterruptedDueToReplStateChange.json | 4 ++-- .../errors/post-42-LegacyNotPrimary.json | 4 ++-- .../errors/post-42-NotPrimaryNoSecondaryOk.json | 4 ++-- .../errors/post-42-NotPrimaryOrSecondary.json | 4 ++-- .../errors/post-42-NotWritablePrimary.json | 4 ++-- .../errors/post-42-PrimarySteppedDown.json | 4 ++-- .../errors/post-42-ShutdownInProgress.json | 4 ++-- test/test_topology.py | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json b/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json index 40c4ed6c80..f9aa9a3aaf 100644 --- a/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json +++ b/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json b/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json index 5c489f5ecb..c729ad8697 100644 --- a/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json +++ b/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json b/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json index f0851b299e..9d84032ca6 100644 --- a/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json +++ b/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json b/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json index a675f0ca54..dad9015cb9 100644 --- a/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json +++ b/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json b/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json index ea9bf1d16b..78b858dc92 100644 --- a/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json +++ b/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json b/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json index 10211fca70..fd603ab774 100644 --- a/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json +++ b/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json b/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json index fa98d0bf06..16b84f6cc7 100644 --- a/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json +++ b/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json b/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json index cd587205b6..456eb8475c 100644 --- a/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json +++ b/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 8 + "maxWireVersion": 9 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 8, + "maxWireVersion": 9, "type": "command", "response": { "ok": 0, diff --git a/test/test_topology.py b/test/test_topology.py index 47e670b20d..489c60f6b0 100644 --- a/test/test_topology.py +++ b/test/test_topology.py @@ -581,7 +581,7 @@ def test_wire_version(self): ) self.assertEqual(server.description.min_wire_version, 1) - self.assertEqual(server.description.max_wire_version, 8) + self.assertEqual(server.description.max_wire_version, common.MIN_SUPPORTED_WIRE_VERSION) t.select_servers(any_server_selector, _Op.TEST) # Incompatible. From c38d908defde8298cd7e63decdafcff541fec03e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 24 Jun 2026 18:51:19 -0500 Subject: [PATCH 03/28] Revert "PYTHON-5808 Fix CI failures after dropping MongoDB 4.2 support" This reverts commit b2b6c5bbe5cf7730adc56df57c360beba04ca2de. --- .../errors/post-42-InterruptedAtShutdown.json | 4 ++-- .../errors/post-42-InterruptedDueToReplStateChange.json | 4 ++-- .../errors/post-42-LegacyNotPrimary.json | 4 ++-- .../errors/post-42-NotPrimaryNoSecondaryOk.json | 4 ++-- .../errors/post-42-NotPrimaryOrSecondary.json | 4 ++-- .../errors/post-42-NotWritablePrimary.json | 4 ++-- .../errors/post-42-PrimarySteppedDown.json | 4 ++-- .../errors/post-42-ShutdownInProgress.json | 4 ++-- test/test_topology.py | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json b/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json index f9aa9a3aaf..40c4ed6c80 100644 --- a/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json +++ b/test/discovery_and_monitoring/errors/post-42-InterruptedAtShutdown.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json b/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json index c729ad8697..5c489f5ecb 100644 --- a/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json +++ b/test/discovery_and_monitoring/errors/post-42-InterruptedDueToReplStateChange.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json b/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json index 9d84032ca6..f0851b299e 100644 --- a/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json +++ b/test/discovery_and_monitoring/errors/post-42-LegacyNotPrimary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json b/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json index dad9015cb9..a675f0ca54 100644 --- a/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json +++ b/test/discovery_and_monitoring/errors/post-42-NotPrimaryNoSecondaryOk.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json b/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json index 78b858dc92..ea9bf1d16b 100644 --- a/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json +++ b/test/discovery_and_monitoring/errors/post-42-NotPrimaryOrSecondary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json b/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json index fd603ab774..10211fca70 100644 --- a/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json +++ b/test/discovery_and_monitoring/errors/post-42-NotWritablePrimary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json b/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json index 16b84f6cc7..fa98d0bf06 100644 --- a/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json +++ b/test/discovery_and_monitoring/errors/post-42-PrimarySteppedDown.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json b/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json index 456eb8475c..cd587205b6 100644 --- a/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json +++ b/test/discovery_and_monitoring/errors/post-42-ShutdownInProgress.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 9 + "maxWireVersion": 8 } ] ], @@ -42,7 +42,7 @@ { "address": "a:27017", "when": "afterHandshakeCompletes", - "maxWireVersion": 9, + "maxWireVersion": 8, "type": "command", "response": { "ok": 0, diff --git a/test/test_topology.py b/test/test_topology.py index 489c60f6b0..47e670b20d 100644 --- a/test/test_topology.py +++ b/test/test_topology.py @@ -581,7 +581,7 @@ def test_wire_version(self): ) self.assertEqual(server.description.min_wire_version, 1) - self.assertEqual(server.description.max_wire_version, common.MIN_SUPPORTED_WIRE_VERSION) + self.assertEqual(server.description.max_wire_version, 8) t.select_servers(any_server_selector, _Op.TEST) # Incompatible. From f4122aaf4de8e6a2c4ac86f0a00e1819c2c9f004 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 24 Jun 2026 19:12:10 -0500 Subject: [PATCH 04/28] PYTHON-5808 Fix CI failures after dropping MongoDB 4.2 support Skip SDAM error spec tests whose mock servers report wire versions below the new minimum (9 / MongoDB 4.4), and update the test_wire_version assertion to use MIN_SUPPORTED_WIRE_VERSION. --- .../test_discovery_and_monitoring.py | 16 ++++++++++++++++ test/test_discovery_and_monitoring.py | 16 ++++++++++++++++ test/test_topology.py | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/test/asynchronous/test_discovery_and_monitoring.py b/test/asynchronous/test_discovery_and_monitoring.py index 6867a12060..38cf269374 100644 --- a/test/asynchronous/test_discovery_and_monitoring.py +++ b/test/asynchronous/test_discovery_and_monitoring.py @@ -269,6 +269,22 @@ def create_tests(): test_name = f"test_{dirname}_{os.path.splitext(filename)[0]}" new_test.__name__ = test_name + + # Skip scenarios where all mock server responses report a wire + # version below the minimum supported (server no longer reachable). + max_version = max( + ( + r[1].get("maxWireVersion", 0) + for phase in scenario_def.get("phases", []) + for r in phase.get("responses", []) + ), + default=common.MIN_SUPPORTED_WIRE_VERSION, + ) + if max_version < common.MIN_SUPPORTED_WIRE_VERSION: + new_test = unittest.skip( + f"Server wire version {max_version} is below minimum {common.MIN_SUPPORTED_WIRE_VERSION}" + )(new_test) + setattr(TestAllScenarios, new_test.__name__, new_test) diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index 1f2c21f9c1..eb156447f7 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -269,6 +269,22 @@ def create_tests(): test_name = f"test_{dirname}_{os.path.splitext(filename)[0]}" new_test.__name__ = test_name + + # Skip scenarios where all mock server responses report a wire + # version below the minimum supported (server no longer reachable). + max_version = max( + ( + r[1].get("maxWireVersion", 0) + for phase in scenario_def.get("phases", []) + for r in phase.get("responses", []) + ), + default=common.MIN_SUPPORTED_WIRE_VERSION, + ) + if max_version < common.MIN_SUPPORTED_WIRE_VERSION: + new_test = unittest.skip( + f"Server wire version {max_version} is below minimum {common.MIN_SUPPORTED_WIRE_VERSION}" + )(new_test) + setattr(TestAllScenarios, new_test.__name__, new_test) diff --git a/test/test_topology.py b/test/test_topology.py index 47e670b20d..489c60f6b0 100644 --- a/test/test_topology.py +++ b/test/test_topology.py @@ -581,7 +581,7 @@ def test_wire_version(self): ) self.assertEqual(server.description.min_wire_version, 1) - self.assertEqual(server.description.max_wire_version, 8) + self.assertEqual(server.description.max_wire_version, common.MIN_SUPPORTED_WIRE_VERSION) t.select_servers(any_server_selector, _Op.TEST) # Incompatible. From 6a1bad41ecda690c720af5f87206066c2f4f54cd Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 25 Jun 2026 05:43:39 -0500 Subject: [PATCH 05/28] PYTHON-5808 Fix mockupdb CI failures after dropping MongoDB 4.2 support Update mock servers in mockupdb tests to use MIN_SUPPORTED_WIRE_VERSION instead of the hardcoded wire version 8 (MongoDB 4.2). --- test/mockupdb/test_cursor_namespace.py | 2 +- test/mockupdb/test_op_msg.py | 3 ++- test/mockupdb/test_standalone_shard.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/mockupdb/test_cursor_namespace.py b/test/mockupdb/test_cursor_namespace.py index f77f4a5c2c..adc73ee4e6 100644 --- a/test/mockupdb/test_cursor_namespace.py +++ b/test/mockupdb/test_cursor_namespace.py @@ -42,7 +42,7 @@ class TestCursorNamespace(PyMongoTestCase): @classmethod def setUpClass(cls): - cls.server = MockupDB(auto_ismaster={"maxWireVersion": 8}) + cls.server = MockupDB(auto_ismaster={"maxWireVersion": MIN_SUPPORTED_WIRE_VERSION}) cls.server.run() cls.client = cls.unmanaged_simple_client(cls.server.uri) diff --git a/test/mockupdb/test_op_msg.py b/test/mockupdb/test_op_msg.py index a509d13182..2b97464593 100644 --- a/test/mockupdb/test_op_msg.py +++ b/test/mockupdb/test_op_msg.py @@ -28,6 +28,7 @@ _HAVE_MOCKUPDB = False from pymongo import MongoClient, WriteConcern +from pymongo.common import MIN_SUPPORTED_WIRE_VERSION from pymongo.cursor_shared import CursorType from pymongo.operations import DeleteOne, InsertOne, UpdateOne @@ -281,7 +282,7 @@ class TestOpMsg(PyMongoTestCase): @classmethod def setUpClass(cls): - cls.server = MockupDB(auto_ismaster=True, max_wire_version=8) + cls.server = MockupDB(auto_ismaster=True, max_wire_version=MIN_SUPPORTED_WIRE_VERSION) cls.server.run() cls.client = cls.unmanaged_simple_client(cls.server.uri) diff --git a/test/mockupdb/test_standalone_shard.py b/test/mockupdb/test_standalone_shard.py index c290d63bce..c5f1517484 100644 --- a/test/mockupdb/test_standalone_shard.py +++ b/test/mockupdb/test_standalone_shard.py @@ -29,6 +29,7 @@ from pymongo import MongoClient +from pymongo.common import MIN_SUPPORTED_WIRE_VERSION from pymongo.errors import OperationFailure pytestmark = pytest.mark.mockupdb @@ -37,7 +38,7 @@ class TestStandaloneShard(unittest.TestCase): # See PYTHON-2048 and SERVER-44591. def test_bulk_txn_error_message(self): - server = MockupDB(auto_ismaster={"maxWireVersion": 8}) + server = MockupDB(auto_ismaster={"maxWireVersion": MIN_SUPPORTED_WIRE_VERSION}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) From 3e6873a00f2dfd29f45aa3556d0d30bb07ef8985 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 25 Jun 2026 07:31:37 -0500 Subject: [PATCH 06/28] PYTHON-5808 Fix gevent coverage and auth CI failures Fix shlex-splitting bug in coverage command that broke multi-word marker expressions (e.g. 'not default_async and default') when GREEN_FRAMEWORK is set, by building subprocess args as a list instead of a shell string. Switch enterprise auth setup to use SASL_HOST_BUILD and PRINCIPAL_BUILD secrets so GSSAPI and SASL tests connect to a MongoDB 4.4+ server instead of the legacy 4.2 LDAP test server. --- .evergreen/scripts/run_tests.py | 12 ++++++------ .evergreen/scripts/setup_tests.py | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py index 49986c67ab..0ca9855540 100644 --- a/.evergreen/scripts/run_tests.py +++ b/.evergreen/scripts/run_tests.py @@ -4,7 +4,6 @@ import logging import os import platform -import shlex import shutil import subprocess import sys @@ -205,11 +204,12 @@ def run() -> None: 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 + binary = sys.executable + result = subprocess.run( # noqa: S603 + [binary, "-m", "coverage", "run", "-m", "pytest", *TEST_ARGS, *sys.argv[1:]], + check=False, + ) + subprocess.run([binary, "-m", "coverage", "report"], check=False) # noqa: S603 if result.returncode != 0: print(result.stderr) sys.exit(result.returncode) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index e188dcaa9d..7eeb7b511e 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -263,16 +263,17 @@ def handle_test_env() -> None: keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) - principal = config["PRINCIPAL"] + principal = config["PRINCIPAL_BUILD"] LOGGER.info("Running kinit") os.environ["KRB5_CONFIG"] = str(krb_conf) cmd = f"kinit -k -t {keytab_file} -p {principal}" run_command(cmd) LOGGER.info("Setting GSSAPI variables") - write_env("GSSAPI_HOST", config["SASL_HOST"]) + write_env("GSSAPI_HOST", config["SASL_HOST_BUILD"]) write_env("GSSAPI_PORT", config["SASL_PORT"]) - write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"]) + write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL_BUILD"]) + write_env("SASL_HOST", config["SASL_HOST_BUILD"]) if test_name == "doctest": UV_ARGS.append("--extra docs") From 2a8974eef5f518436192e36a90b7222ef37facd0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 25 Jun 2026 07:36:29 -0500 Subject: [PATCH 07/28] PYTHON-5808 Use KEYTAB_BASE64_BUILD for enterprise auth kinit The KEYTAB_BASE64 keytab only contains entries for the old PRINCIPAL (ldaptest.10gen.cc). Switch to KEYTAB_BASE64_BUILD which matches PRINCIPAL_BUILD (ldaptest.build.10gen.cc). --- .evergreen/scripts/setup_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 7eeb7b511e..81160025a3 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -259,7 +259,7 @@ def handle_test_env() -> None: krb_conf.touch() write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") - keytab = base64.b64decode(config["KEYTAB_BASE64"]) + keytab = base64.b64decode(config["KEYTAB_BASE64_BUILD"]) keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) From fcb2f7abeae16ff6a82d1e3d201ea36bc6f1da4f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 1 Jul 2026 16:40:55 -0500 Subject: [PATCH 08/28] PYTHON-5808 Revert KEYTAB_BASE64_BUILD change in setup_tests.py Use the original KEYTAB_BASE64 secret for kinit. --- .evergreen/scripts/setup_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 81160025a3..7eeb7b511e 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -259,7 +259,7 @@ def handle_test_env() -> None: krb_conf.touch() write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") - keytab = base64.b64decode(config["KEYTAB_BASE64_BUILD"]) + keytab = base64.b64decode(config["KEYTAB_BASE64"]) keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) From bfe0f4303c702defea3e16d2f0a5378c3da986a5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 1 Jul 2026 17:25:39 -0500 Subject: [PATCH 09/28] PYTHON-5808 Configure krb5.conf to use BUILD LDAP realm Point kinit at ldaptest.build.10gen.cc (the MongoDB 4.4+ LDAP server) by writing a krb5.conf with the appropriate realm configuration instead of using an empty file. --- .evergreen/scripts/setup_tests.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 7eeb7b511e..2769346bb7 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -256,7 +256,15 @@ def handle_test_env() -> None: else: # BUILD-3830 krb_conf = ROOT / ".evergreen/krb5.conf.empty" - krb_conf.touch() + krb_conf.write_text( + "[libdefaults]\n" + " default_realm = LDAPTEST.10GEN.CC\n\n" + "[realms]\n" + " LDAPTEST.10GEN.CC = {\n" + " kdc = ldaptest.build.10gen.cc\n" + " admin_server = ldaptest.build.10gen.cc\n" + " }\n" + ) write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") keytab = base64.b64decode(config["KEYTAB_BASE64"]) From 07bd003bc270383813365702f0625d1231a7f3c1 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 1 Jul 2026 17:38:07 -0500 Subject: [PATCH 10/28] PYTHON-5808 Use PRINCIPAL (not PRINCIPAL_BUILD) for kinit and GSSAPI --- .evergreen/scripts/setup_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 2769346bb7..57513f68df 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -271,7 +271,7 @@ def handle_test_env() -> None: keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) - principal = config["PRINCIPAL_BUILD"] + principal = config["PRINCIPAL"] LOGGER.info("Running kinit") os.environ["KRB5_CONFIG"] = str(krb_conf) cmd = f"kinit -k -t {keytab_file} -p {principal}" @@ -280,7 +280,7 @@ def handle_test_env() -> None: LOGGER.info("Setting GSSAPI variables") write_env("GSSAPI_HOST", config["SASL_HOST_BUILD"]) write_env("GSSAPI_PORT", config["SASL_PORT"]) - write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL_BUILD"]) + write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"]) write_env("SASL_HOST", config["SASL_HOST_BUILD"]) if test_name == "doctest": From a85318804923943e323eb072d42ce9a41a9af105 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 6 Jul 2026 16:17:08 -0500 Subject: [PATCH 11/28] try with all new secrets --- .evergreen/scripts/setup_tests.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 57513f68df..75df7f44c2 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -256,18 +256,9 @@ def handle_test_env() -> None: else: # BUILD-3830 krb_conf = ROOT / ".evergreen/krb5.conf.empty" - krb_conf.write_text( - "[libdefaults]\n" - " default_realm = LDAPTEST.10GEN.CC\n\n" - "[realms]\n" - " LDAPTEST.10GEN.CC = {\n" - " kdc = ldaptest.build.10gen.cc\n" - " admin_server = ldaptest.build.10gen.cc\n" - " }\n" - ) write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") - keytab = base64.b64decode(config["KEYTAB_BASE64"]) + keytab = base64.b64decode(config["KEYTAB_BASE64_BUILD"]) keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) @@ -280,7 +271,7 @@ def handle_test_env() -> None: LOGGER.info("Setting GSSAPI variables") write_env("GSSAPI_HOST", config["SASL_HOST_BUILD"]) write_env("GSSAPI_PORT", config["SASL_PORT"]) - write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"]) + write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL_BUILD"]) write_env("SASL_HOST", config["SASL_HOST_BUILD"]) if test_name == "doctest": From 48505a4125d358d169942ac45ebf60ffc6ea6d33 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 6 Jul 2026 16:18:37 -0500 Subject: [PATCH 12/28] fix krb conf --- .evergreen/scripts/setup_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 75df7f44c2..7342a122db 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -256,9 +256,10 @@ def handle_test_env() -> None: else: # BUILD-3830 krb_conf = ROOT / ".evergreen/krb5.conf.empty" + krb_conf.touch() write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") - keytab = base64.b64decode(config["KEYTAB_BASE64_BUILD"]) + keytab = base64.b64decode(config["KEYTAB_BASE64"]) keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) @@ -271,7 +272,7 @@ def handle_test_env() -> None: LOGGER.info("Setting GSSAPI variables") write_env("GSSAPI_HOST", config["SASL_HOST_BUILD"]) write_env("GSSAPI_PORT", config["SASL_PORT"]) - write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL_BUILD"]) + write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"]) write_env("SASL_HOST", config["SASL_HOST_BUILD"]) if test_name == "doctest": From bda89fd214590e6f1db148afef3369ffc38ad066 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 6 Jul 2026 16:24:14 -0500 Subject: [PATCH 13/28] fix conf --- .evergreen/scripts/setup_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 7342a122db..751c66fb89 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -259,11 +259,11 @@ def handle_test_env() -> None: krb_conf.touch() write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") - keytab = base64.b64decode(config["KEYTAB_BASE64"]) + keytab = base64.b64decode(config["KEYTAB_BASE64_BUILD"]) keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) - principal = config["PRINCIPAL"] + principal = config["PRINCIPAL_BUILD"] LOGGER.info("Running kinit") os.environ["KRB5_CONFIG"] = str(krb_conf) cmd = f"kinit -k -t {keytab_file} -p {principal}" From ad6c63a50edb80a4b192a7d54cc1d398352f3ffa Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 6 Jul 2026 16:25:42 -0500 Subject: [PATCH 14/28] fix conf --- .evergreen/scripts/setup_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 751c66fb89..81160025a3 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -272,7 +272,7 @@ def handle_test_env() -> None: LOGGER.info("Setting GSSAPI variables") write_env("GSSAPI_HOST", config["SASL_HOST_BUILD"]) write_env("GSSAPI_PORT", config["SASL_PORT"]) - write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"]) + write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL_BUILD"]) write_env("SASL_HOST", config["SASL_HOST_BUILD"]) if test_name == "doctest": From 355fb924e6e80f684967a4ce858ef596e8a51e30 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 19 Aug 2026 13:50:06 -0500 Subject: [PATCH 15/28] use the windows-2022-latest-small-DEVPROD-19149 host --- .evergreen/scripts/generate_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 6dfa014a16..335b468ec9 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -231,6 +231,8 @@ def create_enterprise_auth_variants(): if host == "win64": # https://jira.mongodb.org/browse/PYTHON-5704 tasks = [".test-standard-auth !.pypy .auth !.free-threaded"] + if host == "rhel": + host = "windows-2022-latest-small-DEVPROD-19149" # noqa: PLW2901 variant = create_variant(tasks, display_name, host=host, expansions=expansions) variants.append(variant) return variants From 4025abaaa718bcf9c4c0d32782339b70ea870a67 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 19 Aug 2026 13:53:45 -0500 Subject: [PATCH 16/28] use the windows-2022-latest-small-DEVPROD-19149 host --- .evergreen/scripts/generate_config.py | 4 ++-- .evergreen/scripts/generate_config_utils.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 335b468ec9..18927feadc 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -231,8 +231,8 @@ def create_enterprise_auth_variants(): if host == "win64": # https://jira.mongodb.org/browse/PYTHON-5704 tasks = [".test-standard-auth !.pypy .auth !.free-threaded"] - if host == "rhel": - host = "windows-2022-latest-small-DEVPROD-19149" # noqa: PLW2901 + if host == "win64": + host = "DEVPROD-19149" # noqa: PLW2901 variant = create_variant(tasks, display_name, host=host, expansions=expansions) variants.append(variant) return variants diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 78bd3527cf..8b02ee8538 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -67,6 +67,9 @@ class Host: 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()) +HOSTS["DEVPROD-19149"] = Host( + "DEVPROD-19149", "windows-2022-latest-small-DEVPROD-19149", "DEVPROD-19149", dict() +) DEFAULT_HOST = HOSTS["rhel8"] # Other hosts From a817f992c4a23074762802bbc850efb8974bf26b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 19 Aug 2026 14:41:25 -0500 Subject: [PATCH 17/28] PYTHON-5808 Fix _Query.get_message() call after merge Upstream removed the use_cmd parameter from _Query.get_message(). Update the call site in cursor_base.py to match the new signature. --- pymongo/asynchronous/cursor_base.py | 2 +- pymongo/synchronous/cursor_base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongo/asynchronous/cursor_base.py b/pymongo/asynchronous/cursor_base.py index e8ac4c3139..2c3e5f02f2 100644 --- a/pymongo/asynchronous/cursor_base.py +++ b/pymongo/asynchronous/cursor_base.py @@ -116,7 +116,7 @@ async def _run_with_conn( if more_to_come: request_id, data, max_doc_size = 0, b"", 0 else: - message = operation.get_message(read_preference, conn, use_cmd) + message = operation.get_message(read_preference, conn) request_id, data, max_doc_size = _split_message(message) user_fields = _CURSOR_DOC_FIELDS if use_cmd else None docs, reply, duration = await run_cursor_command( diff --git a/pymongo/synchronous/cursor_base.py b/pymongo/synchronous/cursor_base.py index 4cad4e0c09..5a5698b35c 100644 --- a/pymongo/synchronous/cursor_base.py +++ b/pymongo/synchronous/cursor_base.py @@ -116,7 +116,7 @@ def _run_with_conn( if more_to_come: request_id, data, max_doc_size = 0, b"", 0 else: - message = operation.get_message(read_preference, conn, use_cmd) + message = operation.get_message(read_preference, conn) request_id, data, max_doc_size = _split_message(message) user_fields = _CURSOR_DOC_FIELDS if use_cmd else None docs, reply, duration = run_cursor_command( From 04f7e23933d0d7afc7bd01d076009c054300da53 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 19 Aug 2026 15:06:44 -0500 Subject: [PATCH 18/28] PYTHON-5808 Remove from_command arg from Response/PinnedResponse constructors --- pymongo/asynchronous/cursor_base.py | 2 -- pymongo/synchronous/cursor_base.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/pymongo/asynchronous/cursor_base.py b/pymongo/asynchronous/cursor_base.py index 2c3e5f02f2..0f07769f20 100644 --- a/pymongo/asynchronous/cursor_base.py +++ b/pymongo/asynchronous/cursor_base.py @@ -154,7 +154,6 @@ async def _run_with_conn( conn=conn, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] more_to_come=more_to_come, ) @@ -163,7 +162,6 @@ async def _run_with_conn( address=conn.address, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] ) diff --git a/pymongo/synchronous/cursor_base.py b/pymongo/synchronous/cursor_base.py index 5a5698b35c..3a04fad681 100644 --- a/pymongo/synchronous/cursor_base.py +++ b/pymongo/synchronous/cursor_base.py @@ -154,7 +154,6 @@ def _run_with_conn( conn=conn, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] more_to_come=more_to_come, ) @@ -163,7 +162,6 @@ def _run_with_conn( address=conn.address, duration=duration, request_id=request_id, - from_command=use_cmd, docs=docs, # type: ignore[arg-type] ) From b2e776d78aa97b35fb7af1fc51fcb99c52b364a0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 19 Aug 2026 15:14:44 -0500 Subject: [PATCH 19/28] PYTHON-5808 Fix auth-enterprise-win64 variant to use DEVPROD-19149 distro --- .evergreen/generated_configs/variants.yml | 2 +- .evergreen/scripts/generate_config_utils.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 62d139dfd3..a0dbc0e450 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -284,7 +284,7 @@ buildvariants: - name: .test-standard-auth !.pypy .auth !.free-threaded display_name: Auth Enterprise Win64 run_on: - - windows-2022-latest-small + - windows-2022-latest-small-DEVPROD-19149 expansions: TEST_NAME: enterprise_auth AUTH: auth diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index d7630dd558..1b76184a6b 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -143,9 +143,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" + # 8.0+ Windows builds must run on win-latest, unless an explicit host overrides this + if ( + "run_on" not in kwargs + and ( + "win64" in display_name.lower() + or ("win32" in display_name.lower() and version and version >= "8.0") + ) + and host in (None, "win64", HOSTS.get("win64")) ): kwargs["run_on"] = HOSTS["win-latest"].run_on return create_variant_generic( From f879a7016727b0672bf328ef405090a2586a3f39 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 20 Aug 2026 10:45:20 -0500 Subject: [PATCH 20/28] PYTHON-5808 Install Python via uv on images without a toolchain --- .evergreen/scripts/install-dependencies.sh | 27 ++++++++++++++++++++++ .evergreen/scripts/setup-dev-env.sh | 6 +++++ .evergreen/scripts/setup-uv-python.sh | 11 +++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 8df2af79ca..f259bf63bd 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -3,6 +3,7 @@ set -eu HERE=$(dirname ${BASH_SOURCE:-$0}) +HERE="$( cd -- "$HERE" > /dev/null 2>&1 && pwd )" pushd "$(dirname "$(dirname $HERE)")" > /dev/null # Source the env files to pick up common variables. @@ -33,4 +34,30 @@ if ! command -v just &>/dev/null; then uv tool install rust-just fi +# Some images (e.g. the DEVPROD-19149 Windows image) ship without a Python toolchain. +# Install one with uv and expose it so that both pymongo and drivers-evergreen-tools +# can find it: DRIVERS_TOOLS_PYTHON is honored by DET's ensure_python3(). +if ! command -v python3 &>/dev/null && ! command -v python &>/dev/null; then + echo "No system Python found, installing with uv..." + # UV_PYTHON may be a toolchain path; fall back to a bare version for the download. + _py_ver="${UV_PYTHON:-}" + case "$_py_ver" in + ""|*/*|*\\*) _py_ver="3.10" ;; + esac + uv python install "$_py_ver" + _py_bin="$(uv python find --no-project --managed-python "$_py_ver")" + if [ "Windows_NT" = "${OS:-}" ]; then + _py_bin=$(cygpath -u "$_py_bin") + fi + _py_dir=$(dirname "$_py_bin") + export PATH="$_py_dir:$PATH" + export DRIVERS_TOOLS_PYTHON="$_py_bin" + # Persist for the steps that source env.sh rather than inheriting this shell. + cat <> "$HERE/env.sh" +export PATH="$_py_dir:\$PATH" +export DRIVERS_TOOLS_PYTHON="$_py_bin" +EOT + echo "No system Python found, installing with uv... done ($_py_bin)." +fi + popd > /dev/null diff --git a/.evergreen/scripts/setup-dev-env.sh b/.evergreen/scripts/setup-dev-env.sh index 83b1b622b4..e58d6210fe 100755 --- a/.evergreen/scripts/setup-dev-env.sh +++ b/.evergreen/scripts/setup-dev-env.sh @@ -22,6 +22,12 @@ fi # Ensure dependencies are installed. bash $HERE/install-dependencies.sh +# Re-source env.sh: install-dependencies.sh may have appended to it, e.g. when it +# had to install Python on an image that lacks a toolchain. +if [ -f $HERE/env.sh ]; then + . $HERE/env.sh +fi + # Add the default install path to the path if needed. if [ -z "${PYMONGO_BIN_DIR:-}" ]; then export PATH="$PATH:$HOME/.local/bin" diff --git a/.evergreen/scripts/setup-uv-python.sh b/.evergreen/scripts/setup-uv-python.sh index a32e4a8f92..e83abce4b9 100755 --- a/.evergreen/scripts/setup-uv-python.sh +++ b/.evergreen/scripts/setup-uv-python.sh @@ -41,9 +41,16 @@ if [ -z "${UV_PYTHON:-}" ]; then _exe="python.exe" fi if [ -n "${IS_WIN32:-}" ]; then - _python="C:/python/32/Python${_python}/${_exe}" + _win_python="C:/python/32/Python${_python}/${_exe}" else - _python="C:/python/Python${_python}/${_exe}" + _win_python="C:/python/Python${_python}/${_exe}" + fi + # Fall back to the version string if the toolchain path doesn't exist. + # uv will then download and manage Python itself. + if [ -f "$_win_python" ]; then + _python="$_win_python" + else + _python="$TOOLCHAIN_VERSION" fi elif [ -d "/opt/python/$_python/bin" ]; then _python="/opt/python/$_python/bin/python3" From 9a5583b3ba986244f8392ae39920c1a8f24f4e1b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 20 Aug 2026 11:06:14 -0500 Subject: [PATCH 21/28] PYTHON-5808 Probe for a venv-capable Python before using the one on PATH --- .evergreen/scripts/install-dependencies.sh | 33 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index f259bf63bd..84610f8a38 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -35,10 +35,33 @@ if ! command -v just &>/dev/null; then fi # Some images (e.g. the DEVPROD-19149 Windows image) ship without a Python toolchain. -# Install one with uv and expose it so that both pymongo and drivers-evergreen-tools -# can find it: DRIVERS_TOOLS_PYTHON is honored by DET's ensure_python3(). -if ! command -v python3 &>/dev/null && ! command -v python &>/dev/null; then - echo "No system Python found, installing with uv..." +# A python may still be on PATH there (the mingw python3 that comes with chocolatey) +# that cannot bootstrap pip, so probe for one that can actually create a virtual +# environment - that is what drivers-evergreen-tools needs it for. +_have_python="" +for _cand in python3 python; do + if ! command -v "$_cand" &>/dev/null; then + continue + fi + _probe=$(mktemp -d) + _probe_arg="$_probe" + if [ "Windows_NT" = "${OS:-}" ]; then + _probe_arg=$(cygpath -aw "$_probe") + fi + if "$_cand" -m venv "$_probe_arg" &>/dev/null; then + _have_python="$_cand" + fi + rm -rf "$_probe" + if [ -n "$_have_python" ]; then + break + fi +done + +# Install a Python with uv and expose it so that both pymongo and +# drivers-evergreen-tools can find it: DRIVERS_TOOLS_PYTHON is honored by +# DET's ensure_python3(), which is what builds its virtual environments. +if [ -z "$_have_python" ]; then + echo "No usable Python found, installing with uv..." # UV_PYTHON may be a toolchain path; fall back to a bare version for the download. _py_ver="${UV_PYTHON:-}" case "$_py_ver" in @@ -57,7 +80,7 @@ if ! command -v python3 &>/dev/null && ! command -v python &>/dev/null; then export PATH="$_py_dir:\$PATH" export DRIVERS_TOOLS_PYTHON="$_py_bin" EOT - echo "No system Python found, installing with uv... done ($_py_bin)." + echo "No usable Python found, installing with uv... done ($_py_bin)." fi popd > /dev/null From f9d6d23322d958493b9f5b14ec7fcd43abb4fc25 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 20 Aug 2026 12:34:06 -0500 Subject: [PATCH 22/28] PYTHON-5808 Shadow the broken mingw python3 with the uv-installed one --- .evergreen/scripts/install-dependencies.sh | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 84610f8a38..89942470d1 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -35,33 +35,28 @@ if ! command -v just &>/dev/null; then fi # Some images (e.g. the DEVPROD-19149 Windows image) ship without a Python toolchain. -# A python may still be on PATH there (the mingw python3 that comes with chocolatey) -# that cannot bootstrap pip, so probe for one that can actually create a virtual -# environment - that is what drivers-evergreen-tools needs it for. -_have_python="" -for _cand in python3 python; do - if ! command -v "$_cand" &>/dev/null; then - continue - fi +# A python3 may still be on PATH there (the mingw one that comes with chocolatey) that +# runs but cannot bootstrap pip, so probe for one that can actually create a virtual +# environment. We probe python3 specifically because that is the name +# drivers-evergreen-tools' find_python3 falls back to, and it only checks that the +# interpreter runs - not that it can build the venv it then goes on to need. +_have_python3="" +if command -v python3 &>/dev/null; then _probe=$(mktemp -d) _probe_arg="$_probe" if [ "Windows_NT" = "${OS:-}" ]; then _probe_arg=$(cygpath -aw "$_probe") fi - if "$_cand" -m venv "$_probe_arg" &>/dev/null; then - _have_python="$_cand" + if python3 -m venv "$_probe_arg" &>/dev/null; then + _have_python3="1" fi rm -rf "$_probe" - if [ -n "$_have_python" ]; then - break - fi -done +fi # Install a Python with uv and expose it so that both pymongo and -# drivers-evergreen-tools can find it: DRIVERS_TOOLS_PYTHON is honored by -# DET's ensure_python3(), which is what builds its virtual environments. -if [ -z "$_have_python" ]; then - echo "No usable Python found, installing with uv..." +# drivers-evergreen-tools can find it. +if [ -z "$_have_python3" ]; then + echo "No venv-capable python3 found, installing with uv..." # UV_PYTHON may be a toolchain path; fall back to a bare version for the download. _py_ver="${UV_PYTHON:-}" case "$_py_ver" in @@ -73,6 +68,11 @@ if [ -z "$_have_python" ]; then _py_bin=$(cygpath -u "$_py_bin") fi _py_dir=$(dirname "$_py_bin") + # uv's Windows builds ship python.exe only. Without a python3.exe alongside it the + # mingw python3 further down PATH still wins every bare `python3` lookup. + if [ "Windows_NT" = "${OS:-}" ] && [ ! -e "$_py_dir/python3.exe" ]; then + cp "$_py_bin" "$_py_dir/python3.exe" + fi export PATH="$_py_dir:$PATH" export DRIVERS_TOOLS_PYTHON="$_py_bin" # Persist for the steps that source env.sh rather than inheriting this shell. @@ -80,7 +80,8 @@ if [ -z "$_have_python" ]; then export PATH="$_py_dir:\$PATH" export DRIVERS_TOOLS_PYTHON="$_py_bin" EOT - echo "No usable Python found, installing with uv... done ($_py_bin)." + echo "Installed Python at $_py_bin" + echo "python3 now resolves to: $(command -v python3 || echo NONE)" fi popd > /dev/null From 119795117f7ef5d33516d32889d7ba5f2d4a1d35 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 25 Aug 2026 11:06:59 -0500 Subject: [PATCH 23/28] PYTHON-5808 Remove the DEVPROD-19149 Windows host override --- .evergreen/generated_configs/variants.yml | 2 +- .evergreen/scripts/generate_config.py | 2 -- .evergreen/scripts/generate_config_utils.py | 14 +++----------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index a0dbc0e450..62d139dfd3 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -284,7 +284,7 @@ buildvariants: - name: .test-standard-auth !.pypy .auth !.free-threaded display_name: Auth Enterprise Win64 run_on: - - windows-2022-latest-small-DEVPROD-19149 + - windows-2022-latest-small expansions: TEST_NAME: enterprise_auth AUTH: auth diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index a71ef25e0f..3318625a25 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -231,8 +231,6 @@ def create_enterprise_auth_variants(): if host == "win64": # https://jira.mongodb.org/browse/PYTHON-5704 tasks = [".test-standard-auth !.pypy .auth !.free-threaded"] - if host == "win64": - host = "DEVPROD-19149" # noqa: PLW2901 variant = create_variant(tasks, display_name, host=host, expansions=expansions) variants.append(variant) return variants diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index 1b76184a6b..296d4e2416 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -67,9 +67,6 @@ class Host: 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()) -HOSTS["DEVPROD-19149"] = Host( - "DEVPROD-19149", "windows-2022-latest-small-DEVPROD-19149", "DEVPROD-19149", dict() -) DEFAULT_HOST = HOSTS["rhel8"] # Other hosts @@ -143,14 +140,9 @@ def create_variant( expansions = (expansions and expansions.copy()) or dict() if version: expansions["VERSION"] = version - # 8.0+ Windows builds must run on win-latest, unless an explicit host overrides this - if ( - "run_on" not in kwargs - and ( - "win64" in display_name.lower() - or ("win32" in display_name.lower() and version and version >= "8.0") - ) - and host in (None, "win64", HOSTS.get("win64")) + # 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( From 237145011bd9941bd1a61d89f8b1f02758870942 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 25 Aug 2026 11:47:38 -0500 Subject: [PATCH 24/28] PYTHON-5808 Use the unsuffixed enterprise auth secrets --- .evergreen/scripts/setup_tests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 432a012b5f..b5c40b9c5a 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -259,21 +259,21 @@ def handle_test_env() -> None: krb_conf.touch() write_env("KRB5_CONFIG", krb_conf) LOGGER.info("Writing keytab") - keytab = base64.b64decode(config["KEYTAB_BASE64_BUILD"]) + keytab = base64.b64decode(config["KEYTAB_BASE64"]) keytab_file = ROOT / ".evergreen/drivers.keytab" with keytab_file.open("wb") as fid: fid.write(keytab) - principal = config["PRINCIPAL_BUILD"] + principal = config["PRINCIPAL"] LOGGER.info("Running kinit") os.environ["KRB5_CONFIG"] = str(krb_conf) cmd = f"kinit -k -t {keytab_file} -p {principal}" run_command(cmd) LOGGER.info("Setting GSSAPI variables") - write_env("GSSAPI_HOST", config["SASL_HOST_BUILD"]) + write_env("GSSAPI_HOST", config["SASL_HOST"]) write_env("GSSAPI_PORT", config["SASL_PORT"]) - write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL_BUILD"]) - write_env("SASL_HOST", config["SASL_HOST_BUILD"]) + write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"]) + write_env("SASL_HOST", config["SASL_HOST"]) if test_name == "doctest": UV_ARGS.append("--extra docs") From c3fe231766baf66e51a015a1e436d73c453df236 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 25 Aug 2026 15:36:16 -0400 Subject: [PATCH 25/28] Second pass --- .evergreen/generated_configs/variants.yml | 6 ++-- .evergreen/scripts/generate_config.py | 9 ++--- pymongo/asynchronous/auth.py | 25 +++++++------- pymongo/asynchronous/auth_aws.py | 3 -- pymongo/asynchronous/bulk.py | 9 ----- pymongo/asynchronous/change_stream.py | 32 +----------------- pymongo/asynchronous/collection.py | 23 +++---------- pymongo/asynchronous/cursor.py | 10 +++--- pymongo/asynchronous/mongo_client.py | 33 ++++--------------- pymongo/asynchronous/pool.py | 2 +- pymongo/asynchronous/topology.py | 3 +- pymongo/encryption_options.py | 8 ++--- pymongo/message.py | 4 +-- pymongo/operations.py | 11 +++---- pymongo/synchronous/auth.py | 21 +++++------- pymongo/synchronous/auth_aws.py | 3 -- pymongo/synchronous/bulk.py | 9 ----- pymongo/synchronous/change_stream.py | 32 +----------------- pymongo/synchronous/collection.py | 23 +++---------- pymongo/synchronous/cursor.py | 10 +++--- pymongo/synchronous/mongo_client.py | 33 ++++--------------- pymongo/synchronous/pool.py | 2 +- pymongo/synchronous/topology.py | 3 +- test/__init__.py | 19 ++--------- test/asynchronous/__init__.py | 19 ++--------- test/asynchronous/test_change_stream.py | 1 - test/asynchronous/test_collection.py | 18 ++-------- test/asynchronous/test_csot.py | 3 -- test/asynchronous/test_cursor.py | 17 ++++------ test/asynchronous/test_database.py | 14 +++----- test/asynchronous/test_examples.py | 1 - test/asynchronous/test_monitoring.py | 13 -------- test/asynchronous/test_read_concern.py | 6 +--- test/asynchronous/test_retryable_reads.py | 5 --- test/asynchronous/test_retryable_writes.py | 2 -- .../asynchronous/test_sdam_monitoring_spec.py | 9 ----- test/asynchronous/test_transactions.py | 10 ------ test/test_change_stream.py | 1 - test/test_collection.py | 18 ++-------- test/test_csot.py | 3 -- test/test_cursor.py | 17 ++++------ test/test_database.py | 14 +++----- test/test_examples.py | 1 - test/test_message.py | 23 ++----------- test/test_monitoring.py | 13 -------- test/test_read_concern.py | 6 +--- test/test_retryable_reads.py | 5 --- test/test_retryable_writes.py | 2 -- test/test_sdam_monitoring_spec.py | 9 ----- test/test_transactions.py | 10 ------ 50 files changed, 104 insertions(+), 469 deletions(-) diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 62d139dfd3..32955ba2d7 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -134,7 +134,7 @@ buildvariants: COMPRESSOR: zlib - name: compression-zstd-rhel8 tasks: - - name: .test-standard !.server-4.2 + - name: .test-standard display_name: Compression zstd RHEL8 run_on: - rhel87-small @@ -142,8 +142,8 @@ buildvariants: 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 + - name: .test-standard !.server-4.4 !.server-5.0 .python-3.14 + - name: .test-standard !.server-4.4 !.server-5.0 .python-3.14t display_name: Compression zstd Ubuntu-22 run_on: - ubuntu2204-small diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 3318625a25..0b4c45740f 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -187,10 +187,7 @@ def create_compression_variants(): variants = [] for compressor in "snappy", "zlib", "zstd": expansions = dict(COMPRESSOR=compressor) - if compressor == "zstd": - tasks = [".test-standard !.server-4.2"] - else: - tasks = [".test-standard"] + tasks = [".test-standard"] display_name = get_variant_name(f"Compression {compressor}", host) variants.append( create_variant( @@ -204,8 +201,8 @@ def create_compression_variants(): 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", + ".test-standard !.server-4.4 !.server-5.0 .python-3.14", + ".test-standard !.server-4.4 !.server-5.0 .python-3.14t", ] display_name = get_variant_name(f"Compression {compressor}", host) variants.append( diff --git a/pymongo/asynchronous/auth.py b/pymongo/asynchronous/auth.py index cd3cee6295..d1b0a8f68c 100644 --- a/pymongo/asynchronous/auth.py +++ b/pymongo/asynchronous/auth.py @@ -343,20 +343,17 @@ async def _authenticate_x509(credentials: MongoCredential, conn: AsyncConnection async def _authenticate_default(credentials: MongoCredential, conn: AsyncConnection) -> None: - if conn.max_wire_version >= 7: - if conn.negotiated_mechs: - mechs = conn.negotiated_mechs - else: - source = credentials.source - cmd = conn.hello_cmd() - cmd["saslSupportedMechs"] = source + "." + credentials.username - mechs = (await conn.command(source, cmd, publish_events=False)).get( - "saslSupportedMechs", [] - ) - if "SCRAM-SHA-256" in mechs: - return await _authenticate_scram(credentials, conn, "SCRAM-SHA-256") - else: - return await _authenticate_scram(credentials, conn, "SCRAM-SHA-1") + if conn.negotiated_mechs: + mechs = conn.negotiated_mechs + else: + source = credentials.source + cmd = conn.hello_cmd() + cmd["saslSupportedMechs"] = source + "." + credentials.username + mechs = (await conn.command(source, cmd, publish_events=False)).get( + "saslSupportedMechs", [] + ) + if "SCRAM-SHA-256" in mechs: + return await _authenticate_scram(credentials, conn, "SCRAM-SHA-256") else: return await _authenticate_scram(credentials, conn, "SCRAM-SHA-1") diff --git a/pymongo/asynchronous/auth_aws.py b/pymongo/asynchronous/auth_aws.py index 5fb8ec7e07..7982936948 100644 --- a/pymongo/asynchronous/auth_aws.py +++ b/pymongo/asynchronous/auth_aws.py @@ -48,9 +48,6 @@ async def _authenticate_aws(credentials: MongoCredential, conn: AsyncConnection) set_use_cached_credentials(True) - if conn.max_wire_version < 9: - raise ConfigurationError("MONGODB-AWS authentication requires MongoDB version 4.4 or later") - class AwsSaslContext(pymongo_auth_aws.AwsSaslContext): # type: ignore # Dependency injection: def binary_type(self) -> type[Binary]: diff --git a/pymongo/asynchronous/bulk.py b/pymongo/asynchronous/bulk.py index 1ebe7de456..622b3cf2b7 100644 --- a/pymongo/asynchronous/bulk.py +++ b/pymongo/asynchronous/bulk.py @@ -102,8 +102,6 @@ def __init__( self.bypass_doc_val = bypass_document_validation self.uses_collation = False self.uses_array_filters = False - self.uses_hint_update = False - self.uses_hint_delete = False self.uses_sort = False self.is_retryable = True self.retrying = False @@ -154,7 +152,6 @@ def add_update( self.uses_array_filters = True cmd["arrayFilters"] = array_filters if hint is not None: - self.uses_hint_update = True cmd["hint"] = hint if sort is not None: self.uses_sort = True @@ -182,7 +179,6 @@ def add_replace( self.uses_collation = True cmd["collation"] = collation if hint is not None: - self.uses_hint_update = True cmd["hint"] = hint if sort is not None: self.uses_sort = True @@ -202,7 +198,6 @@ def add_delete( self.uses_collation = True cmd["collation"] = collation if hint is not None: - self.uses_hint_delete = True cmd["hint"] = hint if limit == _DELETE_ALL: # A bulk_write containing a delete_many is not retryable. @@ -572,10 +567,6 @@ async def execute_no_results( raise ConfigurationError("arrayFilters is unsupported for unacknowledged writes.") # Guard against unsupported unacknowledged writes. unack = write_concern and not write_concern.acknowledged - if unack and self.uses_hint_delete and conn.max_wire_version < 9: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use hint on unacknowledged delete commands." - ) if unack and self.uses_sort and conn.max_wire_version < 25: raise ConfigurationError( "Must be connected to MongoDB 8.0+ to use sort on unacknowledged update commands." diff --git a/pymongo/asynchronous/change_stream.py b/pymongo/asynchronous/change_stream.py index e9d588ac95..7d8876852b 100644 --- a/pymongo/asynchronous/change_stream.py +++ b/pymongo/asynchronous/change_stream.py @@ -43,31 +43,6 @@ _IS_SYNC = False -# The change streams spec considers the following server errors from the -# getMore command non-resumable. All other getMore errors are resumable. -_RESUMABLE_GETMORE_ERRORS = frozenset( - [ - 6, # HostUnreachable - 7, # HostNotFound - 89, # NetworkTimeout - 91, # ShutdownInProgress - 189, # PrimarySteppedDown - 262, # ExceededTimeLimit - 9001, # SocketException - 10107, # NotWritablePrimary - 11600, # InterruptedAtShutdown - 11602, # InterruptedDueToReplStateChange - 13435, # NotPrimaryNoSecondaryOk - 13436, # NotPrimaryOrSecondary - 63, # StaleShardVersion - 150, # StaleEpoch - 13388, # StaleConfig - 234, # RetryChangeStream - 133, # FailedToSatisfyReadPreference - ] -) - - if TYPE_CHECKING: from pymongo.asynchronous.client_session import AsyncClientSession from pymongo.asynchronous.collection import AsyncCollection @@ -81,11 +56,7 @@ def _resumable(exc: PyMongoError) -> bool: if isinstance(exc, (ConnectionFailure, CursorNotFound)): return True if isinstance(exc, OperationFailure): - if exc._max_wire_version is None: - return False - return ( - exc._max_wire_version >= 9 and exc.has_error_label("ResumableChangeStreamError") - ) or (exc._max_wire_version < 9 and exc.code in _RESUMABLE_GETMORE_ERRORS) + return exc.has_error_label("ResumableChangeStreamError") return False @@ -227,7 +198,6 @@ def _process_result(self, result: Mapping[str, Any], conn: AsyncConnection) -> N self._start_at_operation_time is None and self._uses_resume_after is False and self._uses_start_after is False - and conn.max_wire_version >= 7 ): self._start_at_operation_time = result.get("operationTime") # PYTHON-2181: informative error on missing operationTime. diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index be32d7a2b5..1595828260 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -1166,8 +1166,7 @@ async def replace_one( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.asynchronous.collection.AsyncCollection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param session: a :class:`~pymongo.asynchronous.client_session.AsyncClientSession`. :param let: Map of parameter names and values. Values must be @@ -1282,8 +1281,7 @@ async def update_one( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.asynchronous.collection.AsyncCollection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param session: a :class:`~pymongo.asynchronous.client_session.AsyncClientSession`. :param let: Map of parameter names and values. Values must be @@ -1388,8 +1386,7 @@ async def update_many( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.asynchronous.collection.AsyncCollection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param session: a :class:`~pymongo.asynchronous.client_session.AsyncClientSession`. :param let: Map of parameter names and values. Values must be @@ -1511,10 +1508,6 @@ async def _delete( else: delete_doc["collation"] = collation if hint is not None: - if not acknowledged and conn.max_wire_version < 9: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use hint on unacknowledged delete commands." - ) if not isinstance(hint, str): hint = helpers_shared._index_document(hint) delete_doc["hint"] = hint @@ -2244,8 +2237,6 @@ async def _create_indexes( 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]]: for index in indexes: if not isinstance(index, IndexModel): @@ -2258,12 +2249,6 @@ def gen_indexes() -> Iterator[Mapping[str, Any]]: cmd = {"createIndexes": self.name, "indexes": list(gen_indexes())} cmd.update(kwargs) - if "commitQuorum" in kwargs and not supports_quorum: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use the " - "commitQuorum option for createIndexes" - ) - await self._command( conn, cmd, @@ -2339,7 +2324,7 @@ async def create_index( :class:`~pymongo.collation.Collation`. - `wildcardProjection`: Allows users to include or exclude specific field paths from a `wildcard index`_ using the {"$**" : 1} key - pattern. Requires MongoDB >= 4.2. + pattern. - `hidden`: if ``True``, this index will be hidden from the query planner and will not be evaluated as part of query plan selection. Requires MongoDB >= 4.4. diff --git a/pymongo/asynchronous/cursor.py b/pymongo/asynchronous/cursor.py index 1b2428a6d8..362b4a9739 100644 --- a/pymongo/asynchronous/cursor.py +++ b/pymongo/asynchronous/cursor.py @@ -635,9 +635,8 @@ def max_scan(self, max_scan: Optional[int]) -> AsyncCursor[_DocumentType]: def max(self, spec: _Sort) -> AsyncCursor[_DocumentType]: """Adds ``max`` operator that specifies upper bound for specific index. - When using ``max``, :meth:`~hint` should also be configured to ensure - the query uses the expected index and starting in MongoDB 4.2 - :meth:`~hint` will be required. + When using ``max``, :meth:`~hint` is required to ensure the query + uses the expected index. :param spec: a list of field, limit pairs specifying the exclusive upper bound for all keys of a specific index in order. @@ -657,9 +656,8 @@ def max(self, spec: _Sort) -> AsyncCursor[_DocumentType]: def min(self, spec: _Sort) -> AsyncCursor[_DocumentType]: """Adds ``min`` operator that specifies lower bound for specific index. - When using ``min``, :meth:`~hint` should also be configured to ensure - the query uses the expected index and starting in MongoDB 4.2 - :meth:`~hint` will be required. + When using ``min``, :meth:`~hint` is required to ensure the query + uses the expected index. :param spec: a list of field, limit pairs specifying the inclusive lower bound for all keys of a specific index in order. diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index db5c790d64..02f2e88a80 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -81,7 +81,6 @@ PyMongoError, ServerSelectionTimeoutError, WaitQueueTimeoutError, - WriteConcernError, ) from pymongo.lock import ( _HAS_REGISTER_AT_FORK, @@ -421,8 +420,7 @@ def __init__( 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. + must also be enabled on the server. See `compress network traffic `_ for details. - `zlibCompressionLevel`: (int) The zlib compression level to use when zlib is used as the wire protocol compressor. Supported values @@ -2585,7 +2583,7 @@ def _retryable_error_doc(exc: PyMongoError) -> Optional[Mapping[str, Any]]: return None -def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int, is_mongos: bool) -> None: +def _add_retryable_write_error(exc: PyMongoError) -> None: doc = _retryable_error_doc(exc) if doc: code = doc.get("code", 0) @@ -2597,16 +2595,9 @@ def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int, is_mong "to your connection string." ) raise OperationFailure(errmsg, code, exc.details) # type: ignore[attr-defined] - if max_wire_version >= 9: - # In MongoDB 4.4+, the server reports the error labels. - for label in doc.get("errorLabels", []): - exc._add_error_label(label) - else: - # Do not consult writeConcernError for pre-4.4 mongos. - if isinstance(exc, WriteConcernError) and is_mongos: - pass - elif code in helpers_shared._RETRYABLE_ERROR_CODES: - exc._add_error_label("RetryableWriteError") + # The server reports the error labels. + for label in doc.get("errorLabels", []): + exc._add_error_label(label) # AsyncConnection errors are always retryable except NotPrimaryError and WaitQueueTimeoutError which is # handled above. @@ -2735,14 +2726,6 @@ async def __aenter__(self) -> AsyncConnection: ): session._pin(server, conn) self.contribute_socket(conn) - if ( - self.client._encrypter - and not self.client._encrypter._bypass_auto_encryption - and conn.max_wire_version < 8 - ): - raise ConfigurationError( - "Auto-encryption requires a minimum MongoDB version of 4.2" - ) except BaseException as exc: try: await self.handle(type(exc), exc) @@ -3111,17 +3094,13 @@ async def _write(self) -> T: :return: Output for func()'s call """ try: - max_wire_version = 0 - is_mongos = False self._server = await self._get_server() async with self._client._checkout(self._server, self._session) as conn: - max_wire_version = conn.max_wire_version sessions_supported = ( self._session and self._server.description.retryable_writes_supported and conn.supports_sessions ) - is_mongos = conn.is_mongos 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. @@ -3138,7 +3117,7 @@ async def _write(self) -> T: if not self._retryable: raise # Add the RetryableWriteError label, if applicable. - _add_retryable_write_error(exc, max_wire_version, is_mongos) + _add_retryable_write_error(exc) raise async def _read(self) -> T: diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index a126962e4a..eb9cb215eb 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -226,7 +226,7 @@ async def unpin(self) -> None: def hello_cmd(self) -> dict[str, Any]: # As of PYTHON-5713, always use OP_MSG for the handshake since all - # supported servers (MongoDB 4.2+, wire version >= 8) support it. + # supported servers (MongoDB 4.4+, wire version >= 9) support it. if self.opts.server_api or self.hello_ok or self.opts.load_balanced: return {HelloCompat.CMD: 1} return {HelloCompat.LEGACY_CMD: 1, "helloOk": True} diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index cd5d430f8a..c9ee9dfc0b 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -764,8 +764,7 @@ async def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None elif isinstance(error, (NotPrimaryError, OperationFailure)): # As per the SDAM spec if: # - the server sees a "not primary" error, and - # - the server is not shutting down, and - # - the server version is >= 4.2, then + # - the server is not shutting down, then # we keep the existing connection pool, but mark the server type # as Unknown and request an immediate check of the server. # Otherwise, we clear the connection pool, mark the server as diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index f2fcd47c65..e410ef6846 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -77,13 +77,13 @@ def __init__( ) -> None: """Options to configure automatic client-side field level encryption. - Automatic client-side field level encryption requires MongoDB >=4.2 - enterprise or a MongoDB >=4.2 Atlas cluster. Automatic encryption is not + Automatic client-side field level encryption requires MongoDB + enterprise or a MongoDB Atlas cluster. Automatic encryption is not supported for operations on a database or view and will result in error. - Although automatic encryption requires MongoDB >=4.2 enterprise or a - MongoDB >=4.2 Atlas cluster, automatic *decryption* is supported for all + Although automatic encryption requires MongoDB enterprise or a + MongoDB Atlas cluster, automatic *decryption* is supported for all users. To configure automatic *decryption* without automatic *encryption* set ``bypass_auto_encryption=True``. Explicit encryption and explicit decryption is also supported for all users diff --git a/pymongo/message.py b/pymongo/message.py index 27a99f468c..df7d940532 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -229,7 +229,6 @@ def _gen_get_more_command( batch_size: Optional[int], max_await_time_ms: Optional[int], comment: Optional[Any], - conn: _AgnosticConnection, ) -> dict[str, Any]: """Generate a getMore command document.""" cmd: dict[str, Any] = {"getMore": cursor_id, "collection": coll} @@ -237,7 +236,7 @@ def _gen_get_more_command( cmd["batchSize"] = batch_size if max_await_time_ms is not None: cmd["maxTimeMS"] = max_await_time_ms - if comment is not None and conn.max_wire_version >= 9: + if comment is not None: cmd["comment"] = comment return cmd @@ -1403,7 +1402,6 @@ def as_command( self.ntoreturn, self.max_await_time_ms, self.comment, - conn, ) if self.session: self.session._apply_to(cmd, False, self.read_preference, conn) # type: ignore[arg-type] diff --git a/pymongo/operations.py b/pymongo/operations.py index b664a289ba..99b6a00c52 100644 --- a/pymongo/operations.py +++ b/pymongo/operations.py @@ -366,8 +366,7 @@ def __init__( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.asynchronous.collection.AsyncCollection.create_index` or :meth:`~pymongo.collection.Collection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param sort: Specify which document the operation updates if the query matches multiple documents. The first document matched by the sort order will be updated. :param namespace: (optional) The namespace in which to replace a document. @@ -566,8 +565,7 @@ def __init__( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.asynchronous.collection.AsyncCollection.create_index` or :meth:`~pymongo.collection.Collection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param namespace: The namespace in which to update a document. :param sort: Specify which document the operation updates if the query matches multiple documents. The first document matched by the sort order will be updated. @@ -651,8 +649,7 @@ def __init__( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.asynchronous.collection.AsyncCollection.create_index` or :meth:`~pymongo.collection.Collection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param namespace: (optional) The namespace in which to update documents. .. versionchanged:: 4.9 @@ -742,7 +739,7 @@ def __init__(self, keys: _IndexKeyHint, **kwargs: Any) -> None: that specifies the collation to use. - `wildcardProjection`: Allows users to include or exclude specific field paths from a `wildcard index`_ using the { "$**" : 1} key - pattern. Requires MongoDB >= 4.2. + pattern. - `hidden`: if ``True``, this index will be hidden from the query planner and will not be evaluated as part of query plan selection. Requires MongoDB >= 4.4. diff --git a/pymongo/synchronous/auth.py b/pymongo/synchronous/auth.py index 5bb5cb454f..18c4e62b57 100644 --- a/pymongo/synchronous/auth.py +++ b/pymongo/synchronous/auth.py @@ -341,18 +341,15 @@ def _authenticate_x509(credentials: MongoCredential, conn: Connection) -> None: def _authenticate_default(credentials: MongoCredential, conn: Connection) -> None: - if conn.max_wire_version >= 7: - if conn.negotiated_mechs: - mechs = conn.negotiated_mechs - else: - source = credentials.source - cmd = conn.hello_cmd() - cmd["saslSupportedMechs"] = source + "." + credentials.username - mechs = (conn.command(source, cmd, publish_events=False)).get("saslSupportedMechs", []) - if "SCRAM-SHA-256" in mechs: - return _authenticate_scram(credentials, conn, "SCRAM-SHA-256") - else: - return _authenticate_scram(credentials, conn, "SCRAM-SHA-1") + if conn.negotiated_mechs: + mechs = conn.negotiated_mechs + else: + source = credentials.source + cmd = conn.hello_cmd() + cmd["saslSupportedMechs"] = source + "." + credentials.username + mechs = (conn.command(source, cmd, publish_events=False)).get("saslSupportedMechs", []) + if "SCRAM-SHA-256" in mechs: + return _authenticate_scram(credentials, conn, "SCRAM-SHA-256") else: return _authenticate_scram(credentials, conn, "SCRAM-SHA-1") diff --git a/pymongo/synchronous/auth_aws.py b/pymongo/synchronous/auth_aws.py index b20f3c2110..777e0620f5 100644 --- a/pymongo/synchronous/auth_aws.py +++ b/pymongo/synchronous/auth_aws.py @@ -48,9 +48,6 @@ def _authenticate_aws(credentials: MongoCredential, conn: Connection) -> None: set_use_cached_credentials(True) - if conn.max_wire_version < 9: - raise ConfigurationError("MONGODB-AWS authentication requires MongoDB version 4.4 or later") - class AwsSaslContext(pymongo_auth_aws.AwsSaslContext): # type: ignore # Dependency injection: def binary_type(self) -> type[Binary]: diff --git a/pymongo/synchronous/bulk.py b/pymongo/synchronous/bulk.py index 81718f2f0e..79d64c07c2 100644 --- a/pymongo/synchronous/bulk.py +++ b/pymongo/synchronous/bulk.py @@ -102,8 +102,6 @@ def __init__( self.bypass_doc_val = bypass_document_validation self.uses_collation = False self.uses_array_filters = False - self.uses_hint_update = False - self.uses_hint_delete = False self.uses_sort = False self.is_retryable = True self.retrying = False @@ -154,7 +152,6 @@ def add_update( self.uses_array_filters = True cmd["arrayFilters"] = array_filters if hint is not None: - self.uses_hint_update = True cmd["hint"] = hint if sort is not None: self.uses_sort = True @@ -182,7 +179,6 @@ def add_replace( self.uses_collation = True cmd["collation"] = collation if hint is not None: - self.uses_hint_update = True cmd["hint"] = hint if sort is not None: self.uses_sort = True @@ -202,7 +198,6 @@ def add_delete( self.uses_collation = True cmd["collation"] = collation if hint is not None: - self.uses_hint_delete = True cmd["hint"] = hint if limit == _DELETE_ALL: # A bulk_write containing a delete_many is not retryable. @@ -570,10 +565,6 @@ def execute_no_results( raise ConfigurationError("arrayFilters is unsupported for unacknowledged writes.") # Guard against unsupported unacknowledged writes. unack = write_concern and not write_concern.acknowledged - if unack and self.uses_hint_delete and conn.max_wire_version < 9: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use hint on unacknowledged delete commands." - ) if unack and self.uses_sort and conn.max_wire_version < 25: raise ConfigurationError( "Must be connected to MongoDB 8.0+ to use sort on unacknowledged update commands." diff --git a/pymongo/synchronous/change_stream.py b/pymongo/synchronous/change_stream.py index f442cc6c67..c0c15d04ef 100644 --- a/pymongo/synchronous/change_stream.py +++ b/pymongo/synchronous/change_stream.py @@ -43,31 +43,6 @@ _IS_SYNC = True -# The change streams spec considers the following server errors from the -# getMore command non-resumable. All other getMore errors are resumable. -_RESUMABLE_GETMORE_ERRORS = frozenset( - [ - 6, # HostUnreachable - 7, # HostNotFound - 89, # NetworkTimeout - 91, # ShutdownInProgress - 189, # PrimarySteppedDown - 262, # ExceededTimeLimit - 9001, # SocketException - 10107, # NotWritablePrimary - 11600, # InterruptedAtShutdown - 11602, # InterruptedDueToReplStateChange - 13435, # NotPrimaryNoSecondaryOk - 13436, # NotPrimaryOrSecondary - 63, # StaleShardVersion - 150, # StaleEpoch - 13388, # StaleConfig - 234, # RetryChangeStream - 133, # FailedToSatisfyReadPreference - ] -) - - if TYPE_CHECKING: from pymongo.synchronous.client_session import ClientSession from pymongo.synchronous.collection import Collection @@ -81,11 +56,7 @@ def _resumable(exc: PyMongoError) -> bool: if isinstance(exc, (ConnectionFailure, CursorNotFound)): return True if isinstance(exc, OperationFailure): - if exc._max_wire_version is None: - return False - return ( - exc._max_wire_version >= 9 and exc.has_error_label("ResumableChangeStreamError") - ) or (exc._max_wire_version < 9 and exc.code in _RESUMABLE_GETMORE_ERRORS) + return exc.has_error_label("ResumableChangeStreamError") return False @@ -227,7 +198,6 @@ def _process_result(self, result: Mapping[str, Any], conn: Connection) -> None: self._start_at_operation_time is None and self._uses_resume_after is False and self._uses_start_after is False - and conn.max_wire_version >= 7 ): self._start_at_operation_time = result.get("operationTime") # PYTHON-2181: informative error on missing operationTime. diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index b74354c98d..cf8cdb7842 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -1166,8 +1166,7 @@ def replace_one( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.collection.Collection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param session: a :class:`~pymongo.client_session.ClientSession`. :param let: Map of parameter names and values. Values must be @@ -1282,8 +1281,7 @@ def update_one( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.collection.Collection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param session: a :class:`~pymongo.client_session.ClientSession`. :param let: Map of parameter names and values. Values must be @@ -1388,8 +1386,7 @@ def update_many( predicate specified either by its string name, or in the same format as passed to :meth:`~pymongo.collection.Collection.create_index` (e.g. - ``[('field', ASCENDING)]``). This option is only supported on - MongoDB 4.2 and above. + ``[('field', ASCENDING)]``). :param session: a :class:`~pymongo.client_session.ClientSession`. :param let: Map of parameter names and values. Values must be @@ -1511,10 +1508,6 @@ def _delete( else: delete_doc["collation"] = collation if hint is not None: - if not acknowledged and conn.max_wire_version < 9: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use hint on unacknowledged delete commands." - ) if not isinstance(hint, str): hint = helpers_shared._index_document(hint) delete_doc["hint"] = hint @@ -2242,8 +2235,6 @@ def _create_indexes( 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]]: for index in indexes: if not isinstance(index, IndexModel): @@ -2256,12 +2247,6 @@ def gen_indexes() -> Iterator[Mapping[str, Any]]: cmd = {"createIndexes": self.name, "indexes": list(gen_indexes())} cmd.update(kwargs) - if "commitQuorum" in kwargs and not supports_quorum: - raise ConfigurationError( - "Must be connected to MongoDB 4.4+ to use the " - "commitQuorum option for createIndexes" - ) - self._command( conn, cmd, @@ -2335,7 +2320,7 @@ def create_index( :class:`~pymongo.collation.Collation`. - `wildcardProjection`: Allows users to include or exclude specific field paths from a `wildcard index`_ using the {"$**" : 1} key - pattern. Requires MongoDB >= 4.2. + pattern. - `hidden`: if ``True``, this index will be hidden from the query planner and will not be evaluated as part of query plan selection. Requires MongoDB >= 4.4. diff --git a/pymongo/synchronous/cursor.py b/pymongo/synchronous/cursor.py index fddbcc520f..9d6908f35a 100644 --- a/pymongo/synchronous/cursor.py +++ b/pymongo/synchronous/cursor.py @@ -633,9 +633,8 @@ def max_scan(self, max_scan: Optional[int]) -> Cursor[_DocumentType]: def max(self, spec: _Sort) -> Cursor[_DocumentType]: """Adds ``max`` operator that specifies upper bound for specific index. - When using ``max``, :meth:`~hint` should also be configured to ensure - the query uses the expected index and starting in MongoDB 4.2 - :meth:`~hint` will be required. + When using ``max``, :meth:`~hint` is required to ensure the query + uses the expected index. :param spec: a list of field, limit pairs specifying the exclusive upper bound for all keys of a specific index in order. @@ -655,9 +654,8 @@ def max(self, spec: _Sort) -> Cursor[_DocumentType]: def min(self, spec: _Sort) -> Cursor[_DocumentType]: """Adds ``min`` operator that specifies lower bound for specific index. - When using ``min``, :meth:`~hint` should also be configured to ensure - the query uses the expected index and starting in MongoDB 4.2 - :meth:`~hint` will be required. + When using ``min``, :meth:`~hint` is required to ensure the query + uses the expected index. :param spec: a list of field, limit pairs specifying the inclusive lower bound for all keys of a specific index in order. diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 3959455d3f..ffb55ce333 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -71,7 +71,6 @@ PyMongoError, ServerSelectionTimeoutError, WaitQueueTimeoutError, - WriteConcernError, ) from pymongo.lock import ( _HAS_REGISTER_AT_FORK, @@ -422,8 +421,7 @@ def __init__( 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. + must also be enabled on the server. See `compress network traffic `_ for details. - `zlibCompressionLevel`: (int) The zlib compression level to use when zlib is used as the wire protocol compressor. Supported values @@ -2574,7 +2572,7 @@ def _retryable_error_doc(exc: PyMongoError) -> Optional[Mapping[str, Any]]: return None -def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int, is_mongos: bool) -> None: +def _add_retryable_write_error(exc: PyMongoError) -> None: doc = _retryable_error_doc(exc) if doc: code = doc.get("code", 0) @@ -2586,16 +2584,9 @@ def _add_retryable_write_error(exc: PyMongoError, max_wire_version: int, is_mong "to your connection string." ) raise OperationFailure(errmsg, code, exc.details) # type: ignore[attr-defined] - if max_wire_version >= 9: - # In MongoDB 4.4+, the server reports the error labels. - for label in doc.get("errorLabels", []): - exc._add_error_label(label) - else: - # Do not consult writeConcernError for pre-4.4 mongos. - if isinstance(exc, WriteConcernError) and is_mongos: - pass - elif code in helpers_shared._RETRYABLE_ERROR_CODES: - exc._add_error_label("RetryableWriteError") + # The server reports the error labels. + for label in doc.get("errorLabels", []): + exc._add_error_label(label) # Connection errors are always retryable except NotPrimaryError and WaitQueueTimeoutError which is # handled above. @@ -2724,14 +2715,6 @@ def __enter__(self) -> Connection: ): session._pin(server, conn) self.contribute_socket(conn) - if ( - self.client._encrypter - and not self.client._encrypter._bypass_auto_encryption - and conn.max_wire_version < 8 - ): - raise ConfigurationError( - "Auto-encryption requires a minimum MongoDB version of 4.2" - ) except BaseException as exc: try: self.handle(type(exc), exc) @@ -3100,17 +3083,13 @@ def _write(self) -> T: :return: Output for func()'s call """ try: - max_wire_version = 0 - is_mongos = False self._server = self._get_server() with self._client._checkout(self._server, self._session) as conn: - max_wire_version = conn.max_wire_version sessions_supported = ( self._session and self._server.description.retryable_writes_supported and conn.supports_sessions ) - is_mongos = conn.is_mongos 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. @@ -3127,7 +3106,7 @@ def _write(self) -> T: if not self._retryable: raise # Add the RetryableWriteError label, if applicable. - _add_retryable_write_error(exc, max_wire_version, is_mongos) + _add_retryable_write_error(exc) raise def _read(self) -> T: diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index b75415eeeb..64ff4210a5 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -226,7 +226,7 @@ def unpin(self) -> None: def hello_cmd(self) -> dict[str, Any]: # As of PYTHON-5713, always use OP_MSG for the handshake since all - # supported servers (MongoDB 4.2+, wire version >= 8) support it. + # supported servers (MongoDB 4.4+, wire version >= 9) support it. if self.opts.server_api or self.hello_ok or self.opts.load_balanced: return {HelloCompat.CMD: 1} return {HelloCompat.LEGACY_CMD: 1, "helloOk": True} diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index 5f0a4ef88b..e2ceec7466 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -762,8 +762,7 @@ def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None: elif isinstance(error, (NotPrimaryError, OperationFailure)): # As per the SDAM spec if: # - the server sees a "not primary" error, and - # - the server is not shutting down, and - # - the server version is >= 4.2, then + # - the server is not shutting down, then # we keep the existing connection pool, but mark the server type # as Unknown and request an immediate check of the server. # Otherwise, we clear the connection pool, mark the server as diff --git a/test/__init__.py b/test/__init__.py index a7d389c1a0..7aff0a4620 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -777,13 +777,7 @@ def require_exhaust_cursors(self, func): ) def supports_transactions(self): - if self.version.at_least(4, 1, 8): - return self.is_mongos or self.is_rs - - if self.version.at_least(4, 0): - return self.is_rs - - return False + return self.is_mongos or self.is_rs def require_transactions(self, func): """Run a test only if the deployment might support transactions. @@ -822,16 +816,7 @@ def mongos_seeds(self): @property def supports_failCommand_fail_point(self): """Does the server support the failCommand fail point?""" - if self.is_mongos: - return self.version.at_least(4, 1, 5) and self.test_commands_enabled - else: - return self.version.at_least(4, 0) and self.test_commands_enabled - - @property - def requires_hint_with_min_max_queries(self): - """Does the server require a hint with min/max queries.""" - # Changed in SERVER-39567. - return self.version.at_least(4, 1, 10) + return self.test_commands_enabled @property def max_bson_size(self): diff --git a/test/asynchronous/__init__.py b/test/asynchronous/__init__.py index a677644d08..4ecaa589cf 100644 --- a/test/asynchronous/__init__.py +++ b/test/asynchronous/__init__.py @@ -777,13 +777,7 @@ def require_exhaust_cursors(self, func): ) def supports_transactions(self): - if self.version.at_least(4, 1, 8): - return self.is_mongos or self.is_rs - - if self.version.at_least(4, 0): - return self.is_rs - - return False + return self.is_mongos or self.is_rs def require_transactions(self, func): """Run a test only if the deployment might support transactions. @@ -822,16 +816,7 @@ def mongos_seeds(self): @property def supports_failCommand_fail_point(self): """Does the server support the failCommand fail point?""" - if self.is_mongos: - return self.version.at_least(4, 1, 5) and self.test_commands_enabled - else: - return self.version.at_least(4, 0) and self.test_commands_enabled - - @property - def requires_hint_with_min_max_queries(self): - """Does the server require a hint with min/max queries.""" - # Changed in SERVER-39567. - return self.version.at_least(4, 1, 10) + return self.test_commands_enabled @property async def max_bson_size(self): diff --git a/test/asynchronous/test_change_stream.py b/test/asynchronous/test_change_stream.py index 809e8c6e9d..6b1b7b4cb9 100644 --- a/test/asynchronous/test_change_stream.py +++ b/test/asynchronous/test_change_stream.py @@ -983,7 +983,6 @@ async def test_raw(self): self.assertEqual(change["ns"]["coll"], self.watched_collection().name) self.assertEqual(change["fullDocument"], raw_doc) - @async_client_context.require_version_min(4, 0) # Needed for start_at_operation_time. async def test_uuid_representations(self): """Test with uuid document _ids and different uuid_representation.""" optime = (await self.db.command("ping"))["operationTime"] diff --git a/test/asynchronous/test_collection.py b/test/asynchronous/test_collection.py index d787080d02..3c91faf7ef 100644 --- a/test/asynchronous/test_collection.py +++ b/test/asynchronous/test_collection.py @@ -270,17 +270,7 @@ async def test_create_indexes(self): with self.write_concern_collection() as coll: await coll.create_indexes([IndexModel("hello")]) - @async_client_context.require_version_max(4, 3, -1) - async def test_create_indexes_commitQuorum_requires_44(self): - db = self.db - with self.assertRaisesRegex( - ConfigurationError, - r"Must be connected to MongoDB 4\.4\+ to use the commitQuorum option for createIndexes", - ): - await db.coll.create_indexes([IndexModel("a")], commitQuorum="majority") - @async_client_context.require_no_standalone - @async_client_context.require_version_min(4, 4, -1) async def test_create_indexes_commitQuorum(self): await self.db.coll.create_indexes([IndexModel("a")], commitQuorum="majority") @@ -1848,12 +1838,8 @@ async def test_exhaust(self): # and the socket has pending data (more_to_come=True) we have to close # and discard the socket. cur = client[self.db.name].test.find(cursor_type=CursorType.EXHAUST, batch_size=2) - if async_client_context.version.at_least(4, 2): - # On 4.2+ we use OP_MSG which only sets more_to_come=True after the - # first getMore. - for _ in range(3): - await anext(cur) - else: + # OP_MSG only sets more_to_come=True after the first getMore. + for _ in range(3): await anext(cur) self.assertEqual(0, len(pool.conns)) # if sys.platform.startswith("java") or "PyPy" in sys.version: diff --git a/test/asynchronous/test_csot.py b/test/asynchronous/test_csot.py index a3989c9c18..2566c141fb 100644 --- a/test/asynchronous/test_csot.py +++ b/test/asynchronous/test_csot.py @@ -90,9 +90,6 @@ async def test_change_stream_can_resume_after_timeouts(self): await stream.try_next() self.assertTrue(ctx.exception.timeout) self.assertTrue(stream.alive) - # Resume before the insert on 3.6 because 4.0 is required to avoid skipping documents - if async_client_context.version < (4, 0): - await stream.try_next() await coll.insert_one({}) with pymongo.timeout(10): self.assertTrue(await stream.next()) diff --git a/test/asynchronous/test_cursor.py b/test/asynchronous/test_cursor.py index 1fe023720a..f8fbac8032 100644 --- a/test/asynchronous/test_cursor.py +++ b/test/asynchronous/test_cursor.py @@ -855,16 +855,13 @@ async def test_where(self): self.assertEqual(3, len(await db.test.find().where(Code("this.x < 3")).to_list())) code_with_scope = Code("this.x < i", {"i": 3}) - if async_client_context.version.at_least(4, 3, 3): - # MongoDB 4.4 removed support for Code with scope. - with self.assertRaises(OperationFailure): - await db.test.find().where(code_with_scope).to_list() - - code_with_empty_scope = Code("this.x < 3", {}) - with self.assertRaises(OperationFailure): - await db.test.find().where(code_with_empty_scope).to_list() - else: - self.assertEqual(3, len(await db.test.find().where(code_with_scope).to_list())) + # MongoDB 4.4 removed support for Code with scope. + with self.assertRaises(OperationFailure): + await db.test.find().where(code_with_scope).to_list() + + code_with_empty_scope = Code("this.x < 3", {}) + with self.assertRaises(OperationFailure): + await db.test.find().where(code_with_empty_scope).to_list() self.assertEqual(10, len(await db.test.find().to_list())) self.assertEqual([0, 1, 2], [a["x"] async for a in db.test.find().where("this.x < 3")]) diff --git a/test/asynchronous/test_database.py b/test/asynchronous/test_database.py index e8b37f6405..4b9f41c244 100644 --- a/test/asynchronous/test_database.py +++ b/test/asynchronous/test_database.py @@ -382,7 +382,6 @@ async def test_validate_collection(self): self.assertTrue(await db.validate_collection(db.test, scandata=True, full=True)) self.assertTrue(await db.validate_collection(db.test, True, True)) - @async_client_context.require_version_min(4, 3, 3) @async_client_context.require_no_standalone async def test_validate_collection_background(self): db = self.client.pymongo_test.with_options(write_concern=WriteConcern(w="majority")) @@ -710,15 +709,10 @@ async def test_database_aggregation(self): @async_client_context.require_no_mongos async def test_database_aggregation_fake_cursor(self): coll_name = "test_output" - write_stage: dict - if async_client_context.version < (4, 3): - db_name = "admin" - write_stage = {"$out": coll_name} - else: - # SERVER-43287 disallows writing with $out to the admin db, use - # $merge instead. - db_name = "pymongo_test" - write_stage = {"$merge": {"into": {"db": db_name, "coll": coll_name}}} + # SERVER-43287 disallows writing with $out to the admin db, use + # $merge instead. + db_name = "pymongo_test" + write_stage = {"$merge": {"into": {"db": db_name, "coll": coll_name}}} output_coll = self.client[db_name][coll_name] await output_coll.drop() self.addAsyncCleanup(output_coll.drop) diff --git a/test/asynchronous/test_examples.py b/test/asynchronous/test_examples.py index 58ce1621c1..6a6d6b3d9e 100644 --- a/test/asynchronous/test_examples.py +++ b/test/asynchronous/test_examples.py @@ -862,7 +862,6 @@ async def test_aggregate_examples(self): ) # End Aggregation Example 4 - @async_client_context.require_version_min(4, 4) async def test_aggregate_projection_example(self): db = self.db diff --git a/test/asynchronous/test_monitoring.py b/test/asynchronous/test_monitoring.py index 7d6075ad48..c29ab71cc2 100644 --- a/test/asynchronous/test_monitoring.py +++ b/test/asynchronous/test_monitoring.py @@ -279,19 +279,6 @@ async def test_find_options(self): "allowPartialResults": True, } - if async_client_context.version < (4, 1, 0, -1): - query["max_scan"] = 10 - cmd["maxScan"] = 10 - - await self._test_find_options(query, cmd) - - @async_client_context.require_version_max(3, 7, 2) - async def test_find_snapshot(self): - # Test "snapshot" parameter separately, can't combine with "sort". - query = {"filter": {}, "snapshot": True} - - cmd = {"find": "test", "filter": {}, "snapshot": True} - await self._test_find_options(query, cmd) async def test_command_and_get_more(self): diff --git a/test/asynchronous/test_read_concern.py b/test/asynchronous/test_read_concern.py index 487ae78cf6..b694d8c0bf 100644 --- a/test/asynchronous/test_read_concern.py +++ b/test/asynchronous/test_read_concern.py @@ -111,11 +111,7 @@ async def test_aggregate_out(self): await coll.aggregate([{"$match": {"field": "value"}}, {"$out": "output_collection"}]) ).to_list() - # Aggregate with $out supports readConcern MongoDB 4.2 onwards. - if async_client_context.version >= (4, 1): - self.assertIn("readConcern", self.listener.started_events[0].command) - else: - self.assertNotIn("readConcern", self.listener.started_events[0].command) + self.assertIn("readConcern", self.listener.started_events[0].command) if __name__ == "__main__": diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 345748a276..ecf621fc7e 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -278,7 +278,6 @@ async def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @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_and_overload_retargeting_is_enabled( self, ): @@ -323,7 +322,6 @@ async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_ @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, ): @@ -365,7 +363,6 @@ async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_o @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, ): @@ -407,7 +404,6 @@ async def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_th 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() @@ -459,7 +455,6 @@ def failed(event: CommandFailedEvent) -> None: 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" diff --git a/test/asynchronous/test_retryable_writes.py b/test/asynchronous/test_retryable_writes.py index 9055e6dcd3..90be59016a 100644 --- a/test/asynchronous/test_retryable_writes.py +++ b/test/asynchronous/test_retryable_writes.py @@ -413,7 +413,6 @@ async def asyncSetUp(self) -> None: }, } - @async_client_context.require_version_min(4, 0) @client_knobs(heartbeat_frequency=0.05, min_heartbeat_interval=0.05) async def test_RetryableWriteError_error_label(self): listener = OvertCommandListener() @@ -431,7 +430,6 @@ async def test_RetryableWriteError_error_label(self): # In MongoDB 4.4+ we rely on the server returning the error label. self.assertIn("RetryableWriteError", listener.succeeded_events[-1].reply["errorLabels"]) - @async_client_context.require_version_min(4, 4) async def test_RetryableWriteError_error_label_RawBSONDocument(self): # using RawBSONDocument should not cause errorLabel parsing to fail async with self.fail_point(self.fail_insert): diff --git a/test/asynchronous/test_sdam_monitoring_spec.py b/test/asynchronous/test_sdam_monitoring_spec.py index dc4fda9b3e..5ffeb81eb9 100644 --- a/test/asynchronous/test_sdam_monitoring_spec.py +++ b/test/asynchronous/test_sdam_monitoring_spec.py @@ -356,15 +356,6 @@ def marked_unknown_and_rediscovered(): async def test_network_error_publishes_events(self): await self._test_app_error({"closeConnection": True}, ConnectionFailure) - # In 4.4+, not primary errors from failCommand don't cause SDAM state - # changes because topologyVersion is not incremented. - @async_client_context.require_version_max(4, 3) - async def test_not_primary_error_publishes_events(self): - await self._test_app_error( - {"errorCode": 10107, "closeConnection": False, "errorLabels": ["RetryableWriteError"]}, - NotPrimaryError, - ) - async def test_shutdown_error_publishes_events(self): await self._test_app_error( {"errorCode": 91, "closeConnection": False, "errorLabels": ["RetryableWriteError"]}, diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 39f22ca7ae..a565139f0b 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -139,14 +139,6 @@ async def test_transaction_write_concern_override(self): (coll.drop_indexes, [], {}), (coll.aggregate, [[{"$out": "aggout"}]], {}), ] - # Creating a collection in a transaction requires MongoDB 4.4+. - if async_client_context.version < (4, 3, 4): - unsupported_txn_writes.extend( - [ - (db.create_collection, ["collection"], {}), - ] - ) - for op in unsupported_txn_writes: op, args, kwargs = op async with client.start_session() as s: @@ -214,7 +206,6 @@ async def test_unpin_for_non_transaction_operation(self): self.assertGreater(len(addresses), 1) @async_client_context.require_transactions - @async_client_context.require_version_min(4, 3, 4) async def test_create_collection(self): client = async_client_context.client db = client.pymongo_test @@ -315,7 +306,6 @@ async def gridfs_open_upload_stream(*args, **kwargs): ): await op(*args, session=s) # type: ignore - # Require 4.2+ for large (16MB+) transactions. @async_client_context.require_transactions @unittest.skipIf(sys.platform == "win32", "Our Windows machines are too slow to pass this test") async def test_transaction_starts_with_batched_write(self): diff --git a/test/test_change_stream.py b/test/test_change_stream.py index 13a709a109..5fb0f6046d 100644 --- a/test/test_change_stream.py +++ b/test/test_change_stream.py @@ -965,7 +965,6 @@ def test_raw(self): self.assertEqual(change["ns"]["coll"], self.watched_collection().name) self.assertEqual(change["fullDocument"], raw_doc) - @client_context.require_version_min(4, 0) # Needed for start_at_operation_time. def test_uuid_representations(self): """Test with uuid document _ids and different uuid_representation.""" optime = (self.db.command("ping"))["operationTime"] diff --git a/test/test_collection.py b/test/test_collection.py index 7a52f94081..a595d7c54a 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -270,17 +270,7 @@ def test_create_indexes(self): with self.write_concern_collection() as coll: coll.create_indexes([IndexModel("hello")]) - @client_context.require_version_max(4, 3, -1) - def test_create_indexes_commitQuorum_requires_44(self): - db = self.db - with self.assertRaisesRegex( - ConfigurationError, - r"Must be connected to MongoDB 4\.4\+ to use the commitQuorum option for createIndexes", - ): - db.coll.create_indexes([IndexModel("a")], commitQuorum="majority") - @client_context.require_no_standalone - @client_context.require_version_min(4, 4, -1) def test_create_indexes_commitQuorum(self): self.db.coll.create_indexes([IndexModel("a")], commitQuorum="majority") @@ -1830,12 +1820,8 @@ def test_exhaust(self): # and the socket has pending data (more_to_come=True) we have to close # and discard the socket. cur = client[self.db.name].test.find(cursor_type=CursorType.EXHAUST, batch_size=2) - if client_context.version.at_least(4, 2): - # On 4.2+ we use OP_MSG which only sets more_to_come=True after the - # first getMore. - for _ in range(3): - next(cur) - else: + # OP_MSG only sets more_to_come=True after the first getMore. + for _ in range(3): next(cur) self.assertEqual(0, len(pool.conns)) # if sys.platform.startswith("java") or "PyPy" in sys.version: diff --git a/test/test_csot.py b/test/test_csot.py index da6c1ca384..2b1997c945 100644 --- a/test/test_csot.py +++ b/test/test_csot.py @@ -90,9 +90,6 @@ def test_change_stream_can_resume_after_timeouts(self): stream.try_next() self.assertTrue(ctx.exception.timeout) self.assertTrue(stream.alive) - # Resume before the insert on 3.6 because 4.0 is required to avoid skipping documents - if client_context.version < (4, 0): - stream.try_next() coll.insert_one({}) with pymongo.timeout(10): self.assertTrue(stream.next()) diff --git a/test/test_cursor.py b/test/test_cursor.py index 58aae5f0f9..806dba45c9 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -846,16 +846,13 @@ def test_where(self): self.assertEqual(3, len(db.test.find().where(Code("this.x < 3")).to_list())) code_with_scope = Code("this.x < i", {"i": 3}) - if client_context.version.at_least(4, 3, 3): - # MongoDB 4.4 removed support for Code with scope. - with self.assertRaises(OperationFailure): - db.test.find().where(code_with_scope).to_list() - - code_with_empty_scope = Code("this.x < 3", {}) - with self.assertRaises(OperationFailure): - db.test.find().where(code_with_empty_scope).to_list() - else: - self.assertEqual(3, len(db.test.find().where(code_with_scope).to_list())) + # MongoDB 4.4 removed support for Code with scope. + with self.assertRaises(OperationFailure): + db.test.find().where(code_with_scope).to_list() + + code_with_empty_scope = Code("this.x < 3", {}) + with self.assertRaises(OperationFailure): + db.test.find().where(code_with_empty_scope).to_list() self.assertEqual(10, len(db.test.find().to_list())) self.assertEqual([0, 1, 2], [a["x"] for a in db.test.find().where("this.x < 3")]) diff --git a/test/test_database.py b/test/test_database.py index 61d2494eb9..f1932c86fa 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -377,7 +377,6 @@ def test_validate_collection(self): self.assertTrue(db.validate_collection(db.test, scandata=True, full=True)) self.assertTrue(db.validate_collection(db.test, True, True)) - @client_context.require_version_min(4, 3, 3) @client_context.require_no_standalone def test_validate_collection_background(self): db = self.client.pymongo_test.with_options(write_concern=WriteConcern(w="majority")) @@ -701,15 +700,10 @@ def test_database_aggregation(self): @client_context.require_no_mongos def test_database_aggregation_fake_cursor(self): coll_name = "test_output" - write_stage: dict - if client_context.version < (4, 3): - db_name = "admin" - write_stage = {"$out": coll_name} - else: - # SERVER-43287 disallows writing with $out to the admin db, use - # $merge instead. - db_name = "pymongo_test" - write_stage = {"$merge": {"into": {"db": db_name, "coll": coll_name}}} + # SERVER-43287 disallows writing with $out to the admin db, use + # $merge instead. + db_name = "pymongo_test" + write_stage = {"$merge": {"into": {"db": db_name, "coll": coll_name}}} output_coll = self.client[db_name][coll_name] output_coll.drop() self.addCleanup(output_coll.drop) diff --git a/test/test_examples.py b/test/test_examples.py index ccc709e3aa..b97e1f7bfa 100644 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -862,7 +862,6 @@ def test_aggregate_examples(self): ) # End Aggregation Example 4 - @client_context.require_version_min(4, 4) def test_aggregate_projection_example(self): db = self.db diff --git a/test/test_message.py b/test/test_message.py index 9c11ffef5b..9f477fa36f 100644 --- a/test/test_message.py +++ b/test/test_message.py @@ -444,45 +444,28 @@ def test_get_more_basic(self): batch_size=None, max_await_time_ms=None, comment=None, - conn=self._make_conn(), ) self.assertEqual(cmd["getMore"], 12345) self.assertEqual(cmd["collection"], "col") def test_get_more_with_batch_size(self): - cmd = _gen_get_more_command( - 1, "col", batch_size=100, max_await_time_ms=None, comment=None, conn=self._make_conn() - ) + cmd = _gen_get_more_command(1, "col", batch_size=100, max_await_time_ms=None, comment=None) self.assertEqual(cmd["batchSize"], 100) def test_get_more_with_max_await_time_ms(self): - cmd = _gen_get_more_command( - 1, "col", batch_size=None, max_await_time_ms=500, comment=None, conn=self._make_conn() - ) + cmd = _gen_get_more_command(1, "col", batch_size=None, max_await_time_ms=500, comment=None) self.assertEqual(cmd["maxTimeMS"], 500) - def test_get_more_comment_added_on_high_wire_version(self): + def test_get_more_with_comment(self): cmd = _gen_get_more_command( 1, "col", batch_size=None, max_await_time_ms=None, comment="my comment", - conn=self._make_conn(9), ) self.assertEqual(cmd["comment"], "my comment") - def test_get_more_comment_not_added_on_low_wire_version(self): - cmd = _gen_get_more_command( - 1, - "col", - batch_size=None, - max_await_time_ms=None, - comment="my comment", - conn=self._make_conn(8), - ) - self.assertNotIn("comment", cmd) - if __name__ == "__main__": unittest.main() diff --git a/test/test_monitoring.py b/test/test_monitoring.py index ec24acb4a0..fde78a7c1e 100644 --- a/test/test_monitoring.py +++ b/test/test_monitoring.py @@ -277,19 +277,6 @@ def test_find_options(self): "allowPartialResults": True, } - if client_context.version < (4, 1, 0, -1): - query["max_scan"] = 10 - cmd["maxScan"] = 10 - - self._test_find_options(query, cmd) - - @client_context.require_version_max(3, 7, 2) - def test_find_snapshot(self): - # Test "snapshot" parameter separately, can't combine with "sort". - query = {"filter": {}, "snapshot": True} - - cmd = {"find": "test", "filter": {}, "snapshot": True} - self._test_find_options(query, cmd) def test_command_and_get_more(self): diff --git a/test/test_read_concern.py b/test/test_read_concern.py index 93398d14c9..f9b2faf67f 100644 --- a/test/test_read_concern.py +++ b/test/test_read_concern.py @@ -109,11 +109,7 @@ def test_aggregate_out(self): coll = self.db.get_collection("coll", read_concern=ReadConcern("local")) (coll.aggregate([{"$match": {"field": "value"}}, {"$out": "output_collection"}])).to_list() - # Aggregate with $out supports readConcern MongoDB 4.2 onwards. - if client_context.version >= (4, 1): - self.assertIn("readConcern", self.listener.started_events[0].command) - else: - self.assertNotIn("readConcern", self.listener.started_events[0].command) + self.assertIn("readConcern", self.listener.started_events[0].command) if __name__ == "__main__": diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index 9a24119020..bf0650393b 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -278,7 +278,6 @@ def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @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_and_overload_retargeting_is_enabled( self, ): @@ -323,7 +322,6 @@ def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_differ @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, ): @@ -365,7 +363,6 @@ def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_ @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, ): @@ -407,7 +404,6 @@ def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same 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() @@ -459,7 +455,6 @@ def failed(event: CommandFailedEvent) -> None: 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" diff --git a/test/test_retryable_writes.py b/test/test_retryable_writes.py index 577f9cbf0f..71552b208e 100644 --- a/test/test_retryable_writes.py +++ b/test/test_retryable_writes.py @@ -411,7 +411,6 @@ def setUp(self) -> None: }, } - @client_context.require_version_min(4, 0) @client_knobs(heartbeat_frequency=0.05, min_heartbeat_interval=0.05) def test_RetryableWriteError_error_label(self): listener = OvertCommandListener() @@ -429,7 +428,6 @@ def test_RetryableWriteError_error_label(self): # In MongoDB 4.4+ we rely on the server returning the error label. self.assertIn("RetryableWriteError", listener.succeeded_events[-1].reply["errorLabels"]) - @client_context.require_version_min(4, 4) def test_RetryableWriteError_error_label_RawBSONDocument(self): # using RawBSONDocument should not cause errorLabel parsing to fail with self.fail_point(self.fail_insert): diff --git a/test/test_sdam_monitoring_spec.py b/test/test_sdam_monitoring_spec.py index e2772b6306..437516999e 100644 --- a/test/test_sdam_monitoring_spec.py +++ b/test/test_sdam_monitoring_spec.py @@ -356,15 +356,6 @@ def marked_unknown_and_rediscovered(): def test_network_error_publishes_events(self): self._test_app_error({"closeConnection": True}, ConnectionFailure) - # In 4.4+, not primary errors from failCommand don't cause SDAM state - # changes because topologyVersion is not incremented. - @client_context.require_version_max(4, 3) - def test_not_primary_error_publishes_events(self): - self._test_app_error( - {"errorCode": 10107, "closeConnection": False, "errorLabels": ["RetryableWriteError"]}, - NotPrimaryError, - ) - def test_shutdown_error_publishes_events(self): self._test_app_error( {"errorCode": 91, "closeConnection": False, "errorLabels": ["RetryableWriteError"]}, diff --git a/test/test_transactions.py b/test/test_transactions.py index 8f4397a292..3b341f3aff 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -135,14 +135,6 @@ def test_transaction_write_concern_override(self): (coll.drop_indexes, [], {}), (coll.aggregate, [[{"$out": "aggout"}]], {}), ] - # Creating a collection in a transaction requires MongoDB 4.4+. - if client_context.version < (4, 3, 4): - unsupported_txn_writes.extend( - [ - (db.create_collection, ["collection"], {}), - ] - ) - for op in unsupported_txn_writes: op, args, kwargs = op with client.start_session() as s: @@ -206,7 +198,6 @@ def test_unpin_for_non_transaction_operation(self): self.assertGreater(len(addresses), 1) @client_context.require_transactions - @client_context.require_version_min(4, 3, 4) def test_create_collection(self): client = client_context.client db = client.pymongo_test @@ -307,7 +298,6 @@ def gridfs_open_upload_stream(*args, **kwargs): ): op(*args, session=s) # type: ignore - # Require 4.2+ for large (16MB+) transactions. @client_context.require_transactions @unittest.skipIf(sys.platform == "win32", "Our Windows machines are too slow to pass this test") def test_transaction_starts_with_batched_write(self): From 3fbaf3725af9efacdefcf773f7cc7838980a12da Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Wed, 26 Aug 2026 09:51:24 -0400 Subject: [PATCH 26/28] Cleanup + remove DEVPROD-19149 workaround --- .evergreen/scripts/install-dependencies.sh | 50 ---------------------- pymongo/asynchronous/aggregation.py | 4 +- pymongo/asynchronous/change_stream.py | 6 +-- pymongo/synchronous/aggregation.py | 4 +- pymongo/synchronous/change_stream.py | 6 +-- 5 files changed, 6 insertions(+), 64 deletions(-) diff --git a/.evergreen/scripts/install-dependencies.sh b/.evergreen/scripts/install-dependencies.sh index 89942470d1..7f943d7d00 100755 --- a/.evergreen/scripts/install-dependencies.sh +++ b/.evergreen/scripts/install-dependencies.sh @@ -34,54 +34,4 @@ if ! command -v just &>/dev/null; then uv tool install rust-just fi -# Some images (e.g. the DEVPROD-19149 Windows image) ship without a Python toolchain. -# A python3 may still be on PATH there (the mingw one that comes with chocolatey) that -# runs but cannot bootstrap pip, so probe for one that can actually create a virtual -# environment. We probe python3 specifically because that is the name -# drivers-evergreen-tools' find_python3 falls back to, and it only checks that the -# interpreter runs - not that it can build the venv it then goes on to need. -_have_python3="" -if command -v python3 &>/dev/null; then - _probe=$(mktemp -d) - _probe_arg="$_probe" - if [ "Windows_NT" = "${OS:-}" ]; then - _probe_arg=$(cygpath -aw "$_probe") - fi - if python3 -m venv "$_probe_arg" &>/dev/null; then - _have_python3="1" - fi - rm -rf "$_probe" -fi - -# Install a Python with uv and expose it so that both pymongo and -# drivers-evergreen-tools can find it. -if [ -z "$_have_python3" ]; then - echo "No venv-capable python3 found, installing with uv..." - # UV_PYTHON may be a toolchain path; fall back to a bare version for the download. - _py_ver="${UV_PYTHON:-}" - case "$_py_ver" in - ""|*/*|*\\*) _py_ver="3.10" ;; - esac - uv python install "$_py_ver" - _py_bin="$(uv python find --no-project --managed-python "$_py_ver")" - if [ "Windows_NT" = "${OS:-}" ]; then - _py_bin=$(cygpath -u "$_py_bin") - fi - _py_dir=$(dirname "$_py_bin") - # uv's Windows builds ship python.exe only. Without a python3.exe alongside it the - # mingw python3 further down PATH still wins every bare `python3` lookup. - if [ "Windows_NT" = "${OS:-}" ] && [ ! -e "$_py_dir/python3.exe" ]; then - cp "$_py_bin" "$_py_dir/python3.exe" - fi - export PATH="$_py_dir:$PATH" - export DRIVERS_TOOLS_PYTHON="$_py_bin" - # Persist for the steps that source env.sh rather than inheriting this shell. - cat <> "$HERE/env.sh" -export PATH="$_py_dir:\$PATH" -export DRIVERS_TOOLS_PYTHON="$_py_bin" -EOT - echo "Installed Python at $_py_bin" - echo "python3 now resolves to: $(command -v python3 || echo NONE)" -fi - popd > /dev/null diff --git a/pymongo/asynchronous/aggregation.py b/pymongo/asynchronous/aggregation.py index 74b65dfd60..3d5842bde0 100644 --- a/pymongo/asynchronous/aggregation.py +++ b/pymongo/asynchronous/aggregation.py @@ -53,7 +53,7 @@ def __init__( options: MutableMapping[str, Any], let: Optional[Mapping[str, Any]] = None, user_fields: Optional[MutableMapping[str, Any]] = None, - result_processor: Optional[Callable[[Mapping[str, Any], AsyncConnection], None]] = None, + result_processor: Optional[Callable[[Mapping[str, Any]], None]] = None, comment: Any = None, ) -> None: if "explain" in options: @@ -176,7 +176,7 @@ async def get_cursor( ) if self._result_processor: - self._result_processor(result, conn) + self._result_processor(result) # Extract cursor from result or mock/fake one if necessary. if "cursor" in result: diff --git a/pymongo/asynchronous/change_stream.py b/pymongo/asynchronous/change_stream.py index 7d8876852b..33f79814b1 100644 --- a/pymongo/asynchronous/change_stream.py +++ b/pymongo/asynchronous/change_stream.py @@ -48,7 +48,6 @@ from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.database import AsyncDatabase from pymongo.asynchronous.mongo_client import AsyncMongoClient - from pymongo.asynchronous.pool import AsyncConnection def _resumable(exc: PyMongoError) -> bool: @@ -183,13 +182,10 @@ def _aggregation_pipeline(self) -> list[dict[str, Any]]: full_pipeline.extend(self._pipeline) return full_pipeline - def _process_result(self, result: Mapping[str, Any], conn: AsyncConnection) -> None: + def _process_result(self, result: Mapping[str, Any]) -> None: """Callback that caches the postBatchResumeToken or startAtOperationTime from a changeStream aggregate command response containing an empty batch of change documents. - - This is implemented as a callback because we need access to the wire - version in order to determine whether to cache this value. """ if not result["cursor"]["firstBatch"]: if "postBatchResumeToken" in result["cursor"]: diff --git a/pymongo/synchronous/aggregation.py b/pymongo/synchronous/aggregation.py index 5025a4ed93..0f10de4278 100644 --- a/pymongo/synchronous/aggregation.py +++ b/pymongo/synchronous/aggregation.py @@ -53,7 +53,7 @@ def __init__( options: MutableMapping[str, Any], let: Optional[Mapping[str, Any]] = None, user_fields: Optional[MutableMapping[str, Any]] = None, - result_processor: Optional[Callable[[Mapping[str, Any], Connection], None]] = None, + result_processor: Optional[Callable[[Mapping[str, Any]], None]] = None, comment: Any = None, ) -> None: if "explain" in options: @@ -176,7 +176,7 @@ def get_cursor( ) if self._result_processor: - self._result_processor(result, conn) + self._result_processor(result) # Extract cursor from result or mock/fake one if necessary. if "cursor" in result: diff --git a/pymongo/synchronous/change_stream.py b/pymongo/synchronous/change_stream.py index c0c15d04ef..44a6dab2a3 100644 --- a/pymongo/synchronous/change_stream.py +++ b/pymongo/synchronous/change_stream.py @@ -48,7 +48,6 @@ from pymongo.synchronous.collection import Collection from pymongo.synchronous.database import Database from pymongo.synchronous.mongo_client import MongoClient - from pymongo.synchronous.pool import Connection def _resumable(exc: PyMongoError) -> bool: @@ -183,13 +182,10 @@ def _aggregation_pipeline(self) -> list[dict[str, Any]]: full_pipeline.extend(self._pipeline) return full_pipeline - def _process_result(self, result: Mapping[str, Any], conn: Connection) -> None: + def _process_result(self, result: Mapping[str, Any]) -> None: """Callback that caches the postBatchResumeToken or startAtOperationTime from a changeStream aggregate command response containing an empty batch of change documents. - - This is implemented as a callback because we need access to the wire - version in order to determine whether to cache this value. """ if not result["cursor"]["firstBatch"]: if "postBatchResumeToken" in result["cursor"]: From d465117385b70de1c55267dcad7a2ba738b63ff4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 26 Aug 2026 09:54:49 -0500 Subject: [PATCH 27/28] PYTHON-5808 Trim redundant comments and list 9.0 as supported --- README.md | 2 +- doc/changelog.rst | 2 +- pymongo/asynchronous/aggregation.py | 1 - pymongo/asynchronous/mongo_client.py | 1 - pymongo/asynchronous/topology.py | 1 - pymongo/synchronous/aggregation.py | 1 - pymongo/synchronous/mongo_client.py | 1 - pymongo/synchronous/topology.py | 1 - 8 files changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d95810c487..babf0fc79b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ a native Python driver for MongoDB, offering both synchronous and asynchronous A [gridfs](https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.md/) implementation on top of `pymongo`. -PyMongo supports MongoDB 4.4, 5.0, 6.0, 7.0, and 8.0. PyMongo follows [semantic versioning](https://semver.org/spec/v2.0.0.html) for its releases. +PyMongo supports MongoDB 4.4, 5.0, 6.0, 7.0, 8.0, and 9.0. PyMongo follows [semantic versioning](https://semver.org/spec/v2.0.0.html) for its releases. ## Documentation diff --git a/doc/changelog.rst b/doc/changelog.rst index f345d258b0..99230581a5 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,7 +6,7 @@ Changes in Version 4.18.0 (2026/XX/XX) PyMongo 4.18 brings a number of changes including: -- Dropped support for MongoDB 4.2. PyMongo now requires MongoDB 4.4 or later. +- Dropped support for MongoDB 4.2; PyMongo now requires MongoDB 4.4 or later. - Added support for MongoDB 9.0. - Improved TLS connection performance by reusing TLS sessions across connections to the same server, avoiding a full handshake on each new connection. diff --git a/pymongo/asynchronous/aggregation.py b/pymongo/asynchronous/aggregation.py index 3d5842bde0..8d21ecf46f 100644 --- a/pymongo/asynchronous/aggregation.py +++ b/pymongo/asynchronous/aggregation.py @@ -145,7 +145,6 @@ async def get_cursor( cmd = {"aggregate": self._aggregation_target, "pipeline": self._pipeline} cmd.update(self._options) - # Apply this target's read concern if readConcern has not been specified as a kwarg. # $out/$merge pipelines also support readConcern on all supported server versions (4.4+). if "readConcern" not in cmd: read_concern = self._target.read_concern diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 02f2e88a80..baa281b59a 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -2595,7 +2595,6 @@ def _add_retryable_write_error(exc: PyMongoError) -> None: "to your connection string." ) raise OperationFailure(errmsg, code, exc.details) # type: ignore[attr-defined] - # The server reports the error labels. for label in doc.get("errorLabels", []): exc._add_error_label(label) diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index c9ee9dfc0b..72481296ff 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -777,7 +777,6 @@ async def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None err_code = error.details.get("code", default) # type: ignore[union-attr] if err_code in helpers_shared._NOT_PRIMARY_CODES: is_shutting_down = err_code in helpers_shared._SHUTDOWN_CODES - # Mark server Unknown and request check. Clear the pool only on shutdown. if not self._settings.load_balanced: await self._process_change(ServerDescription(address, error=error)) if is_shutting_down: diff --git a/pymongo/synchronous/aggregation.py b/pymongo/synchronous/aggregation.py index 0f10de4278..df49bbfbdd 100644 --- a/pymongo/synchronous/aggregation.py +++ b/pymongo/synchronous/aggregation.py @@ -145,7 +145,6 @@ def get_cursor( cmd = {"aggregate": self._aggregation_target, "pipeline": self._pipeline} cmd.update(self._options) - # Apply this target's read concern if readConcern has not been specified as a kwarg. # $out/$merge pipelines also support readConcern on all supported server versions (4.4+). if "readConcern" not in cmd: read_concern = self._target.read_concern diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index ffb55ce333..c6b03201b6 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -2584,7 +2584,6 @@ def _add_retryable_write_error(exc: PyMongoError) -> None: "to your connection string." ) raise OperationFailure(errmsg, code, exc.details) # type: ignore[attr-defined] - # The server reports the error labels. for label in doc.get("errorLabels", []): exc._add_error_label(label) diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index e2ceec7466..cd451218e1 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -775,7 +775,6 @@ def _handle_error(self, address: _Address, err_ctx: _ErrorContext) -> None: err_code = error.details.get("code", default) # type: ignore[union-attr] if err_code in helpers_shared._NOT_PRIMARY_CODES: is_shutting_down = err_code in helpers_shared._SHUTDOWN_CODES - # Mark server Unknown and request check. Clear the pool only on shutdown. if not self._settings.load_balanced: self._process_change(ServerDescription(address, error=error)) if is_shutting_down: From 9e555f9e8b7ac79898a9e7dab269d34003a9614f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 27 Aug 2026 08:49:40 -0500 Subject: [PATCH 28/28] PYTHON-5808 Match the changelog wording used by previous releases --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 99230581a5..064982d29c 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,7 +6,7 @@ Changes in Version 4.18.0 (2026/XX/XX) PyMongo 4.18 brings a number of changes including: -- Dropped support for MongoDB 4.2; PyMongo now requires MongoDB 4.4 or later. +- Dropped support for MongoDB 4.2. - Added support for MongoDB 9.0. - Improved TLS connection performance by reusing TLS sessions across connections to the same server, avoiding a full handshake on each new connection.