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)
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
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 <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
Removed the *loop* parameter.
.. versionchanged:: 3.11
Passing coroutine objects to ``wait()`` directly is forbidden.
.. 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):
"""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)

View File

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

View File

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