From 4e037da66de479c867f193e992831b0c87e51e38 Mon Sep 17 00:00:00 2001 From: Illia Volochii Date: Tue, 25 May 2021 23:33:45 +0300 Subject: [PATCH] bpo-43216: Remove @asyncio.coroutine --- Doc/library/asyncio-task.rst | 70 --- Doc/library/collections.abc.rst | 4 +- Doc/reference/datamodel.rst | 2 +- Doc/whatsnew/3.11.rst | 4 + Lib/asyncio/coroutines.py | 63 +-- Lib/test/test_asyncio/test_base_events.py | 6 +- Lib/test/test_asyncio/test_events.py | 4 +- Lib/test/test_asyncio/test_locks.py | 38 +- Lib/test/test_asyncio/test_pep492.py | 14 - Lib/test/test_asyncio/test_tasks.py | 488 ++---------------- .../2021-05-25-23-26-38.bpo-43216.xTUyyX.rst | 3 + 11 files changed, 86 insertions(+), 610 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 69e965cfc1d2d3..caccb653aff755 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -138,19 +138,6 @@ other coroutines:: asyncio.run(main()) -.. important:: - - In this documentation the term "coroutine" can be used for - two closely related concepts: - - * a *coroutine function*: an :keyword:`async def` function; - - * a *coroutine object*: an object returned by calling a - *coroutine function*. - -asyncio also supports legacy :ref:`generator-based -` coroutines. - .. rubric:: Tasks @@ -973,60 +960,3 @@ Task Object in the :func:`repr` output of a task object. .. versionadded:: 3.8 - - -.. _asyncio_generator_based_coro: - -Generator-based Coroutines -========================== - -.. note:: - - Support for generator-based coroutines is **deprecated** and - is scheduled for removal in Python 3.10. - -Generator-based coroutines predate async/await syntax. They are -Python generators that use ``yield from`` expressions to await -on Futures and other coroutines. - -Generator-based coroutines should be decorated with -:func:`@asyncio.coroutine `, although this is not -enforced. - - -.. decorator:: coroutine - - Decorator to mark generator-based coroutines. - - This decorator enables legacy generator-based coroutines to be - compatible with async/await code:: - - @asyncio.coroutine - def old_style_coroutine(): - yield from asyncio.sleep(1) - - async def main(): - await old_style_coroutine() - - This decorator should not be used for :keyword:`async def` - coroutines. - - .. deprecated-removed:: 3.8 3.10 - - Use :keyword:`async def` instead. - -.. function:: iscoroutine(obj) - - Return ``True`` if *obj* is a :ref:`coroutine object `. - - This method is different from :func:`inspect.iscoroutine` because - it returns ``True`` for generator-based coroutines. - -.. function:: iscoroutinefunction(func) - - Return ``True`` if *func* is a :ref:`coroutine function - `. - - This method is different from :func:`inspect.iscoroutinefunction` - because it returns ``True`` for generator-based coroutine functions - decorated with :func:`@coroutine `. diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index 2345e78a17e4f5..924d0b58d952fd 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -198,7 +198,7 @@ ABC Inherits from Abstract Methods Mixin .. note:: In CPython, generator-based coroutines (generators decorated with - :func:`types.coroutine` or :func:`asyncio.coroutine`) are + :func:`types.coroutine`) are *awaitables*, even though they do not have an :meth:`__await__` method. Using ``isinstance(gencoro, Awaitable)`` for them will return ``False``. Use :func:`inspect.isawaitable` to detect them. @@ -216,7 +216,7 @@ ABC Inherits from Abstract Methods Mixin .. note:: In CPython, generator-based coroutines (generators decorated with - :func:`types.coroutine` or :func:`asyncio.coroutine`) are + :func:`types.coroutine`) are *awaitables*, even though they do not have an :meth:`__await__` method. Using ``isinstance(gencoro, Coroutine)`` for them will return ``False``. Use :func:`inspect.isawaitable` to detect them. diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index eefdc3d5100b56..2f8ed70e53c735 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2714,7 +2714,7 @@ are awaitable. .. note:: The :term:`generator iterator` objects returned from generators - decorated with :func:`types.coroutine` or :func:`asyncio.coroutine` + decorated with :func:`types.coroutine` are also awaitable, but they do not implement :meth:`__await__`. .. method:: object.__await__(self) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 9058b261560873..1b14d5adf1537c 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -126,6 +126,10 @@ Deprecated Removed ======= +* The :func:`asyncio.coroutine` :term:`decorator` enabling legacy + generator-based coroutines to be compatible with async/await code. + Use :keyword:`async def` instead. + (Contributed by Illia Volochii in :issue:`43216`.) Porting to Python 3.11 diff --git a/Lib/asyncio/coroutines.py b/Lib/asyncio/coroutines.py index 9664ea74d75147..3aeed2f4d96403 100644 --- a/Lib/asyncio/coroutines.py +++ b/Lib/asyncio/coroutines.py @@ -1,15 +1,12 @@ -__all__ = 'coroutine', 'iscoroutinefunction', 'iscoroutine' +__all__ = 'iscoroutinefunction', 'iscoroutine' import collections.abc -import functools import inspect import os import sys import traceback import types -import warnings -from . import base_futures from . import constants from . import format_helpers from .log import logger @@ -30,9 +27,6 @@ def _is_debug_mode(): bool(os.environ.get('PYTHONASYNCIODEBUG'))) -_DEBUG = _is_debug_mode() - - class CoroWrapper: # Wrapper for coroutine object in _DEBUG mode. @@ -102,61 +96,6 @@ def __del__(self): logger.error(msg) -def coroutine(func): - """Decorator to mark coroutines. - - If the coroutine is not yielded from before it is destroyed, - an error message is logged. - """ - warnings.warn('"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead', - DeprecationWarning, - stacklevel=2) - if inspect.iscoroutinefunction(func): - # In Python 3.5 that's all we need to do for coroutines - # defined with "async def". - return func - - if inspect.isgeneratorfunction(func): - coro = func - else: - @functools.wraps(func) - def coro(*args, **kw): - res = func(*args, **kw) - if (base_futures.isfuture(res) or inspect.isgenerator(res) or - isinstance(res, CoroWrapper)): - res = yield from res - else: - # If 'res' is an awaitable, run it. - try: - await_meth = res.__await__ - except AttributeError: - pass - else: - if isinstance(res, collections.abc.Awaitable): - res = yield from await_meth() - return res - - coro = types.coroutine(coro) - if not _DEBUG: - wrapper = coro - else: - @functools.wraps(func) - def wrapper(*args, **kwds): - w = CoroWrapper(coro(*args, **kwds), func=func) - if w._source_traceback: - del w._source_traceback[-1] - # Python < 3.5 does not implement __qualname__ - # on generator objects, so we set it manually. - # We use getattr as some callables (such as - # functools.partial may lack __qualname__). - w.__name__ = getattr(func, '__name__', None) - w.__qualname__ = getattr(func, '__qualname__', None) - return w - - wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction(). - return wrapper - - # A marker for iscoroutinefunction. _is_coroutine = object() diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 5691d4250aca9e..47a9fb98001b4d 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1884,10 +1884,8 @@ def test_accept_connection_exception(self, m_log): MyProto, sock, None, None, mock.ANY, mock.ANY) def test_call_coroutine(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def simple_coroutine(): - pass + async def simple_coroutine(): + pass self.loop.set_debug(True) coro_func = simple_coroutine diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 55fc266cb714b1..e78176997a0591 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -17,6 +17,7 @@ import sys import threading import time +import types import errno import unittest from unittest import mock @@ -2163,8 +2164,7 @@ def test_handle_repr(self): '') # decorated function - with self.assertWarns(DeprecationWarning): - cb = asyncio.coroutine(noop) + cb = types.coroutine(noop) h = asyncio.Handle(cb, (), self.loop) self.assertEqual(repr(h), '' diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 6194cd06176dac..441adeea8f8e07 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -38,14 +38,12 @@ def test_repr(self): def test_lock(self): lock = asyncio.Lock() - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def acquire_lock(): - return (yield from lock) + async def acquire_lock(): + return await lock with self.assertRaisesRegex( TypeError, - "object is not iterable" + "object Lock can't be used in 'await' expression" ): self.loop.run_until_complete(acquire_lock()) @@ -78,18 +76,16 @@ def test_lock_by_with_statement(self): asyncio.BoundedSemaphore(), ] - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def test(lock): - yield from asyncio.sleep(0.01) - self.assertFalse(lock.locked()) - with self.assertRaisesRegex( - TypeError, - "object is not iterable" - ): - with (yield from lock): - pass - self.assertFalse(lock.locked()) + async def test(lock): + await asyncio.sleep(0.01) + self.assertFalse(lock.locked()) + with self.assertRaisesRegex( + TypeError, + r"object \w+ can't be used in 'await' expression" + ): + with await lock: + pass + self.assertFalse(lock.locked()) for primitive in primitives: loop.run_until_complete(test(primitive)) @@ -788,14 +784,12 @@ def test_semaphore(self): sem = asyncio.Semaphore() self.assertEqual(1, sem._value) - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def acquire_lock(): - return (yield from sem) + async def acquire_lock(): + return await sem with self.assertRaisesRegex( TypeError, - "'Semaphore' object is not iterable", + "object Semaphore can't be used in 'await' expression", ): self.loop.run_until_complete(acquire_lock()) diff --git a/Lib/test/test_asyncio/test_pep492.py b/Lib/test/test_asyncio/test_pep492.py index 4bd50f4123e579..f833f788dcb98f 100644 --- a/Lib/test/test_asyncio/test_pep492.py +++ b/Lib/test/test_asyncio/test_pep492.py @@ -123,20 +123,6 @@ def test_iscoroutinefunction(self): async def foo(): pass self.assertTrue(asyncio.iscoroutinefunction(foo)) - def test_function_returning_awaitable(self): - class Awaitable: - def __await__(self): - return ('spam',) - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def func(): - return Awaitable() - - coro = func() - self.assertEqual(coro.send(None), 'spam') - coro.close() - def test_async_def_coroutines(self): async def bar(): return 'spam' diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a9e4cf53566ca9..b3d4c8168ab5ee 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -33,18 +33,6 @@ async def coroutine_function(): pass -@contextlib.contextmanager -def set_coroutine_debug(enabled): - coroutines = asyncio.coroutines - - old_debug = coroutines._DEBUG - try: - coroutines._DEBUG = enabled - yield - finally: - coroutines._DEBUG = old_debug - - def format_coroutine(qualname, state, src, source_traceback, generator=False): if generator: state = '%s' % state @@ -234,43 +222,6 @@ async def test(): self.assertTrue(t.done()) self.assertEqual(t.result(), 'ok') - def test_ensure_future_coroutine_2(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def notmuch(): - return 'ok' - t = asyncio.ensure_future(notmuch(), loop=self.loop) - self.assertIs(t._loop, self.loop) - self.loop.run_until_complete(t) - self.assertTrue(t.done()) - self.assertEqual(t.result(), 'ok') - - a = notmuch() - self.addCleanup(a.close) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - asyncio.ensure_future(a) - self.assertEqual(cm.warnings[0].filename, __file__) - - async def test(): - return asyncio.ensure_future(notmuch()) - t = self.loop.run_until_complete(test()) - self.assertIs(t._loop, self.loop) - self.loop.run_until_complete(t) - self.assertTrue(t.done()) - self.assertEqual(t.result(), 'ok') - - # Deprecated in 3.10 - asyncio.set_event_loop(self.loop) - self.addCleanup(asyncio.set_event_loop, None) - with self.assertWarns(DeprecationWarning) as cm: - t = asyncio.ensure_future(notmuch()) - self.assertEqual(cm.warnings[0].filename, __file__) - self.assertIs(t._loop, self.loop) - self.loop.run_until_complete(t) - self.assertTrue(t.done()) - self.assertEqual(t.result(), 'ok') - def test_ensure_future_future(self): f_orig = self.new_future(self.loop) f_orig.set_result('ko') @@ -318,12 +269,10 @@ class Aw: def __init__(self, coro): self.coro = coro def __await__(self): - return (yield from self.coro) + return self.coro.__await__() - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(): - return 'ok' + async def coro(): + return 'ok' loop = asyncio.new_event_loop() self.set_event_loop(loop) @@ -450,68 +399,6 @@ async def notmuch(): self.assertEqual(t.get_name(), '{6}') self.loop.run_until_complete(t) - def test_task_repr_coro_decorator(self): - self.loop.set_debug(False) - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def notmuch(): - # notmuch() function doesn't use yield from: it will be wrapped by - # @coroutine decorator - return 123 - - # test coroutine function - self.assertEqual(notmuch.__name__, 'notmuch') - self.assertRegex(notmuch.__qualname__, - r'\w+.test_task_repr_coro_decorator' - r'\.\.notmuch') - self.assertEqual(notmuch.__module__, __name__) - - # test coroutine object - gen = notmuch() - # On Python >= 3.5, generators now inherit the name of the - # function, as expected, and have a qualified name (__qualname__ - # attribute). - coro_name = 'notmuch' - coro_qualname = ('BaseTaskTests.test_task_repr_coro_decorator' - '..notmuch') - self.assertEqual(gen.__name__, coro_name) - self.assertEqual(gen.__qualname__, coro_qualname) - - # test repr(CoroWrapper) - if coroutines._DEBUG: - # format the coroutine object - if coroutines._DEBUG: - filename, lineno = test_utils.get_function_source(notmuch) - frame = gen._source_traceback[-1] - coro = ('%s() running, defined at %s:%s, created at %s:%s' - % (coro_qualname, filename, lineno, - frame[0], frame[1])) - else: - code = gen.gi_code - coro = ('%s() running at %s:%s' - % (coro_qualname, code.co_filename, - code.co_firstlineno)) - - self.assertEqual(repr(gen), '' % coro) - - # test pending Task - t = self.new_task(self.loop, gen) - t.add_done_callback(Dummy()) - - # format the coroutine object - if coroutines._DEBUG: - src = '%s:%s' % test_utils.get_function_source(notmuch) - else: - code = gen.gi_code - src = '%s:%s' % (code.co_filename, code.co_firstlineno) - coro = format_coroutine(coro_qualname, 'running', src, - t._source_traceback, - generator=not coroutines._DEBUG) - self.assertEqual(repr(t), - "()]>" % coro) - self.loop.run_until_complete(t) - def test_task_repr_wait_for(self): self.loop.set_debug(False) @@ -527,30 +414,6 @@ async def wait_for(fut): fut.set_result(None) self.loop.run_until_complete(task) - def test_task_repr_partial_corowrapper(self): - # Issue #222: repr(CoroWrapper) must not fail in debug mode if the - # coroutine is a partial function - with set_coroutine_debug(True): - self.loop.set_debug(True) - - async def func(x, y): - await asyncio.sleep(0) - - with self.assertWarns(DeprecationWarning): - partial_func = asyncio.coroutine(functools.partial(func, 1)) - task = self.loop.create_task(partial_func(2)) - - # make warnings quiet - task._log_destroy_pending = False - self.addCleanup(task._coro.close) - - coro_repr = repr(task._coro) - expected = ( - r'\.func at' - ) - self.assertRegex(coro_repr, expected) - def test_task_basics(self): async def outer(): @@ -741,12 +604,10 @@ async def coro(): (asyncio.CancelledError, ('my message',), 2)) def test_cancel_yield(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def task(): - yield - yield - return 12 + async def task(): + await asyncio.sleep(0) + await asyncio.sleep(0) + return 12 t = self.new_task(self.loop, task()) test_utils.run_briefly(self.loop) # start coro @@ -1322,10 +1183,8 @@ async def foo(): def test_wait_duplicate_coroutines(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(s): - return s + async def coro(s): + return s c = coro('test') task = self.new_task( self.loop, @@ -1587,16 +1446,14 @@ def gen(): completed = set() time_shifted = False - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def sleeper(dt, x): - nonlocal time_shifted - yield from asyncio.sleep(dt) - completed.add(x) - if not time_shifted and 'a' in completed and 'b' in completed: - time_shifted = True - loop.advance_time(0.14) - return x + async def sleeper(dt, x): + nonlocal time_shifted + await asyncio.sleep(dt) + completed.add(x) + if not time_shifted and 'a' in completed and 'b' in completed: + time_shifted = True + loop.advance_time(0.14) + return x a = sleeper(0.01, 'a') b = sleeper(0.01, 'b') @@ -1614,10 +1471,6 @@ async def foo(): self.assertTrue('b' in res[:2]) self.assertEqual(res[2], 'c') - # Doing it again should take no time and exercise a different path. - res = loop.run_until_complete(self.new_task(loop, foo())) - self.assertAlmostEqual(0.15, loop.time()) - def test_as_completed_with_timeout(self): def gen(): @@ -1727,19 +1580,15 @@ async def test(): def test_as_completed_duplicate_coroutines(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(s): - return s + async def coro(s): + return s - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def runner(): - result = [] - c = coro('ham') - for f in asyncio.as_completed([c, c, coro('spam')]): - result.append((yield from f)) - return result + async def runner(): + result = [] + c = coro('ham') + for f in asyncio.as_completed([c, c, coro('spam')]): + result.append(await f) + return result fut = self.new_task(self.loop, runner()) self.loop.run_until_complete(fut) @@ -1900,17 +1749,6 @@ async def notmuch(): self.loop.run_until_complete(task), 'ko') - def test_step_result(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def notmuch(): - yield None - yield 1 - return 'ko' - - self.assertRaises( - RuntimeError, self.loop.run_until_complete, notmuch()) - def test_step_result_future(self): # If coroutine returns future, task waits on this future. @@ -1983,53 +1821,15 @@ def fn1(): yield self.assertFalse(asyncio.iscoroutinefunction(fn1)) - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def fn2(): - yield + async def fn2(): + pass self.assertTrue(asyncio.iscoroutinefunction(fn2)) self.assertFalse(asyncio.iscoroutinefunction(mock.Mock())) - def test_yield_vs_yield_from(self): - fut = self.new_future(self.loop) - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def wait_for_future(): - yield fut - - task = wait_for_future() - with self.assertRaises(RuntimeError): - self.loop.run_until_complete(task) - - self.assertFalse(fut.done()) - - def test_yield_vs_yield_from_generator(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(): - yield - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def wait_for_future(): - gen = coro() - try: - yield gen - finally: - gen.close() - - task = wait_for_future() - self.assertRaises( - RuntimeError, - self.loop.run_until_complete, task) - def test_coroutine_non_gen_function(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def func(): - return 'test' + async def func(): + return 'test' self.assertTrue(asyncio.iscoroutinefunction(func)) @@ -2042,10 +1842,8 @@ def func(): def test_coroutine_non_gen_function_return_future(self): fut = self.new_future(self.loop) - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def func(): - return fut + async def func(): + return fut async def coro(): fut.set_result('test') @@ -2053,7 +1851,7 @@ async def coro(): t1 = self.new_task(self.loop, func()) t2 = self.new_task(self.loop, coro()) res = self.loop.run_until_complete(t1) - self.assertEqual(res, 'test') + self.assertEqual(res, fut) self.assertIsNone(t2.result()) def test_current_task(self): @@ -2309,68 +2107,21 @@ def test_wait_invalid_args(self): self.assertRaises(ValueError, self.loop.run_until_complete, asyncio.wait([])) - def test_corowrapper_mocks_generator(self): - - def check(): - # A function that asserts various things. - # Called twice, with different debug flag values. - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(): - # The actual coroutine. - self.assertTrue(gen.gi_running) - yield from fut - - # A completed Future used to run the coroutine. - fut = self.new_future(self.loop) - fut.set_result(None) - - # Call the coroutine. - gen = coro() - - # Check some properties. - self.assertTrue(asyncio.iscoroutine(gen)) - self.assertIsInstance(gen.gi_frame, types.FrameType) - self.assertFalse(gen.gi_running) - self.assertIsInstance(gen.gi_code, types.CodeType) - - # Run it. - self.loop.run_until_complete(gen) - - # The frame should have changed. - self.assertIsNone(gen.gi_frame) + def test_await_corowrapper(self): + async def t1(): + return await t2() - # Test with debug flag cleared. - with set_coroutine_debug(False): - check() + async def t2(): + f = self.new_future(self.loop) + self.new_task(self.loop, t3(f)) + return await f - # Test with debug flag set. - with set_coroutine_debug(True): - check() + async def t3(f): + f.set_result((1, 2, 3)) - def test_yield_from_corowrapper(self): - with set_coroutine_debug(True): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def t1(): - return (yield from t2()) - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def t2(): - f = self.new_future(self.loop) - self.new_task(self.loop, t3(f)) - return (yield from f) - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def t3(f): - f.set_result((1, 2, 3)) - - task = self.new_task(self.loop, t1()) - val = self.loop.run_until_complete(task) - self.assertEqual(val, (1, 2, 3)) + task = self.new_task(self.loop, asyncio.coroutines.CoroWrapper(t1())) + val = self.loop.run_until_complete(task) + self.assertEqual(val, (1, 2, 3)) def test_yield_from_corowrapper_send(self): def foo(): @@ -2431,14 +2182,12 @@ def foo(): def test_log_destroyed_pending_task(self): Task = self.__class__.Task - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def kill_me(loop): - future = self.new_future(loop) - yield from future - # at this point, the only reference to kill_me() task is - # the Task._wakeup() method in future._callbacks - raise Exception("code never reached") + async def kill_me(loop): + future = self.new_future(loop) + await future + # at this point, the only reference to kill_me() task is + # the Task._wakeup() method in future._callbacks + raise Exception("code never reached") mock_handler = mock.Mock() self.loop.set_debug(True) @@ -2456,8 +2205,6 @@ def kill_me(loop): self.loop._run_once() self.assertEqual(len(self.loop._ready), 0) - # remove the future used in kill_me(), and references to the task - del coro.gi_frame.f_locals['future'] coro = None source_traceback = task._source_traceback task = None @@ -2491,62 +2238,6 @@ async def runner(): loop.run_until_complete(runner()) self.assertFalse(m_log.error.called) - @mock.patch('asyncio.coroutines.logger') - def test_coroutine_never_yielded(self, m_log): - with set_coroutine_debug(True): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro_noop(): - pass - - tb_filename = __file__ - tb_lineno = sys._getframe().f_lineno + 2 - # create a coroutine object but don't use it - coro_noop() - support.gc_collect() - - self.assertTrue(m_log.error.called) - message = m_log.error.call_args[0][0] - func_filename, func_lineno = test_utils.get_function_source(coro_noop) - - regex = (r'^ ' - r'was never yielded from\n' - r'Coroutine object created at \(most recent call last, truncated to \d+ last lines\):\n' - r'.*\n' - r' File "%s", line %s, in test_coroutine_never_yielded\n' - r' coro_noop\(\)$' - % (re.escape(coro_noop.__qualname__), - re.escape(func_filename), func_lineno, - re.escape(tb_filename), tb_lineno)) - - self.assertRegex(message, re.compile(regex, re.DOTALL)) - - def test_return_coroutine_from_coroutine(self): - """Return of @asyncio.coroutine()-wrapped function generator object - from @asyncio.coroutine()-wrapped function should have same effect as - returning generator object or Future.""" - def check(): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def outer_coro(): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def inner_coro(): - return 1 - - return inner_coro() - - result = self.loop.run_until_complete(outer_coro()) - self.assertEqual(result, 1) - - # Test with debug flag cleared. - with set_coroutine_debug(False): - check() - - # Test with debug flag set. - with set_coroutine_debug(True): - check() - def test_task_source_traceback(self): self.loop.set_debug(True) @@ -2677,10 +2368,8 @@ def call_soon(callback, *args, **kwargs): raise ValueError self.loop.call_soon = call_soon - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(): - pass + async def coro(): + pass self.assertFalse(m_log.error.called) @@ -2708,23 +2397,6 @@ def test_create_task_with_noncoroutine(self): "a coroutine was expected, got 123"): self.new_task(self.loop, 123) - def test_create_task_with_oldstyle_coroutine(self): - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(): - pass - - task = self.new_task(self.loop, coro()) - self.assertIsInstance(task, self.Task) - self.loop.run_until_complete(task) - - # test it for the second time to ensure that caching - # in asyncio.iscoroutine() doesn't break things. - task = self.new_task(self.loop, coro()) - self.assertIsInstance(task, self.Task) - self.loop.run_until_complete(task) - def test_create_task_with_async_function(self): async def coro(): @@ -3365,7 +3037,7 @@ def test_return_exceptions(self): def test_env_var_debug(self): code = '\n'.join(( 'import asyncio.coroutines', - 'print(asyncio.coroutines._DEBUG)')) + 'print(asyncio.coroutines._is_debug_mode())')) # Test with -E to not fail if the unit test was run with # PYTHONASYNCIODEBUG set to a non-empty string @@ -3542,10 +3214,8 @@ async def coro(): self.other_loop.run_until_complete(fut) def test_duplicate_coroutines(self): - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(s): - return s + async def coro(s): + return s c = coro('abc') fut = self._gather(c, c, coro('def'), c) self._run_loop(self.one_loop) @@ -3771,54 +3441,6 @@ def tearDown(self): self.loop = None super().tearDown() - def test_yield_from_awaitable(self): - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro(): - yield from asyncio.sleep(0) - return 'ok' - - result = self.loop.run_until_complete(coro()) - self.assertEqual('ok', result) - - def test_await_old_style_coro(self): - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro1(): - return 'ok1' - - with self.assertWarns(DeprecationWarning): - @asyncio.coroutine - def coro2(): - yield from asyncio.sleep(0) - return 'ok2' - - async def inner(): - return await asyncio.gather(coro1(), coro2()) - - result = self.loop.run_until_complete(inner()) - self.assertEqual(['ok1', 'ok2'], result) - - def test_debug_mode_interop(self): - # https://bugs.python.org/issue32636 - code = textwrap.dedent(""" - import asyncio - - async def native_coro(): - pass - - @asyncio.coroutine - def old_style_coro(): - yield from native_coro() - - asyncio.run(old_style_coro()) - """) - - assert_python_ok("-Wignore::DeprecationWarning", "-c", code, - PYTHONASYNCIODEBUG="1") - if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst b/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst new file mode 100644 index 00000000000000..e10484dc4c128b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst @@ -0,0 +1,3 @@ +Remove the :func:`asyncio.coroutine` :term:`decorator` enabling legacy +generator-based coroutines to be compatible with async/await code. Patch by +Illia Volochii.