summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/asyncio-runner.rst30
-rw-r--r--Lib/asyncio/runners.py17
-rw-r--r--Lib/test/test_asyncio/test_runners.py29
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-15-23-38-36.gh-issue-120284.HwsAtY.rst2
4 files changed, 56 insertions, 22 deletions
diff --git a/Doc/library/asyncio-runner.rst b/Doc/library/asyncio-runner.rst
index 8312e55..28d5aaf 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 1b89236..0e63c34 100644
--- a/Lib/asyncio/runners.py
+++ b/Lib/asyncio/runners.py
@@ -3,6 +3,7 @@ __all__ = ('Runner', 'run')
import contextvars
import enum
import functools
+import inspect
import threading
import signal
from . import coroutines
@@ -84,10 +85,7 @@ class Runner:
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 @@ class Runner:
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 266f057..45f70d0 100644
--- a/Lib/test/test_asyncio/test_runners.py
+++ b/Lib/test/test_asyncio/test_runners.py
@@ -93,8 +93,8 @@ class RunTests(BaseTest):
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 @@ class RunnerTests(BaseTest):
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 0000000..a2a6883
--- /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.