[Python-checkins] bpo-43216: Remove @asyncio.coroutine (GH-26369)

vstinner webhook-mailer at python.org
Thu Jul 1 09:14:08 EDT 2021


https://github.com/python/cpython/commit/a1092f62492a3fcd6195bea94eccf8d5a300acb1
commit: a1092f62492a3fcd6195bea94eccf8d5a300acb1
branch: main
author: Illia Volochii <illia.volochii at gmail.com>
committer: vstinner <vstinner at python.org>
date: 2021-07-01T15:13:59+02:00
summary:

bpo-43216: Remove @asyncio.coroutine (GH-26369)

Remove the @asyncio.coroutine decorator
enabling legacy generator-based coroutines to be compatible with async/await
code; remove asyncio.coroutines.CoroWrapper used for wrapping
legacy coroutine objects in the debug mode.

The decorator has been deprecated
since Python 3.8 and the removal was initially scheduled for Python 3.10.

files:
A Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst
M Doc/library/asyncio-task.rst
M Doc/library/collections.abc.rst
M Doc/reference/datamodel.rst
M Doc/whatsnew/3.11.rst
M Lib/asyncio/coroutines.py
M Lib/test/test_asyncio/test_base_events.py
M Lib/test/test_asyncio/test_events.py
M Lib/test/test_asyncio/test_locks.py
M Lib/test/test_asyncio/test_pep492.py
M Lib/test/test_asyncio/test_tasks.py

diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index bbdef3345a4d42..44ec3cc8826c02 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -148,9 +148,6 @@ other coroutines::
    * a *coroutine object*: an object returned by calling a
      *coroutine function*.
 
-asyncio also supports legacy :ref:`generator-based
-<asyncio_generator_based_coro>` coroutines.
-
 
 .. rubric:: Tasks
 
