bpo-32314: Implement asyncio.run() (#4852)

This commit is contained in:
Yury Selivanov 2017-12-14 09:42:21 -05:00 committed by GitHub
parent eadad1b97f
commit 02a0a19206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 173 additions and 9 deletions

View File

@ -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::

View File

@ -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__ +

48
Lib/asyncio/runners.py Normal file
View File

@ -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()

View File

@ -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())

View File

@ -0,0 +1 @@
Implement asyncio.run().