bpo-32314: Implement asyncio.run() (#4852)
This commit is contained in:
parent
eadad1b97f
commit
02a0a19206
|
@ -92,6 +92,24 @@ Coroutines (and tasks) can only run when the event loop is running.
|
||||||
used in a callback-style code, wrap its result with :func:`ensure_future`.
|
used in a callback-style code, wrap its result with :func:`ensure_future`.
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: asyncio.run(coro, \*, debug=False)
|
||||||
|
|
||||||
|
This function runs the passed coroutine, taking care of
|
||||||
|
managing the asyncio event loop and finalizing asynchronous
|
||||||
|
generators.
|
||||||
|
|
||||||
|
This function cannot be called when another asyncio event loop is
|
||||||
|
running in the same thread.
|
||||||
|
|
||||||
|
If debug is True, the event loop will be run in debug mode.
|
||||||
|
|
||||||
|
This function always creates a new event loop and closes it at
|
||||||
|
the end. It should be used as a main entry point for asyncio
|
||||||
|
programs, and should ideally only be called once.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
|
|
||||||
.. _asyncio-hello-world-coroutine:
|
.. _asyncio-hello-world-coroutine:
|
||||||
|
|
||||||
Example: Hello World coroutine
|
Example: Hello World coroutine
|
||||||
|
@ -104,10 +122,7 @@ Example of coroutine displaying ``"Hello World"``::
|
||||||
async def hello_world():
|
async def hello_world():
|
||||||
print("Hello World!")
|
print("Hello World!")
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
asyncio.run(hello_world())
|
||||||
# Blocking call which returns when the hello_world() coroutine is done
|
|
||||||
loop.run_until_complete(hello_world())
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
@ -127,7 +142,8 @@ using the :meth:`sleep` function::
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
async def display_date(loop):
|
async def display_date():
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
end_time = loop.time() + 5.0
|
end_time = loop.time() + 5.0
|
||||||
while True:
|
while True:
|
||||||
print(datetime.datetime.now())
|
print(datetime.datetime.now())
|
||||||
|
@ -135,10 +151,7 @@ using the :meth:`sleep` function::
|
||||||
break
|
break
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
asyncio.run(display_date())
|
||||||
# Blocking call which returns when the display_date() coroutine is done
|
|
||||||
loop.run_until_complete(display_date(loop))
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from .events import *
|
||||||
from .futures import *
|
from .futures import *
|
||||||
from .locks import *
|
from .locks import *
|
||||||
from .protocols import *
|
from .protocols import *
|
||||||
|
from .runners import *
|
||||||
from .queues import *
|
from .queues import *
|
||||||
from .streams import *
|
from .streams import *
|
||||||
from .subprocess import *
|
from .subprocess import *
|
||||||
|
@ -23,6 +24,7 @@ __all__ = (base_events.__all__ +
|
||||||
futures.__all__ +
|
futures.__all__ +
|
||||||
locks.__all__ +
|
locks.__all__ +
|
||||||
protocols.__all__ +
|
protocols.__all__ +
|
||||||
|
runners.__all__ +
|
||||||
queues.__all__ +
|
queues.__all__ +
|
||||||
streams.__all__ +
|
streams.__all__ +
|
||||||
subprocess.__all__ +
|
subprocess.__all__ +
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
__all__ = 'run',
|
||||||
|
|
||||||
|
from . import coroutines
|
||||||
|
from . import events
|
||||||
|
|
||||||
|
|
||||||
|
def run(main, *, debug=False):
|
||||||
|
"""Run a coroutine.
|
||||||
|
|
||||||
|
This function runs the passed coroutine, taking care of
|
||||||
|
managing the asyncio event loop and finalizing asynchronous
|
||||||
|
generators.
|
||||||
|
|
||||||
|
This function cannot be called when another asyncio event loop is
|
||||||
|
running in the same thread.
|
||||||
|
|
||||||
|
If debug is True, the event loop will be run in debug mode.
|
||||||
|
|
||||||
|
This function always creates a new event loop and closes it at the end.
|
||||||
|
It should be used as a main entry point for asyncio programs, and should
|
||||||
|
ideally only be called once.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
print('hello')
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
"""
|
||||||
|
if events._get_running_loop() is not None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"asyncio.run() cannot be called from a running event loop")
|
||||||
|
|
||||||
|
if not coroutines.iscoroutine(main):
|
||||||
|
raise ValueError("a coroutine was expected, got {!r}".format(main))
|
||||||
|
|
||||||
|
loop = events.new_event_loop()
|
||||||
|
try:
|
||||||
|
events.set_event_loop(loop)
|
||||||
|
loop.set_debug(debug)
|
||||||
|
return loop.run_until_complete(main)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||||
|
finally:
|
||||||
|
events.set_event_loop(None)
|
||||||
|
loop.close()
|
|
@ -0,0 +1,100 @@
|
||||||
|
import asyncio
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
class TestPolicy(asyncio.AbstractEventLoopPolicy):
|
||||||
|
|
||||||
|
def __init__(self, loop_factory):
|
||||||
|
self.loop_factory = loop_factory
|
||||||
|
self.loop = None
|
||||||
|
|
||||||
|
def get_event_loop(self):
|
||||||
|
# shouldn't ever be called by asyncio.run()
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
def new_event_loop(self):
|
||||||
|
return self.loop_factory()
|
||||||
|
|
||||||
|
def set_event_loop(self, loop):
|
||||||
|
if loop is not None:
|
||||||
|
# we want to check if the loop is closed
|
||||||
|
# in BaseTest.tearDown
|
||||||
|
self.loop = loop
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def new_loop(self):
|
||||||
|
loop = asyncio.BaseEventLoop()
|
||||||
|
loop._process_events = mock.Mock()
|
||||||
|
loop._selector = mock.Mock()
|
||||||
|
loop._selector.select.return_value = ()
|
||||||
|
loop.shutdown_ag_run = False
|
||||||
|
|
||||||
|
async def shutdown_asyncgens():
|
||||||
|
loop.shutdown_ag_run = True
|
||||||
|
loop.shutdown_asyncgens = shutdown_asyncgens
|
||||||
|
|
||||||
|
return loop
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
policy = TestPolicy(self.new_loop)
|
||||||
|
asyncio.set_event_loop_policy(policy)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
policy = asyncio.get_event_loop_policy()
|
||||||
|
if policy.loop is not None:
|
||||||
|
self.assertTrue(policy.loop.is_closed())
|
||||||
|
self.assertTrue(policy.loop.shutdown_ag_run)
|
||||||
|
|
||||||
|
asyncio.set_event_loop_policy(None)
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
|
||||||
|
class RunTests(BaseTest):
|
||||||
|
|
||||||
|
def test_asyncio_run_return(self):
|
||||||
|
async def main():
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
return 42
|
||||||
|
|
||||||
|
self.assertEqual(asyncio.run(main()), 42)
|
||||||
|
|
||||||
|
def test_asyncio_run_raises(self):
|
||||||
|
async def main():
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
raise ValueError('spam')
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, 'spam'):
|
||||||
|
asyncio.run(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'):
|
||||||
|
asyncio.run(o)
|
||||||
|
|
||||||
|
def test_asyncio_run_debug(self):
|
||||||
|
async def main(expected):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
self.assertIs(loop.get_debug(), expected)
|
||||||
|
|
||||||
|
asyncio.run(main(False))
|
||||||
|
asyncio.run(main(True), debug=True)
|
||||||
|
|
||||||
|
def test_asyncio_run_from_running_loop(self):
|
||||||
|
async def main():
|
||||||
|
coro = main()
|
||||||
|
try:
|
||||||
|
asyncio.run(coro)
|
||||||
|
finally:
|
||||||
|
coro.close() # Suppress ResourceWarning
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(RuntimeError,
|
||||||
|
'cannot be called from a running'):
|
||||||
|
asyncio.run(main())
|
|
@ -0,0 +1 @@
|
||||||
|
Implement asyncio.run().
|
Loading…
Reference in New Issue