@@ -1042,60 +1039,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 <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 <coroutine>`.
-
-   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
-   <coroutine>`.
-
-   This method is different from :func:`inspect.iscoroutinefunction`
-   because it returns ``True`` for generator-based coroutine functions
-   decorated with :func:`@coroutine <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 8e3d5f31754c8f..94d8bef4d521b0 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -171,7 +171,15 @@ Deprecated
 Removed
 =======
 
-
+* The :func:`@asyncio.coroutine <asyncio.coroutine>` :term:`decorator` enabling
+  legacy generator-based coroutines to be compatible with async/await code.
+  The function has been deprecated since Python 3.8 and the removal was
+  initially scheduled for Python 3.10. Use :keyword:`async def` instead.
+  (Contributed by Illia Volochii in :issue:`43216`.)
+
+* :class:`asyncio.coroutines.CoroWrapper` used for wrapping legacy
+  generator-based coroutine objects in the debug mode.
+  (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..0e4b489f30fd7c 100644
--- a/Lib/asyncio/coroutines.py
+++ b/Lib/asyncio/coroutines.py
@@ -1,162 +1,19 @@
-__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
 
 
 def _is_debug_mode():
-    # If you set _DEBUG to true, @coroutine will wrap the resulting
-    # generator objects in a CoroWrapper instance (defined below).  That
-    # instance will log a message when the generator is never iterated
-    # over, which may happen when you forget to use "await" or "yield from"
-    # with a coroutine call.
-    # Note that the value of the _DEBUG flag is taken
-    # when the decorator is used, so to be of any use it must be set
-    # before you define your coroutines.  A downside of using this feature
-    # is that tracebacks show entries for the CoroWrapper.__next__ method
-    # when _DEBUG is true.
+    # See: https://docs.python.org/3/library/asyncio-dev.html#asyncio-debug-mode.
     return sys.flags.dev_mode or (not sys.flags.ignore_environment and
                                   bool(os.environ.get('PYTHONASYNCIODEBUG')))
 
 
-_DEBUG = _is_debug_mode()
-
-
-class CoroWrapper:
-    # Wrapper for coroutine object in _DEBUG mode.
-
-    def __init__(self, gen, func=None):
-        assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
-        self.gen = gen
-        self.func = func  # Used to unwrap @coroutine decorator
-        self._source_traceback = format_helpers.extract_stack(sys._getframe(1))
-        self.__name__ = getattr(gen, '__name__', None)
-        self.__qualname__ = getattr(gen, '__qualname__', None)
-
-    def __repr__(self):
-        coro_repr = _format_coroutine(self)
-        if self._source_traceback:
-            frame = self._source_traceback[-1]
-            coro_repr += f', created at {frame[0]}:{frame[1]}'
-
-        return f'<{self.__class__.__name__} {coro_repr}>'
-
-    def __iter__(self):
-        return self
-
-    def __next__(self):
-        return self.gen.send(None)
-
-    def send(self, value):
-        return self.gen.send(value)
-
-    def throw(self, type, value=None, traceback=None):
-        return self.gen.throw(type, value, traceback)
-
-    def close(self):
-        return self.gen.close()
-
-    @property
-    def gi_frame(self):
-        return self.gen.gi_frame
-
-    @property
-    def gi_running(self):
-        return self.gen.gi_running
-
-    @property
-    def gi_code(self):
-        return self.gen.gi_code
-
-    def __await__(self):
-        return self
-
-    @property
-    def gi_yieldfrom(self):
-        return self.gen.gi_yieldfrom
-
-    def __del__(self):
-        # Be careful accessing self.gen.frame -- self.gen might not exist.
-        gen = getattr(self, 'gen', None)
-        frame = getattr(gen, 'gi_frame', None)
-        if frame is not None and frame.f_lasti == -1:
-            msg = f'{self!r} was never yielded from'
-            tb = getattr(self, '_source_traceback', ())
-            if tb:
-                tb = ''.join(traceback.format_list(tb))
-                msg += (f'\nCoroutine object created at '
-                        f'(most recent call last, truncated to '
-                        f'{constants.DEBUG_STACK_DEPTH} last lines):\n')
-                msg += tb.rstrip()
-            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()
 
@@ -170,7 +27,7 @@ def iscoroutinefunction(func):
 # Prioritize native coroutine check to speed-up
 # asyncio.iscoroutine.
 _COROUTINE_TYPES = (types.CoroutineType, types.GeneratorType,
-                    collections.abc.Coroutine, CoroWrapper)
+                    collections.abc.Coroutine)
 _iscoroutine_typecache = set()
 
 
@@ -193,16 +50,11 @@ def iscoroutine(obj):
 def _format_coroutine(coro):
     assert iscoroutine(coro)
 
-    is_corowrapper = isinstance(coro, CoroWrapper)
-
     def get_name(coro):
         # Coroutines compiled with Cython sometimes don't have
         # proper __qualname__ or __name__.  While that is a bug
         # in Cython, asyncio shouldn't crash with an AttributeError
         # in its __repr__ functions.
-        if is_corowrapper:
-            return format_helpers._format_callback(coro.func, (), {})
-
         if hasattr(coro, '__qualname__') and coro.__qualname__:
             coro_name = coro.__qualname__
         elif hasattr(coro, '__name__') and coro.__name__:
@@ -247,18 +99,8 @@ def is_running(coro):
     filename = coro_code.co_filename or '<empty co_filename>'
 
     lineno = 0
-    if (is_corowrapper and
-            coro.func is not None and
-            not inspect.isgeneratorfunction(coro.func)):
-        source = format_helpers._get_function_source(coro.func)
-        if source is not None:
-            filename, lineno = source
-        if coro_frame is None:
-            coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
-        else:
-            coro_repr = f'{coro_name} running, defined at {filename}:{lineno}'
 
-    elif coro_frame is not None:
+    if coro_frame is not None:
         lineno = coro_frame.f_lineno
         coro_repr = f'{coro_name} running at {filename}:{lineno}'
 
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):
                         '<Handle cancelled>')
 
         # decorated function
-        with self.assertWarns(DeprecationWarning):
-            cb = asyncio.coroutine(noop)
+        cb = types.coroutine(noop)
         h = asyncio.Handle(cb, (), self.loop)
         self.assertEqual(repr(h),
                         '<Handle noop() at %s:%s>'
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..86710872ace024 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
 
 
- at 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'\.<locals>\.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'
-                         '.<locals>.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), '<CoroWrapper %s>' % 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),
-                         "<Task pending name='TestTask' %s cb=[<Dummy>()]>" % 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'<coroutine object \w+\.test_task_repr_partial_corowrapper'
-            r'\.<locals>\.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,136 +2107,15 @@ 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)
-
-        # Test with debug flag cleared.
-        with set_coroutine_debug(False):
-            check()
-
-        # Test with debug flag set.
-        with set_coroutine_debug(True):
-            check()
-
-    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))
-
-    def test_yield_from_corowrapper_send(self):
-        def foo():
-            a = yield
-            return a
-
-        def call(arg):
-            cw = asyncio.coroutines.CoroWrapper(foo())
-            cw.send(None)
-            try:
-                cw.send(arg)
-            except StopIteration as ex:
-                return ex.args[0]
-            else:
-                raise AssertionError('StopIteration was expected')
-
-        self.assertEqual(call((1, 2)), (1, 2))
-        self.assertEqual(call('spam'), 'spam')
-
-    def test_corowrapper_weakref(self):
-        wd = weakref.WeakValueDictionary()
-        def foo(): yield from []
-        cw = asyncio.coroutines.CoroWrapper(foo())
-        wd['cw'] = cw  # Would fail without __weakref__ slot.
-        cw.gen = None  # Suppress warning from __del__.
-
-    def test_corowrapper_throw(self):
-        # Issue 429: CoroWrapper.throw must be compatible with gen.throw
-        def foo():
-            value = None
-            while True:
-                try:
-                    value = yield value
-                except Exception as e:
-                    value = e
-
-        exception = Exception("foo")
-        cw = asyncio.coroutines.CoroWrapper(foo())
-        cw.send(None)
-        self.assertIs(exception, cw.throw(exception))
-
-        cw = asyncio.coroutines.CoroWrapper(foo())
-        cw.send(None)
-        self.assertIs(exception, cw.throw(Exception, exception))
-
-        cw = asyncio.coroutines.CoroWrapper(foo())
-        cw.send(None)
-        exception = cw.throw(Exception, "foo")
-        self.assertIsInstance(exception, Exception)
-        self.assertEqual(exception.args, ("foo", ))
-
-        cw = asyncio.coroutines.CoroWrapper(foo())
-        cw.send(None)
-        exception = cw.throw(Exception, "foo", None)
-        self.assertIsInstance(exception, Exception)
-        self.assertEqual(exception.args, ("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 +2133,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 +2166,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'^<CoroWrapper %s\(?\)? .* at %s:%s, .*> '
-                    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 +2296,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 +2325,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 +2965,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 +3142,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)
@@ -3633,9 +3231,7 @@ def target(self, fail=False, cancel=False, timeout=None,
             # otherwise it spills errors and breaks **other** unittests, since
             # 'target' is interacting with threads.
 
-            # With this call, `coro` will be advanced, so that
-            # CoroWrapper.__del__ won't do anything when asyncio tests run
-            # in debug mode.
+            # With this call, `coro` will be advanced.
             self.loop.call_soon_threadsafe(coro.send, None)
         try:
             return future.result(timeout)
@@ -3771,54 +3367,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..845ef95d1ad296
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst
@@ -0,0 +1,6 @@
+Remove the :func:`@asyncio.coroutine <asyncio.coroutine>` :term:`decorator`
+enabling legacy generator-based coroutines to be compatible with async/await
+code; remove :class:`asyncio.coroutines.CoroWrapper` used for wrapping
+legacy coroutine objects in the debug mode. The decorator has been deprecated
+since Python 3.8 and the removal was initially scheduled for Python 3.10.
+Patch by Illia Volochii.



More information about the Python-checkins mailing list