mirror of https://github.com/python/cpython
gh-120284: Enhance `asyncio.run` to accept awaitable objects (#120566)
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
parent
46f5cbca4c
commit
1229cb8c14
|
@ -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.
|
||||
|
||||
Return the coroutine's result or raise its exception.
|
||||
The argument can be any awaitable object.
|
||||
|
||||
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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.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()
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Allow :meth:`asyncio.Runner.run` to accept :term:`awaitable`
|
||||
objects instead of simply :term:`coroutine`\s.
|
Loading…
Reference in New Issue