https://github.com/python/cpython/commit/fd6a602d0438b42fa5f83d7cccc2b6d99cff8ad1
commit: fd6a602d0438b42fa5f83d7cccc2b6d99cff8ad1
branch: 3.13
author: Thomas Grainger <tagr...@gmail.com>
committer: gvanrossum <gvanros...@gmail.com>
date: 2025-05-18T06:50:07-07:00
summary:

[3.13] gh-133745: Fix asyncio task factory name/context kwarg breaks (#133948)

In 3.13.3 we accidentally broke the interface for custom task factory. Factory 
authors added workarounds.
This PR (for 3.13.4) unbreaks task factories that haven't made a workaround yet 
while also supporting those that have.

NOTE: The custom task factory API will change to what we accidentally released 
in 3.13.3.

Co-authored-by: Kumar Aditya <kumaradi...@python.org>
Co-authored-by: Guido van Rossum <gvanros...@gmail.com>

files:
A Misc/NEWS.d/next/Library/2025-05-14-08-13-08.gh-issue-133745.rjgJkH.rst
M Doc/library/asyncio-eventloop.rst
M Lib/asyncio/base_events.py
M Lib/test/test_asyncio/test_taskgroups.py

diff --git a/Doc/library/asyncio-eventloop.rst 
b/Doc/library/asyncio-eventloop.rst
index 92b98e2b2c862c..20b1ccd105109e 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -355,7 +355,7 @@ Creating Futures and Tasks
 
    .. versionadded:: 3.5.2
 
-.. method:: loop.create_task(coro, *, name=None, context=None)
+.. method:: loop.create_task(coro, *, name=None, context=None, **kwargs)
 
    Schedule the execution of :ref:`coroutine <coroutine>` *coro*.
    Return a :class:`Task` object.
@@ -364,6 +364,11 @@ Creating Futures and Tasks
    for interoperability. In this case, the result type is a subclass
    of :class:`Task`.
 
+   The full function signature is largely the same as that of the
+   :class:`Task` constructor (or factory) - all of the keyword arguments to
+   this function are passed through to that interface, except *name*,
+   or *context* if it is ``None``.
+
    If the *name* argument is provided and not ``None``, it is set as
    the name of the task using :meth:`Task.set_name`.
 
@@ -377,6 +382,13 @@ Creating Futures and Tasks
    .. versionchanged:: 3.11
       Added the *context* parameter.
 
+   .. versionchanged:: 3.13.3
+      Added ``kwargs`` which passes on arbitrary extra parameters, including  
``name`` and ``context``.
+
+   .. versionchanged:: 3.13.4
+      Rolled back the change that passes on *name* and *context* (if it is 
None),
+      while still passing on other arbitrary keyword arguments (to avoid 
breaking backwards compatibility with 3.13.3).
+
 .. method:: loop.set_task_factory(factory)
 
    Set a task factory that will be used by
@@ -388,6 +400,13 @@ Creating Futures and Tasks
    event loop, and *coro* is a coroutine object.  The callable
    must pass on all *kwargs*, and return a :class:`asyncio.Task`-compatible 
object.
 
+   .. versionchanged:: 3.13.3
+      Required that all *kwargs* are passed on to :class:`asyncio.Task`.
+
+   .. versionchanged:: 3.13.4
+      *name* is no longer passed to task factories. *context* is no longer 
passed
+      to task factories if it is ``None``.
+
 .. method:: loop.get_task_factory()
 
    Return a task factory or ``None`` if the default one is in use.
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index d2cedb68985853..07ec6de6414ec2 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -458,18 +458,24 @@ def create_future(self):
         """Create a Future object attached to the loop."""
         return futures.Future(loop=self)
 
-    def create_task(self, coro, **kwargs):
+    def create_task(self, coro, *, name=None, context=None, **kwargs):
         """Schedule a coroutine object.
 
         Return a task object.
         """
         self._check_closed()
         if self._task_factory is not None:
-            return self._task_factory(self, coro, **kwargs)
+            if context is not None:
+                kwargs["context"] = context
+
+            task = self._task_factory(self, coro, **kwargs)
+            task.set_name(name)
+
+        else:
+            task = tasks.Task(coro, loop=self, name=name, context=context, 
**kwargs)
+            if task._source_traceback:
+                del task._source_traceback[-1]
 
-        task = tasks.Task(coro, loop=self, **kwargs)
-        if task._source_traceback:
-            del task._source_traceback[-1]
         try:
             return task
         finally:
diff --git a/Lib/test/test_asyncio/test_taskgroups.py 
b/Lib/test/test_asyncio/test_taskgroups.py
index 9f2211e3232e54..ad61cb46c7c07c 100644
--- a/Lib/test/test_asyncio/test_taskgroups.py
+++ b/Lib/test/test_asyncio/test_taskgroups.py
@@ -1081,18 +1081,6 @@ async def throw_error():
         # cancellation happens here and error is more understandable
         await asyncio.sleep(0)
 
-    async def test_name(self):
-        name = None
-
-        async def asyncfn():
-            nonlocal name
-            name = asyncio.current_task().get_name()
-
-        async with asyncio.TaskGroup() as tg:
-            tg.create_task(asyncfn(), name="example name")
-
-        self.assertEqual(name, "example name")
-
 
 class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
     loop_factory = asyncio.EventLoop
diff --git 
a/Misc/NEWS.d/next/Library/2025-05-14-08-13-08.gh-issue-133745.rjgJkH.rst 
b/Misc/NEWS.d/next/Library/2025-05-14-08-13-08.gh-issue-133745.rjgJkH.rst
new file mode 100644
index 00000000000000..78cc3d9cfa0ee7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-14-08-13-08.gh-issue-133745.rjgJkH.rst
@@ -0,0 +1 @@
+In 3.13.3 we accidentally changed the signature of the asyncio 
``create_task()`` family of methods and how it calls a custom task factory in a 
backwards incompatible way. Since some 3rd party libraries have already made 
changes to work around the issue that might break if we simply reverted the 
changes, we're instead changing things to be backwards compatible with 3.13.2 
while still supporting those workarounds for 3.13.3. In particular, the 
special-casing of ``name`` and ``context`` is back (until 3.14) and 
consequently eager tasks may still find that their name hasn't been set before 
they execute their first yielding await.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to