.. 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.
Added *loop_factory* parameter.
+ .. versionchanged:: 3.14
+
+ *coro* can be any awaitable object.
+
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.
import contextvars
import enum
import functools
+import inspect
import threading
import signal
from . import coroutines
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(
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()
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):
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()
--- /dev/null
+Allow :meth:`asyncio.Runner.run` to accept :term:`awaitable`
+objects instead of simply :term:`coroutine`\s.