Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-43216: Remove @asyncio.coroutine #26369

Merged
merged 7 commits into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 0 additions & 60 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,6 @@ other coroutines::
* a *coroutine object*: an object returned by calling a
*coroutine function*.
illia-v marked this conversation as resolved.
Show resolved Hide resolved

asyncio also supports legacy :ref:`generator-based
<asyncio_generator_based_coro>` coroutines.


.. rubric:: Tasks

Expand Down Expand Up @@ -973,60 +970,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>`.
4 changes: 2 additions & 2 deletions Doc/library/collections.abc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ 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`.)
illia-v marked this conversation as resolved.
Show resolved Hide resolved


Porting to Python 3.11
Expand Down
166 changes: 4 additions & 162 deletions Lib/asyncio/coroutines.py
Original file line number Diff line number Diff line change
@@ -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)
illia-v marked this conversation as resolved.
Show resolved Hide resolved
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()

Expand All @@ -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()


Expand All @@ -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__:
Expand Down Expand Up @@ -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}'

Expand Down
6 changes: 2 additions & 4 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import sys
import threading
import time
import types
import errno
import unittest
from unittest import mock
Expand Down Expand Up @@ -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>'
Expand Down
Loading