From 5852bd80feae24933169274f101e918266539503 Mon Sep 17 00:00:00 2001 From: Logan Pulley Date: Thu, 16 Oct 2025 08:42:32 -0700 Subject: [PATCH 1/4] Change with_transaction callback return type to Awaitable --- pymongo/asynchronous/client_session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index 8674e98447..6ab3b39983 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -143,8 +143,8 @@ TYPE_CHECKING, Any, AsyncContextManager, + Awaitable, Callable, - Coroutine, Mapping, MutableMapping, NoReturn, @@ -604,7 +604,7 @@ def _inherit_option(self, name: str, val: _T) -> _T: async def with_transaction( self, - callback: Callable[[AsyncClientSession], Coroutine[Any, Any, _T]], + callback: Callable[[AsyncClientSession], Awaitable[_T]], read_concern: Optional[ReadConcern] = None, write_concern: Optional[WriteConcern] = None, read_preference: Optional[_ServerMode] = None, From 6686557caf989b33a650743325e2705150aeaa0d Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 20 Oct 2025 15:24:44 -0400 Subject: [PATCH 2/4] PYTHON-5623 - Loosen AsyncClientSession.with_transaction callback type --- tools/synchro.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/synchro.py b/tools/synchro.py index e3d4835502..1444b22994 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -322,6 +322,14 @@ def translate_coroutine_types(lines: list[str]) -> list[str]: index = lines.index(type) new = type.replace(old, res.group(3)) lines[index] = new + coroutine_types = [line for line in lines if "Awaitable[" in line] + for type in coroutine_types: + res = re.search(r"Awaitable\[([A-z]+)\]", type) + if res: + old = res[0] + index = lines.index(type) + new = type.replace(old, res.group(1)) + lines[index] = new return lines From cf4d171fd82761a517131f603d1ddff5708226d0 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 20 Oct 2025 15:51:07 -0400 Subject: [PATCH 3/4] add test --- test/asynchronous/test_transactions.py | 12 ++++++++++++ test/test_transactions.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index 478710362e..29c5d26423 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -15,6 +15,7 @@ """Execute Transactions Spec tests.""" from __future__ import annotations +import asyncio import sys from io import BytesIO from test.asynchronous.utils_spec_runner import AsyncSpecRunner @@ -468,6 +469,17 @@ async def callback2(session): async with self.client.start_session() as s: self.assertEqual(await s.with_transaction(callback2), "Foo") + @async_client_context.require_transactions + @async_client_context.require_async + async def test_callback_awaitable_no_coroutine(self): + def callback(_): + future = asyncio.Future() + future.set_result("Foo") + return future + + async with self.client.start_session() as s: + self.assertEqual(await s.with_transaction(callback), "Foo") + @async_client_context.require_transactions async def test_callback_not_retried_after_timeout(self): listener = OvertCommandListener() diff --git a/test/test_transactions.py b/test/test_transactions.py index 813d6a688d..37e1a249e0 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -15,6 +15,7 @@ """Execute Transactions Spec tests.""" from __future__ import annotations +import asyncio import sys from io import BytesIO from test.utils_spec_runner import SpecRunner @@ -460,6 +461,17 @@ def callback2(session): with self.client.start_session() as s: self.assertEqual(s.with_transaction(callback2), "Foo") + @client_context.require_transactions + @client_context.require_async + def test_callback_awaitable_no_coroutine(self): + def callback(_): + future = asyncio.Future() + future.set_result("Foo") + return future + + with self.client.start_session() as s: + self.assertEqual(s.with_transaction(callback), "Foo") + @client_context.require_transactions def test_callback_not_retried_after_timeout(self): listener = OvertCommandListener() From 5faf3347f4319e7664feb215088b47d2a5afb4d6 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 20 Oct 2025 15:57:26 -0400 Subject: [PATCH 4/4] Update changelog --- doc/changelog.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index f3eb4f6f23..d381e8dc22 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -17,6 +17,22 @@ PyMongo 4.16 brings a number of changes including: - Removed support for Eventlet. Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. +Changes in Version 4.15.4 (2025/10/21) +-------------------------------------- + +Version 4.15.4 is a bug fix release. + +- Relaxed the callback type of :meth:`~pymongo.asynchronous.client_session.AsyncClientSession.with_transaction` to allow the broader Awaitable type rather than only Coroutine objects. +- Added the missing Python 3.14 trove classifier to the package metadata. + +Issues Resolved +............... + +See the `PyMongo 4.15.4 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 4.15.4 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=47237 + Changes in Version 4.15.3 (2025/10/07) --------------------------------------