Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2548696
PYTHON-5808 Drop support for MongoDB 4.2
blink1073 Jun 24, 2026
b2b6c5b
PYTHON-5808 Fix CI failures after dropping MongoDB 4.2 support
blink1073 Jun 24, 2026
c38d908
Revert "PYTHON-5808 Fix CI failures after dropping MongoDB 4.2 support"
blink1073 Jun 24, 2026
f4122aa
PYTHON-5808 Fix CI failures after dropping MongoDB 4.2 support
blink1073 Jun 25, 2026
6a1bad4
PYTHON-5808 Fix mockupdb CI failures after dropping MongoDB 4.2 support
blink1073 Jun 25, 2026
3e6873a
PYTHON-5808 Fix gevent coverage and auth CI failures
blink1073 Jun 25, 2026
2a8974e
PYTHON-5808 Use KEYTAB_BASE64_BUILD for enterprise auth kinit
blink1073 Jun 25, 2026
fcb2f7a
PYTHON-5808 Revert KEYTAB_BASE64_BUILD change in setup_tests.py
blink1073 Jul 1, 2026
bfe0f43
PYTHON-5808 Configure krb5.conf to use BUILD LDAP realm
blink1073 Jul 1, 2026
07bd003
PYTHON-5808 Use PRINCIPAL (not PRINCIPAL_BUILD) for kinit and GSSAPI
blink1073 Jul 1, 2026
a853188
try with all new secrets
blink1073 Jul 6, 2026
48505a4
fix krb conf
blink1073 Jul 6, 2026
bda89fd
fix conf
blink1073 Jul 6, 2026
ad6c63a
fix conf
blink1073 Jul 6, 2026
355fb92
use the windows-2022-latest-small-DEVPROD-19149 host
blink1073 Aug 19, 2026
4025aba
use the windows-2022-latest-small-DEVPROD-19149 host
blink1073 Aug 19, 2026
7546a0f
Merge branch 'main' of github.com:mongodb/mongo-python-driver into PY…
blink1073 Aug 19, 2026
a817f99
PYTHON-5808 Fix _Query.get_message() call after merge
blink1073 Aug 19, 2026
04f7e23
PYTHON-5808 Remove from_command arg from Response/PinnedResponse cons…
blink1073 Aug 19, 2026
b2e776d
PYTHON-5808 Fix auth-enterprise-win64 variant to use DEVPROD-19149 di…
blink1073 Aug 19, 2026
f879a70
PYTHON-5808 Install Python via uv on images without a toolchain
blink1073 Aug 20, 2026
9a5583b
PYTHON-5808 Probe for a venv-capable Python before using the one on PATH
blink1073 Aug 20, 2026
f9d6d23
PYTHON-5808 Shadow the broken mingw python3 with the uv-installed one
blink1073 Aug 20, 2026
1197951
PYTHON-5808 Remove the DEVPROD-19149 Windows host override
blink1073 Aug 25, 2026
2371450
PYTHON-5808 Use the unsuffixed enterprise auth secrets
blink1073 Aug 25, 2026
c3fe231
Second pass
NoahStapp Aug 25, 2026
6098d4b
Merge branch 'main' into PYTHON-5808
NoahStapp Aug 25, 2026
3fbaf37
Cleanup + remove DEVPROD-19149 workaround
NoahStapp Aug 26, 2026
d465117
PYTHON-5808 Trim redundant comments and list 9.0 as supported
blink1073 Aug 26, 2026
9e555f9
PYTHON-5808 Match the changelog wording used by previous releases
blink1073 Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,190 changes: 530 additions & 660 deletions .evergreen/generated_configs/tasks.yml

Large diffs are not rendered by default.

15 changes: 3 additions & 12 deletions .evergreen/generated_configs/variants.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,16 @@ 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
expansions:
COMPRESSOR: zstd
- name: compression-zstd-ubuntu-22
tasks:
- name: .test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14
- name: .test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14t
- 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
Expand Down Expand Up @@ -492,15 +492,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
Expand Down
9 changes: 3 additions & 6 deletions .evergreen/scripts/generate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion .evergreen/scripts/generate_config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# Globals
##############

ALL_VERSIONS = ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "rapid", "latest"]
ALL_VERSIONS = ["4.4", "5.0", "6.0", "7.0", "8.0", "9.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"]
Expand Down
1 change: 1 addition & 0 deletions .evergreen/scripts/install-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions .evergreen/scripts/setup-dev-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 9 additions & 2 deletions .evergreen/scripts/setup-uv-python.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions .evergreen/scripts/setup_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ def handle_test_env() -> None:
write_env("GSSAPI_HOST", config["SASL_HOST"])
write_env("GSSAPI_PORT", config["SASL_PORT"])
write_env("GSSAPI_PRINCIPAL", config["PRINCIPAL"])
write_env("SASL_HOST", config["SASL_HOST"])
Comment thread
NoahStapp marked this conversation as resolved.

if test_name == "doctest":
UV_ARGS.append("--extra docs")
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, 8.0, and 9.0. PyMongo follows [semantic versioning](https://semver.org/spec/v2.0.0.html) for its releases.

## Documentation

Expand Down
1 change: 1 addition & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +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.
- 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.
Expand Down
13 changes: 4 additions & 9 deletions pymongo/asynchronous/aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -145,13 +145,8 @@ 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)
):
# $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
Expand Down Expand Up @@ -180,7 +175,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:
Expand Down
25 changes: 11 additions & 14 deletions pymongo/asynchronous/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
3 changes: 0 additions & 3 deletions pymongo/asynchronous/auth_aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
13 changes: 0 additions & 13 deletions pymongo/asynchronous/bulk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -572,14 +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_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."
Expand Down
38 changes: 2 additions & 36 deletions pymongo/asynchronous/change_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,49 +43,19 @@

_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
from pymongo.asynchronous.database import AsyncDatabase
from pymongo.asynchronous.mongo_client import AsyncMongoClient
from pymongo.asynchronous.pool import AsyncConnection


def _resumable(exc: PyMongoError) -> bool:
"""Return True if given a resumable change stream error."""
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


Expand Down Expand Up @@ -212,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"]:
Expand All @@ -227,7 +194,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
):
Comment thread
NoahStapp marked this conversation as resolved.
self._start_at_operation_time = result.get("operationTime")
# PYTHON-2181: informative error on missing operationTime.
Expand Down
Loading
Loading