-
-
Notifications
You must be signed in to change notification settings - Fork 34.4k
bpo-32314: Implement asyncio.run() #4852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure it can. It calls
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a flag of broken third party loop implementation, not user code problem.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's your point? The point of this code is to always close the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyways, if you think that the code will read better if
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it is very minor thing.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll merge as-is then. |
||
| loop.set_debug(debug) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never fails
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, it can, if, say, I have a bug in |
||
| return loop.run_until_complete(main) | ||
| finally: | ||
| try: | ||
| loop.run_until_complete(loop.shutdown_asyncgens()) | ||
| finally: | ||
| events.set_event_loop(None) | ||
| loop.close() | ||
| 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()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Implement asyncio.run(). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a bit more concise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.