From 903f0a02c16240dc769a08c30e8d072a4fb09154 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 17 Mar 2022 22:51:40 +0200 Subject: [PATCH] bpo-34790: Remove passing coroutine objects to asyncio.wait() (GH-31964) Co-authored-by: Yury Selivanov --- Doc/library/asyncio-task.rst | 46 +--------------- Lib/asyncio/tasks.py | 14 ++--- Lib/test/test_asyncio/test_tasks.py | 55 +++---------------- .../2022-03-17-19-38-40.bpo-34790.zQIiVJ.rst | 1 + 4 files changed, 15 insertions(+), 101 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-03-17-19-38-40.bpo-34790.zQIiVJ.rst diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index faf5910124f..294f5ab2b22 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -534,7 +534,7 @@ Waiting Primitives .. coroutinefunction:: wait(aws, *, timeout=None, return_when=ALL_COMPLETED) - Run :ref:`awaitable objects ` in the *aws* + Run :class:`~asyncio.Future` and :class:`~asyncio.Task` instances in the *aws* iterable concurrently and block until the condition specified by *return_when*. @@ -577,51 +577,11 @@ Waiting Primitives Unlike :func:`~asyncio.wait_for`, ``wait()`` does not cancel the futures when a timeout occurs. - .. deprecated:: 3.8 - - If any awaitable in *aws* is a coroutine, it is automatically - scheduled as a Task. Passing coroutines objects to - ``wait()`` directly is deprecated as it leads to - :ref:`confusing behavior `. - - .. versionchanged:: 3.10 - Removed the *loop* parameter. - - .. _asyncio_example_wait_coroutine: - .. note:: - - ``wait()`` schedules coroutines as Tasks automatically and later - returns those implicitly created Task objects in ``(done, pending)`` - sets. Therefore the following code won't work as expected:: - - async def foo(): - return 42 - - coro = foo() - done, pending = await asyncio.wait({coro}) - - if coro in done: - # This branch will never be run! - - Here is how the above snippet can be fixed:: - - async def foo(): - return 42 - - task = asyncio.create_task(foo()) - done, pending = await asyncio.wait({task}) - - if task in done: - # Everything will work as expected now. - - .. deprecated-removed:: 3.8 3.11 - - Passing coroutine objects to ``wait()`` directly is - deprecated. - .. versionchanged:: 3.10 Removed the *loop* parameter. + .. versionchanged:: 3.11 + Passing coroutine objects to ``wait()`` directly is forbidden. .. function:: as_completed(aws, *, timeout=None) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 0b5f3226802..e876f8d002c 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -387,7 +387,7 @@ ALL_COMPLETED = concurrent.futures.ALL_COMPLETED async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED): - """Wait for the Futures and coroutines given by fs to complete. + """Wait for the Futures or Tasks given by fs to complete. The fs iterable must not be empty. @@ -405,22 +405,16 @@ async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED): if futures.isfuture(fs) or coroutines.iscoroutine(fs): raise TypeError(f"expect a list of futures, not {type(fs).__name__}") if not fs: - raise ValueError('Set of coroutines/Futures is empty.') + raise ValueError('Set of Tasks/Futures is empty.') if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED): raise ValueError(f'Invalid return_when value: {return_when}') - loop = events.get_running_loop() - fs = set(fs) if any(coroutines.iscoroutine(f) for f in fs): - warnings.warn("The explicit passing of coroutine objects to " - "asyncio.wait() is deprecated since Python 3.8, and " - "scheduled for removal in Python 3.11.", - DeprecationWarning, stacklevel=2) - - fs = {ensure_future(f, loop=loop) for f in fs} + raise TypeError("Passing coroutines is forbidden, use tasks explicitly.") + loop = events.get_running_loop() return await _wait(fs, timeout, return_when, loop) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 141c019dcb9..b86646ee398 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -997,13 +997,12 @@ class BaseTaskTests: async def coro(s): return s - c = coro('test') + c = self.loop.create_task(coro('test')) task = self.new_task( self.loop, - asyncio.wait([c, c, coro('spam')])) + asyncio.wait([c, c, self.loop.create_task(coro('spam'))])) - with self.assertWarns(DeprecationWarning): - done, pending = self.loop.run_until_complete(task) + done, pending = self.loop.run_until_complete(task) self.assertFalse(pending) self.assertEqual(set(f.result() for f in done), {'test', 'spam'}) @@ -1380,11 +1379,9 @@ class BaseTaskTests: async def test(): futs = list(asyncio.as_completed(fs)) self.assertEqual(len(futs), 2) - waiter = asyncio.wait(futs) - # Deprecation from passing coros in futs to asyncio.wait() - with self.assertWarns(DeprecationWarning) as cm: - done, pending = await waiter - self.assertEqual(cm.warnings[0].filename, __file__) + done, pending = await asyncio.wait( + [asyncio.ensure_future(fut) for fut in futs] + ) self.assertEqual(set(f.result() for f in done), {'a', 'b'}) loop = self.new_test_loop(gen) @@ -1434,21 +1431,6 @@ class BaseTaskTests: loop.run_until_complete(test()) - def test_as_completed_coroutine_use_global_loop(self): - # Deprecated in 3.10 - async def coro(): - return 42 - - loop = self.new_test_loop() - asyncio.set_event_loop(loop) - self.addCleanup(asyncio.set_event_loop, None) - futs = asyncio.as_completed([coro()]) - with self.assertWarns(DeprecationWarning) as cm: - futs = list(futs) - self.assertEqual(cm.warnings[0].filename, __file__) - self.assertEqual(len(futs), 1) - self.assertEqual(loop.run_until_complete(futs[0]), 42) - def test_sleep(self): def gen(): @@ -1751,7 +1733,7 @@ class BaseTaskTests: async def outer(): nonlocal proof with self.assertWarns(DeprecationWarning): - d, p = await asyncio.wait([inner()]) + d, p = await asyncio.wait([asyncio.create_task(inner())]) proof += 100 f = asyncio.ensure_future(outer(), loop=self.loop) @@ -3220,29 +3202,6 @@ class SleepTests(test_utils.TestCase): self.assertEqual(result, 11) -class WaitTests(test_utils.TestCase): - def setUp(self): - super().setUp() - self.loop = asyncio.new_event_loop() - self.set_event_loop(self.loop) - - def tearDown(self): - self.loop.close() - self.loop = None - super().tearDown() - - def test_coro_is_deprecated_in_wait(self): - # Remove test when passing coros to asyncio.wait() is removed in 3.11 - with self.assertWarns(DeprecationWarning): - self.loop.run_until_complete( - asyncio.wait([coroutine_function()])) - - task = self.loop.create_task(coroutine_function()) - with self.assertWarns(DeprecationWarning): - self.loop.run_until_complete( - asyncio.wait([task, coroutine_function()])) - - class CompatibilityTests(test_utils.TestCase): # Tests for checking a bridge between old-styled coroutines # and async/await syntax diff --git a/Misc/NEWS.d/next/Library/2022-03-17-19-38-40.bpo-34790.zQIiVJ.rst b/Misc/NEWS.d/next/Library/2022-03-17-19-38-40.bpo-34790.zQIiVJ.rst new file mode 100644 index 00000000000..50a71b5877f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-03-17-19-38-40.bpo-34790.zQIiVJ.rst @@ -0,0 +1 @@ +Remove passing coroutine objects to :func:`asyncio.wait`.