FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

bpo-32314: Implement asyncio.run() by 1st1 · Pull Request #4852 · python/cpython · GitHub

/ cpython Public
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (3) .rst  (2) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
31 changes: 22 additions & 9 deletions Doc/library/asyncio-task.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -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`.


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

Example: Hello World coroutine
Expand All @@ -104,10 +122,7 @@ Example of coroutine displaying ``"Hello World"``::
async def hello_world():
print("Hello World!")

loop = asyncio.get_event_loop()
# Blocking call which returns when the hello_world() coroutine is done
loop.run_until_complete(hello_world())
loop.close()
asyncio.run(hello_world())

.. seealso::

Expand All @@ -127,18 +142,16 @@ using the :meth:`sleep` function::
import asyncio
import datetime

async def display_date(loop):
async def display_date():
loop = asyncio.get_running_loop()
end_time = loop.time() + 5.0
while True:
print(datetime.datetime.now())
if (loop.time() + 1.0) >= end_time:
break
await asyncio.sleep(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

this is a bit more concise

         while (loop.time() + 1.0) < end_time:
              print(datetime.datetime.now())
              await asyncio.sleep(1)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We can replace this while loop with for _ in range(5) and the example will only become clearer.

We'll have a separate pass over asyncio docs before 3.7 is released. We'll try to come up with better examples and improve the current ones. So for now, I'd keep this snippet as is.


loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(display_date(loop))
loop.close()
asyncio.run(display_date())

.. seealso::

Expand Down
2 changes: 2 additions & 0 deletions Lib/asyncio/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .futures import *
from .locks import *
from .protocols import *
from .runners import *
from .queues import *
from .streams import *
from .subprocess import *
Expand All @@ -23,6 +24,7 @@
futures.__all__ +
locks.__all__ +
protocols.__all__ +
runners.__all__ +
queues.__all__ +
streams.__all__ +
subprocess.__all__ +
Expand Down
48 changes: 48 additions & 0 deletions Lib/asyncio/runners.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

set_event_loop() cannot fail (it has a couple asserts but we can neglect this fact).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Sure it can. It calls policy.set_event_loop(), and given the fact that users can provide their own buggy policies, we can't really say that set_event_loop is 100% safe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It's a flag of broken third party loop implementation, not user code problem.
Buggy loop can do weird things, I expect asyncio will be broken somehow anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

What's your point?

The point of this code is to always close the loop, no matter what. What benefit is there in moving set_event_loop() one line up?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Anyways, if you think that the code will read better if set_event_loop is outside of the try block I can move it. We call set_event_loop in the finally block along with the loop.close(), so as you say: if these things are broken then nothing will actually work anyways.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Well, it is very minor thing.
From my perspective wrapping loop.run_until_complete(main) only increases code readability a little but I can live with current implementation too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'll merge as-is then.

loop.set_debug(debug)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Never fails

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Again, it can, if, say, I have a bug in uvloop.Loop.set_debug method. I prefer to assume nothing, when it's possible to set custom policies and custom event loops.

return loop.run_until_complete(main)
finally:
try:
loop.run_until_complete(loop.shutdown_asyncgens())
finally:
events.set_event_loop(None)
loop.close()
100 changes: 100 additions & 0 deletions Lib/test/test_asyncio/test_runners.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -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())
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement asyncio.run().

Back | FazBrowse Home | New Git URL