https://github.com/python/cpython/commit/1229cb8c1412d37cf3206eab407f03e21d602cbd
commit: 1229cb8c1412d37cf3206eab407f03e21d602cbd
branch: main
author: Ron Frederick <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2024-09-26T11:45:08+05:30
summary:

gh-120284: Enhance `asyncio.run` to accept awaitable objects (#120566)

Co-authored-by: Kumar Aditya <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-06-15-23-38-36.gh-issue-120284.HwsAtY.rst
M Doc/library/asyncio-runner.rst
M Lib/asyncio/runners.py
M Lib/test/test_asyncio/test_runners.py

diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 8312e55126a7c5..28d5aaf3692baa 100644
--- a/Doc/library/asyncio-runner.rst
+++ b/Doc/library/asyncio-runner.rst
@@ -24,11 +24,13 @@ Running an asyncio Program
 
 .. function:: run(coro, *, debug=None, loop_factory=None)
 
-   Execute the :term:`coroutine` *coro* and return the result.
+   Execute *coro* in an asyncio event loop and return the result.
 
-   This function runs the passed coroutine, taking care of
-   managing the asyncio event loop, *finalizing asynchronous
-   generators*, and closing the executor.
+   The argument can be any awaitable object.
+
+   This function runs the awaitable, taking care of managing the
+   asyncio event loop, *finalizing asynchronous generators*, and
+   closing the executor.
 
    This function cannot be called when another asyncio event loop is
    running in the same thread.
@@ -70,6 +72,10 @@ Running an asyncio Program
 
       Added *loop_factory* parameter.
 
+   .. versionchanged:: 3.14
+
+      *coro* can be any awaitable object.
+
 
 Runner context manager
 ======================
@@ -104,17 +110,25 @@ Runner context manager
 
    .. method:: run(coro, *, context=None)
 
-      Run a :term:`coroutine <coroutine>` *coro* in the embedded loop.
+      Execute *coro* in the embedded event loop.
+
+      The argument can be any awaitable object.
 
-      Return the coroutine's result or raise its exception.
+      If the argument is a coroutine, it is wrapped in a Task.
 
       An optional keyword-only *context* argument allows specifying a
-      custom :class:`contextvars.Context` for the *coro* to run in.
-      The runner's default context is used if ``None``.
+      custom :class:`contextvars.Context` for the code to run in.
+      The runner's default context is used if context is ``None``.
+
+      Returns the awaitable's result or raises an exception.
 
       This function cannot be called when another asyncio event loop is
       running in the same thread.
 
+      .. versionchanged:: 3.14
+
+         *coro* can be any awaitable object.
+
    .. method:: close()
 
       Close the runner.
diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py
index 1b89236599aad7..0e63c34f60f4d9 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -3,6 +3,7 @@
 import contextvars
 import enum
 import functools
+import inspect
 import threading
 import signal
 from . import coroutines
@@ -84,10 +85,7 @@ def get_loop(self):
         return self._loop
 
     def run(self, coro, *, context=None):
-        """Run a coroutine inside the embedded event loop."""
-        if not coroutines.iscoroutine(coro):
-            raise ValueError("a coroutine was expected, got {!r}".format(coro))
-
+        """Run code in the embedded event loop."""
         if events._get_running_loop() is not None:
             # fail fast with short traceback
             raise RuntimeError(
@@ -95,8 +93,19 @@ def run(self, coro, *, context=None):
 
         self._lazy_init()
 
+        if not coroutines.iscoroutine(coro):
+            if inspect.isawaitable(coro):
+                async def _wrap_awaitable(awaitable):
+                    return await awaitable
+
+                coro = _wrap_awaitable(coro)
+            else:
+                raise TypeError('An asyncio.Future, a coroutine or an '
+                                'awaitable is required')
+
         if context is None:
             context = self._context
+
         task = self._loop.create_task(coro, context=context)
 
         if (threading.current_thread() is threading.main_thread()
diff --git a/Lib/test/test_asyncio/test_runners.py 
b/Lib/test/test_asyncio/test_runners.py
index 266f057f0776c3..45f70d09a2083a 100644
--- a/Lib/test/test_asyncio/test_runners.py
+++ b/Lib/test/test_asyncio/test_runners.py
@@ -93,8 +93,8 @@ async def main():
     def test_asyncio_run_only_coro(self):
         for o in {1, lambda: None}:
             with self.subTest(obj=o), \
-                    self.assertRaisesRegex(ValueError,
-                                           'a coroutine was expected'):
+                    self.assertRaisesRegex(TypeError,
+                                           'an awaitable is required'):
                 asyncio.run(o)
 
     def test_asyncio_run_debug(self):
@@ -319,19 +319,28 @@ async def f():
     def test_run_non_coro(self):
         with asyncio.Runner() as runner:
             with self.assertRaisesRegex(
-                ValueError,
-                "a coroutine was expected"
+                TypeError,
+                "an awaitable is required"
             ):
                 runner.run(123)
 
     def test_run_future(self):
         with asyncio.Runner() as runner:
-            with self.assertRaisesRegex(
-                ValueError,
-                "a coroutine was expected"
-            ):
-                fut = runner.get_loop().create_future()
-                runner.run(fut)
+            fut = runner.get_loop().create_future()
+            fut.set_result('done')
+            self.assertEqual('done', runner.run(fut))
+
+    def test_run_awaitable(self):
+        class MyAwaitable:
+            def __await__(self):
+                return self.run().__await__()
+
+            @staticmethod
+            async def run():
+                return 'done'
+
+        with asyncio.Runner() as runner:
+            self.assertEqual('done', runner.run(MyAwaitable()))
 
     def test_explicit_close(self):
         runner = asyncio.Runner()
diff --git 
a/Misc/NEWS.d/next/Library/2024-06-15-23-38-36.gh-issue-120284.HwsAtY.rst 
b/Misc/NEWS.d/next/Library/2024-06-15-23-38-36.gh-issue-120284.HwsAtY.rst
new file mode 100644
index 00000000000000..a2a6883c3d7686
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-15-23-38-36.gh-issue-120284.HwsAtY.rst
@@ -0,0 +1,2 @@
+Allow :meth:`asyncio.Runner.run` to accept :term:`awaitable`
+objects instead of simply :term:`coroutine`\s.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to