Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
638 changes: 53 additions & 585 deletions .evergreen/generated_configs/tasks.yml

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions .evergreen/generated_configs/variants.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ buildvariants:
tags: [encryption_tag]
- name: encryption-macos
tasks:
- name: .test-non-standard !.pypy !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.pypy !.python-3.15t
- name: .test-non-standard !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.python-3.15t
- name: .test-string-query-preview
display_name: Encryption macOS
run_on:
Expand All @@ -208,8 +208,8 @@ buildvariants:
tags: [encryption_tag]
- name: encryption-win64
tasks:
- name: .test-non-standard !.pypy !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.pypy !.python-3.15t
- name: .test-non-standard !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.python-3.15t
- name: .test-string-query-preview
display_name: Encryption Win64
run_on:
Expand All @@ -232,8 +232,8 @@ buildvariants:
tags: [encryption_tag]
- name: encryption-crypt_shared-macos
tasks:
- name: .test-non-standard !.pypy !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.pypy !.python-3.15t
- name: .test-non-standard !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.python-3.15t
- name: .test-string-query-preview
display_name: Encryption crypt_shared macOS
run_on:
Expand All @@ -245,8 +245,8 @@ buildvariants:
tags: [encryption_tag]
- name: encryption-crypt_shared-win64
tasks:
- name: .test-non-standard !.pypy !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.pypy !.python-3.15t
- name: .test-non-standard !.cov !.python-3.15t
- name: .test-non-standard-no-cov !.python-3.15t
- name: .test-string-query-preview
display_name: Encryption crypt_shared Win64
run_on:
Expand Down Expand Up @@ -301,7 +301,7 @@ buildvariants:
AUTH: auth
- name: auth-enterprise-macos
tasks:
- name: .test-standard-auth !.pypy .auth !.free-threaded
- name: .test-standard-auth .auth !.free-threaded
display_name: Auth Enterprise macOS
run_on:
- macos-14
Expand All @@ -310,7 +310,7 @@ buildvariants:
AUTH: auth
- name: auth-enterprise-win64
tasks:
- name: .test-standard-auth !.pypy .auth !.free-threaded
- name: .test-standard-auth .auth !.free-threaded
display_name: Auth Enterprise Win64
run_on:
- windows-2022-latest-small
Expand Down Expand Up @@ -490,8 +490,8 @@ buildvariants:
SUB_TEST_NAME: pyopenssl
- name: pyopenssl-macos
tasks:
- name: .test-standard !.pypy !.python-3.15t .sync
- name: .test-standard !.pypy !.python-3.15t .async .replica_set-noauth-ssl
- name: .test-standard !.python-3.15t .sync
- name: .test-standard !.python-3.15t .async .replica_set-noauth-ssl
display_name: PyOpenSSL macOS
run_on:
- macos-14
Expand All @@ -500,8 +500,8 @@ buildvariants:
SUB_TEST_NAME: pyopenssl
- name: pyopenssl-win64
tasks:
- name: .test-standard !.pypy !.python-3.15t .sync
- name: .test-standard !.pypy !.python-3.15t .async .replica_set-noauth-ssl
- name: .test-standard !.python-3.15t .sync
- name: .test-standard !.python-3.15t .async .replica_set-noauth-ssl
display_name: PyOpenSSL Win64
run_on:
- windows-2022-latest-small
Expand Down Expand Up @@ -651,34 +651,34 @@ buildvariants:
# Standard nonlinux tests
- name: test-macos
tasks:
- name: .test-standard !.pypy
- name: .test-standard
display_name: "* Test macOS"
run_on:
- macos-14
tags: [standard-non-linux]
- name: test-macos-arm64
tasks:
- name: .test-standard !.pypy .server-6.0
- name: .test-standard !.pypy .server-7.0
- name: .test-standard !.pypy .server-8.0
- name: .test-standard !.pypy .server-9.0
- name: .test-standard !.pypy .server-rapid
- name: .test-standard !.pypy .server-latest
- name: .test-standard .server-6.0
- name: .test-standard .server-7.0
- name: .test-standard .server-8.0
- name: .test-standard .server-9.0
- name: .test-standard .server-rapid
- name: .test-standard .server-latest
display_name: "* Test macOS Arm64"
run_on:
- macos-14-arm64
tags: [standard-non-linux]
- name: test-win64
tasks:
- name: .test-standard !.pypy
- name: .test-no-orchestration !.pypy
- name: .test-standard
- name: .test-no-orchestration
display_name: "* Test Win64"
run_on:
- windows-2022-latest-small
tags: [standard-non-linux]
- name: test-win32
tasks:
- name: .test-standard !.pypy !.free-threaded
- name: .test-standard !.free-threaded
display_name: "* Test Win32"
run_on:
- windows-64-vsMulti-small
Expand Down
49 changes: 14 additions & 35 deletions .evergreen/scripts/generate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
HOSTS,
MIN_MAX_PYTHON,
OTHER_HOSTS,
PYPYS,
SYNCS,
TOPOLOGIES,
create_variant,
Expand Down Expand Up @@ -104,20 +103,18 @@ def create_standard_nonlinux_variants() -> list[BuildVariant]:

