Skip to content

Commit

Permalink
bpo-43216: Remove @asyncio.coroutine
Browse files Browse the repository at this point in the history
  • Loading branch information
illia-v committed May 25, 2021
1 parent d18e5da commit 4e037da
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 610 deletions.
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*.

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

0 comments on commit 4e037da

Please sign in to comment.