bpo-34790: Remove passing coroutine objects to asyncio.wait() (GH-31964)

Co-authored-by: Yury Selivanov <yury@edgedb.com>
This commit is contained in:
Andrew Svetlov 2022-03-17 22:51:40 +02:00 committed by GitHub
parent 33698e8ff4
commit 903f0a02c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 15 additions and 101 deletions

View File

@ -534,7 +534,7 @@ Waiting Primitives
.. coroutinefunction:: wait(aws, *, timeout=None, return_when=ALL_COMPLETED) .. coroutinefunction:: wait(aws, *, timeout=None, return_when=ALL_COMPLETED)
Run :ref:`awaitable objects <asyncio-awaitables>` in the *aws* Run :class:`~asyncio.Future` and :class:`~asyncio.Task` instances in the *aws*
iterable concurrently and block until the condition specified iterable concurrently and block until the condition specified
by *return_when*. by *return_when*.
@ -577,51 +577,11 @@ Waiting Primitives
Unlike :func:`~asyncio.wait_for`, ``wait()`` does not cancel the Unlike :func:`~asyncio.wait_for`, ``wait()`` does not cancel the
futures when a timeout occurs. 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 <asyncio_example_wait_coroutine>`.
.. 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 .. versionchanged:: 3.10
Removed the *loop* parameter. Removed the *loop* parameter.
.. versionchanged:: 3.11
Passing coroutine objects to ``wait()`` directly is forbidden.
.. function:: as_completed(aws, *, timeout=None) .. function:: as_completed(aws, *, timeout=None)

View File

@ -387,7 +387,7 @@ ALL_COMPLETED = concurrent.futures.ALL_COMPLETED
async def wait(fs, *, timeout=None, return_when=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. 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): if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}") raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs: 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): if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}') raise ValueError(f'Invalid return_when value: {return_when}')
loop = events.get_running_loop()
fs = set(fs) fs = set(fs)
if any(coroutines.iscoroutine(f) for f in fs): if any(coroutines.iscoroutine(f) for f in fs):
warnings.warn("The explicit passing of coroutine objects to " raise TypeError("Passing coroutines is forbidden, use tasks explicitly.")
"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}
loop = events.get_running_loop()
return await _wait(fs, timeout, return_when, loop) return await _wait(fs, timeout, return_when, loop)

View File

@ -997,13 +997,12 @@ class BaseTaskTests:
async def coro(s): async def coro(s):
return s return s
c = coro('test') c = self.loop.create_task(coro('test'))
task = self.new_task( task = self.new_task(
self.loop, 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.assertFalse(pending)
self.assertEqual(set(f.result() for f in done), {'test', 'spam'}) self.assertEqual(set(f.result() for f in done), {'test', 'spam'})
@ -1380,11 +1379,9 @@ class BaseTaskTests:
async def test(): async def test():
futs = list(asyncio.as_completed(fs)) futs = list(asyncio.as_completed(fs))
self.assertEqual(len(futs), 2) self.assertEqual(len(futs), 2)
waiter = asyncio.wait(futs) done, pending = await asyncio.wait(
# Deprecation from passing coros in futs to asyncio.wait() [asyncio.ensure_future(fut) for fut in futs]
with self.assertWarns(DeprecationWarning) as cm: )
done, pending = await waiter
self.assertEqual(cm.warnings[0].filename, __file__)
self.assertEqual(set(f.result() for f in done), {'a', 'b'}) self.assertEqual(set(f.result() for f in done), {'a', 'b'})
loop = self.new_test_loop(gen) loop = self.new_test_loop(gen)
@ -1434,21 +1431,6 @@ class BaseTaskTests:
loop.run_until_complete(test()) 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 test_sleep(self):
def gen(): def gen():
@ -1751,7 +1733,7 @@ class BaseTaskTests:
async def outer(): async def outer():
nonlocal proof nonlocal proof
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
d, p = await asyncio.wait([inner()]) d, p = await asyncio.wait([asyncio.create_task(inner())])
proof += 100 proof += 100
f = asyncio.ensure_future(outer(), loop=self.loop) f = asyncio.ensure_future(outer(), loop=self.loop)
@ -3220,29 +3202,6 @@ class SleepTests(test_utils.TestCase):
self.assertEqual(result, 11) 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): class CompatibilityTests(test_utils.TestCase):
# Tests for checking a bridge between old-styled coroutines # Tests for checking a bridge between old-styled coroutines
# and async/await syntax # and async/await syntax

View File

@ -0,0 +1 @@
Remove passing coroutine objects to :func:`asyncio.wait`.