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 1 commit
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
70 changes: 0 additions & 70 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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*.
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 +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 <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
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ Deprecated
Removed
=======

* The :func:`asyncio.coroutine` :term:`decorator` enabling legacy
illia-v marked this conversation as resolved.
Show resolved Hide resolved
generator-based coroutines to be compatible with async/await code.
Use :keyword:`async def` instead.
illia-v marked this conversation as resolved.
Show resolved Hide resolved
(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
63 changes: 1 addition & 62 deletions Lib/asyncio/coroutines.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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)
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 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
38 changes: 16 additions & 22 deletions Lib/test/test_asyncio/test_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

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

Expand Down
14 changes: 0 additions & 14 deletions Lib/test/test_asyncio/test_pep492.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Loading