# Test a subset on each of the other platforms.
for host_name in ("macos", "macos-arm64", "win64", "win32"):
tasks = [".test-standard !.pypy"]
tasks = [".test-standard"]
# MacOS arm64 only works on server versions 6.0+
if host_name == "macos-arm64":
tasks = [
f".test-standard !.pypy .server-{version}" for version in get_versions_from("6.0")
]
tasks = [f".test-standard .server-{version}" for version in get_versions_from("6.0")]
if host_name == "win64":
tasks.append(".test-no-orchestration !.pypy")
tasks.append(".test-no-orchestration")
host = HOSTS[host_name]
tags = ["standard-non-linux"]
expansions = dict()
if host_name == "win32":
expansions["IS_WIN32"] = "1"
tasks = [".test-standard !.pypy !.free-threaded"]
tasks = [".test-standard !.free-threaded"]
display_name = get_variant_name(base_display_name, host)
variant = create_variant(tasks, display_name, host=host, tags=tags, expansions=expansions)
variants.append(variant)
Expand Down Expand Up @@ -146,13 +143,13 @@ def get_encryption_expansions(encryption):
display_name = get_variant_name(encryption, host, **expansions)
tasks = [".test-non-standard !.python-3.15t", ".test-string-query-preview"]
if host != "rhel8":
# Exclude PyPy (not tested with encryption on macOS/win64) and coverage tasks
# (encryption suites exceed the 60-min timeout with coverage overhead on macOS/win64).
# Exclude coverage tasks (encryption suites exceed the 60-min timeout with
# coverage overhead on macOS/win64).
# Also include the non-coverage companion tasks (test-non-standard-no-cov) which
# carry the "latest" server tasks without COVERAGE=1.
tasks = [
".test-non-standard !.pypy !.cov !.python-3.15t",
".test-non-standard-no-cov !.pypy !.python-3.15t",
".test-non-standard !.cov !.python-3.15t",
".test-non-standard-no-cov !.python-3.15t",
".test-string-query-preview",
]
variant = create_variant(
Expand Down Expand Up @@ -275,10 +272,10 @@ def create_enterprise_auth_variants():
tasks = [".test-standard-auth .auth !.free-threaded"]
# https://jira.mongodb.org/browse/PYTHON-5586
if host == "macos":
tasks = [".test-standard-auth !.pypy .auth !.free-threaded"]
tasks = [".test-standard-auth .auth !.free-threaded"]
if host == "win64":
# https://jira.mongodb.org/browse/PYTHON-5704
tasks = [".test-standard-auth !.pypy .auth !.free-threaded"]
tasks = [".test-standard-auth .auth !.free-threaded"]
variant = create_variant(tasks, display_name, host=host, expansions=expansions)
variants.append(variant)
return variants
Expand All @@ -292,11 +289,7 @@ def create_pyopenssl_variants():

for host in ["rhel8", "macos", "win64"]:
display_name = get_variant_name(base_name, host)
base_task = (
".test-standard !.python-3.15t"
if host == "rhel8"
else ".test-standard !.pypy !.python-3.15t"
)
base_task = ".test-standard !.python-3.15t"
# We only need to run a subset on async.
tasks = [f"{base_task} .sync", f"{base_task} .async .replica_set-noauth-ssl"]
variants.append(
Expand Down Expand Up @@ -728,9 +721,6 @@ def create_test_non_standard_tasks():
for (version, topology), python in zip_cycle(list(product(ALL_VERSIONS, TOPOLOGIES)), CPYTHONS):
pr = version == "latest" and topology == "sharded_cluster"
task_combos.add((version, topology, python, pr))
# For each PyPy and topology, rotate through the MongoDB versions.
for (python, topology), version in zip_cycle(list(product(PYPYS, TOPOLOGIES)), ALL_VERSIONS):
task_combos.add((version, topology, python, False))
for version, topology, python, pr in sorted(task_combos):
auth, ssl = get_standard_auth_ssl(topology)
tags = [
Expand All @@ -742,8 +732,6 @@ def create_test_non_standard_tasks():
]
if "t" in python:
tags.append("free-threaded")
if python in PYPYS:
tags.append("pypy")
if pr:
tags.append("pr")
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version)
Expand Down Expand Up @@ -819,11 +807,6 @@ def create_test_standard_auth_tasks():
):
pr = version == "latest"
task_combos.add((version, topology, python, pr))
# Rotate through each PyPy and MongoDB versions.
for (python, topology), version in zip_cycle(
list(product(PYPYS, ["sharded_cluster"])), ALL_VERSIONS
):
task_combos.add((version, topology, python, False))
for version, topology, python, pr in sorted(task_combos):
auth, ssl = get_standard_auth_ssl(topology)
tags = [
Expand All @@ -835,8 +818,6 @@ def create_test_standard_auth_tasks():
]
if "t" in python:
tags.append("free-threaded")
if python in PYPYS:
tags.append("pypy")
if pr:
tags.append("pr")
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version)
Expand All @@ -859,9 +840,9 @@ def create_standard_tasks():
task_combos = set()
# For each python and topology and sync/async, rotate through the the versions.
for (python, topology, sync), version in zip_cycle(
list(product(CPYTHONS + PYPYS, TOPOLOGIES, SYNCS)), ALL_VERSIONS
list(product(CPYTHONS, TOPOLOGIES, SYNCS)), ALL_VERSIONS
):
pr = version == "latest" and python not in PYPYS
pr = version == "latest"
task_combos.add((version, topology, python, sync, pr))

for version, topology, python, sync, pr in sorted(task_combos):
Expand All @@ -875,8 +856,6 @@ def create_standard_tasks():
]
if "t" in python:
tags.append("free-threaded")
if python in PYPYS:
tags.append("pypy")
if pr:
tags.append("pr")
expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version)
Expand All @@ -896,7 +875,7 @@ def create_standard_tasks():

def create_no_orchestration_tasks():
tasks = []
for python in [*MIN_MAX_PYTHON, PYPYS[-1]]:
for python in MIN_MAX_PYTHON:
tags = [
"test-no-orchestration",
f"python-{python}",
Expand Down
8 changes: 2 additions & 6 deletions .evergreen/scripts/generate_config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@

ALL_VERSIONS = ["4.4", "5.0", "6.0", "7.0", "8.0", "9.0", "rapid", "latest"]
CPYTHONS = ["3.11", "3.12", "3.13", "3.14t", "3.14", "3.15t", "3.15"]
PYPYS = ["pypy3.11"]
MIN_SUPPORT_VERSIONS = ["3.11"]
ALL_PYTHONS = CPYTHONS + PYPYS
ALL_PYTHONS = CPYTHONS
MIN_MAX_PYTHON = [CPYTHONS[0], CPYTHONS[-1]]
BATCHTIME_WEEK = 10080
BATCHTIME_DAY = 1440
Expand Down Expand Up @@ -179,10 +178,7 @@ def get_common_name(base: str, sep: str, **kwargs) -> str:
for key, value in kwargs.items():
name = value
if key.lower() in ["python", "uv_python"]:
if not value.startswith("pypy"):
name = f"Python{value}"
else:
name = f"PyPy{value.replace('pypy', '')}"
name = f"Python{value}"
elif key.lower() in DISPLAY_LOOKUP and value in DISPLAY_LOOKUP[key.lower()]:
name = DISPLAY_LOOKUP[key.lower()][value]
else:
Expand Down
4 changes: 2 additions & 2 deletions .evergreen/scripts/setup-uv-python.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Choose the Python that uv uses, and make sure uv can provide it.
#
# Input:
# UV_PYTHON - a version ("3.11", "3.14t"), an implementation ("pypy3.11"),
# or an interpreter path ("/usr/bin/python3.11").
# UV_PYTHON - a version ("3.11", "3.14t") or an interpreter path
# ("/usr/bin/python3.11").
#
# Exports:
# UV_PYTHON_SEARCH_PATH - the Python toolchain bin dir for the request, so uv
Expand Down
3 changes: 1 addition & 2 deletions .evergreen/scripts/setup_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,8 +528,7 @@ def handle_test_env() -> None:
TEST_ARGS = f"test/performance/async_perf_test.py {TEST_ARGS}"

# Add coverage if requested.
# Only cover CPython. PyPy reports suspiciously low coverage.
if opts.cov and platform.python_implementation() == "CPython":
if opts.cov:
# Keep in sync with combine-coverage.sh.
# coverage >=5 is needed for relative_files=true.
UV_ARGS.append("--group coverage")
Expand Down
9 changes: 4 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ be of interest or that has already been addressed.

## Supported Interpreters

PyMongo supports CPython 3.11+ and PyPy 3.11+. Language features not
supported by all interpreters can not be used.
PyMongo supports CPython 3.11+.

## Style Guide

Expand Down Expand Up @@ -464,10 +463,10 @@ To run any of the test suites with minimum supported dependencies, pass `--test-
- If there are any services or atlas clusters to teardown, handle them in `.evergreen/scripts/teardown_tests.py`.
- Add functions to generate the test variant(s) and task(s) to the `.evergreen/scripts/generate_config.py`.
- There are some considerations about the Python version used in the test:
- To request a specific Python, set `UV_PYTHON` (e.g. `UV_PYTHON=3.10`, `UV_PYTHON=3.14t`, or
`UV_PYTHON=pypy3.11`). Tasks are host-agnostic, so the interpreter lookup happens on the host: for a plain
- To request a specific Python, set `UV_PYTHON` (e.g. `UV_PYTHON=3.10` or `UV_PYTHON=3.14t`).
Tasks are host-agnostic, so the interpreter lookup happens on the host: for a plain
CPython version whose toolchain dir exists, `UV_PYTHON_SEARCH_PATH` points uv at it, and for anything else
(including PyPy, or a version the toolchain lacks) uv downloads it.
(a version the toolchain lacks) uv downloads it.
- If a specific Python binary is needed (for example on the FIPS host), set `UV_PYTHON=/path/to/python`.
- The default if `UV_PYTHON` is not set is CPython 3.10. This is deterministic across host types, so a task
that does not pin a version always gets the same Python (with the build headers (`Python.h`) needed to build
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ package that is incompatible with PyMongo.

## Dependencies

PyMongo supports CPython 3.11+ and PyPy3.11+. PyPy support is deprecated and will be removed in a future release.
PyMongo supports CPython 3.11+.

Required dependencies:

Expand Down
6 changes: 1 addition & 5 deletions _setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,7 @@ def build_extension(self, ext):
except ValueError:
pass
ext_modules = []
elif (
sys.platform.startswith("java")
or sys.platform == "cli"
or sys.implementation.name in ("pypy", "graalpy")
):
elif sys.implementation.name == "graalpy":
sys.stdout.write(
"""
*****************************************************\n
Expand Down
7 changes: 3 additions & 4 deletions bson/codec_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,9 @@ def __new__(
) -> CodecOptions:
doc_class = document_class or dict
# Generic aliases like SON[str, Any] or dict[str, Any] aren't classes, so
# resolve to their origin before the subclass check. Whether issubclass()
# raises TypeError or just returns False for such aliases is inconsistent
# across Python implementations (e.g. PyPy vs CPython), so check the
# origin proactively instead of relying on catching the error.
# resolve to their origin before the subclass check. issubclass() raises
# TypeError for such aliases, so check the origin proactively instead of
# relying on catching the error.
check_class = getattr(doc_class, "__origin__", doc_class)
try:
is_mapping = issubclass(check_class, _MutableMapping)
Expand Down
7 changes: 4 additions & 3 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ Changes in Version 4.19.0 (2026/XX/XX)

PyMongo 4.19 brings a number of changes including:

.. warning:: PyMongo 4.19 drops support for Python 3.9 and 3.10 and PyPy 3.9
and 3.10: Python 3.11+ or PyPy 3.11+ is now required.
.. warning:: PyMongo 4.19 drops support for Python 3.9 and 3.10 and removes
PyPy support: Python 3.11+ is now required.

- Added support for Python 3.15.
- Dropped support for Python 3.9 and 3.10 and PyPy 3.9 and 3.10 (PYTHON-6138).
- Dropped support for Python 3.9 and 3.10.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't add it because it looked like we historically don't add ticket numbers to stuff like this

- Dropped support for MongoDB 4.2.
for example

- Removed support for PyPy.
- Added support for running the synchronous and asynchronous clients in
subinterpreters, including inside
``concurrent.futures.InterpreterPoolExecutor`` (Python 3.14+). Only in
Expand Down
Loading
Loading