Skip to content

Commit a1092f6

Browse files
authored
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.
1 parent 3623aaa commit a1092f6

File tree

11 files changed

+86
-764
lines changed

11 files changed

+86
-764
lines changed

Doc/library/asyncio-task.rst

-60
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,6 @@ other coroutines::
148148
* a *coroutine object*: an object returned by calling a
149149
*coroutine function*.
150150

151-
asyncio also supports legacy :ref:`generator-based
152-
<asyncio_generator_based_coro>` coroutines.
153-
154151

155152
.. rubric:: Tasks
156153

@@ -1042,60 +1039,3 @@ Task Object
10421039
in the :func:`repr` output of a task object.
10431040

10441041
.. versionadded:: 3.8
1045-
1046-
1047-
.. _asyncio_generator_based_coro:
1048-
1049-
Generator-based Coroutines
1050-
==========================
1051-
1052-
.. note::
1053-
1054-
Support for generator-based coroutines is **deprecated** and
1055-
is scheduled for removal in Python 3.10.
1056-
1057-
Generator-based coroutines predate async/await syntax. They are
1058-
Python generators that use ``yield from`` expressions to await
1059-
on Futures and other coroutines.
1060-
1061-
Generator-based coroutines should be decorated with
1062-
:func:`@asyncio.coroutine <asyncio.coroutine>`, although this is not
1063-
enforced.
1064-
1065-
1066-
.. decorator:: coroutine
1067-
1068-
Decorator to mark generator-based coroutines.
1069-
1070-
This decorator enables legacy generator-based coroutines to be
1071-
compatible with async/await code::
1072-
1073-
@asyncio.coroutine
1074-
def old_style_coroutine():
1075-
yield from asyncio.sleep(1)
1076-
1077-
async def main():
1078-
await old_style_coroutine()
1079-
1080-
This decorator should not be used for :keyword:`async def`
1081-
coroutines.
1082-
1083-
.. deprecated-removed:: 3.8 3.10
1084-
1085-
Use :keyword:`async def` instead.
1086-
1087-
.. function:: iscoroutine(obj)
1088-
1089-
Return ``True`` if *obj* is a :ref:`coroutine object <coroutine>`.
1090-
1091-
This method is different from :func:`inspect.iscoroutine` because
1092-
it returns ``True`` for generator-based coroutines.
1093-
1094-
.. function:: iscoroutinefunction(func)
1095-
1096-
Return ``True`` if *func* is a :ref:`coroutine function
1097-
<coroutine>`.
1098-
1099-
This method is different from :func:`inspect.iscoroutinefunction`
1100-
because it returns ``True`` for generator-based coroutine functions
1101-
decorated with :func:`@coroutine <coroutine>`.

Doc/library/collections.abc.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ ABC Inherits from Abstract Methods Mixin
198198

199199
.. note::
200200
In CPython, generator-based coroutines (generators decorated with
201-
:func:`types.coroutine` or :func:`asyncio.coroutine`) are
201+
:func:`types.coroutine`) are
202202
*awaitables*, even though they do not have an :meth:`__await__` method.
203203
Using ``isinstance(gencoro, Awaitable)`` for them will return ``False``.
204204
Use :func:`inspect.isawaitable` to detect them.
@@ -216,7 +216,7 @@ ABC Inherits from Abstract Methods Mixin
216216

217217
.. note::
218218
In CPython, generator-based coroutines (generators decorated with
219-
:func:`types.coroutine` or :func:`asyncio.coroutine`) are
219+
:func:`types.coroutine`) are
220220
*awaitables*, even though they do not have an :meth:`__await__` method.
221221
Using ``isinstance(gencoro, Coroutine)`` for them will return ``False``.
222222
Use :func:`inspect.isawaitable` to detect them.

Doc/reference/datamodel.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -2714,7 +2714,7 @@ are awaitable.
27142714
.. note::
27152715

27162716
The :term:`generator iterator` objects returned from generators
2717-
decorated with :func:`types.coroutine` or :func:`asyncio.coroutine`
2717+
decorated with :func:`types.coroutine`
27182718
are also awaitable, but they do not implement :meth:`__await__`.
27192719

27202720
.. method:: object.__await__(self)

Doc/whatsnew/3.11.rst

+9-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,15 @@ Deprecated
171171
Removed
172172
=======
173173

174-
174+
* The :func:`@asyncio.coroutine <asyncio.coroutine>` :term:`decorator` enabling
175+
legacy generator-based coroutines to be compatible with async/await code.
176+
The function has been deprecated since Python 3.8 and the removal was
177+
initially scheduled for Python 3.10. Use :keyword:`async def` instead.
178+
(Contributed by Illia Volochii in :issue:`43216`.)
179+
180+
* :class:`asyncio.coroutines.CoroWrapper` used for wrapping legacy
181+
generator-based coroutine objects in the debug mode.
182+
(Contributed by Illia Volochii in :issue:`43216`.)
175183

176184
Porting to Python 3.11
177185
======================

Lib/asyncio/coroutines.py

+4-162
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,19 @@
1-
__all__ = 'coroutine', 'iscoroutinefunction', 'iscoroutine'
1+
__all__ = 'iscoroutinefunction', 'iscoroutine'
22

33
import collections.abc
4-
import functools
54
import inspect
65
import os
76
import sys
87
import traceback
98
import types
10-
import warnings
11-
12-
from . import base_futures
13-
from . import constants
14-
from . import format_helpers
15-
from .log import logger
169

1710

1811
def _is_debug_mode():
19-
# If you set _DEBUG to true, @coroutine will wrap the resulting
20-
# generator objects in a CoroWrapper instance (defined below). That
21-
# instance will log a message when the generator is never iterated
22-
# over, which may happen when you forget to use "await" or "yield from"
23-
# with a coroutine call.
24-
# Note that the value of the _DEBUG flag is taken
25-
# when the decorator is used, so to be of any use it must be set
26-
# before you define your coroutines. A downside of using this feature
27-
# is that tracebacks show entries for the CoroWrapper.__next__ method
28-
# when _DEBUG is true.
12+
# See: https://docs.python.org/3/library/asyncio-dev.html#asyncio-debug-mode.
2913
return sys.flags.dev_mode or (not sys.flags.ignore_environment and
3014
bool(os.environ.get('PYTHONASYNCIODEBUG')))
3115

3216

33-
_DEBUG = _is_debug_mode()
34-
35-
36-
class CoroWrapper:
37-
# Wrapper for coroutine object in _DEBUG mode.
38-
39-
def __init__(self, gen, func=None):
40-
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
41-
self.gen = gen
42-
self.func = func # Used to unwrap @coroutine decorator
43-
self._source_traceback = format_helpers.extract_stack(sys._getframe(1))
44-
self.__name__ = getattr(gen, '__name__', None)
45-
self.__qualname__ = getattr(gen, '__qualname__', None)
46-
47-
def __repr__(self):
48-
coro_repr = _format_coroutine(self)
49-
if self._source_traceback:
50-
frame = self._source_traceback[-1]
51-
coro_repr += f', created at {frame[0]}:{frame[1]}'
52-
53-
return f'<{self.__class__.__name__} {coro_repr}>'
54-
55-
def __iter__(self):
56-
return self
57-
58-
def __next__(self):
59-
return self.gen.send(None)
60-
61-
def send(self, value):
62-
return self.gen.send(value)
63-
64-
def throw(self, type, value=None, traceback=None):
65-
return self.gen.throw(type, value, traceback)
66-
67-
def close(self):
68-
return self.gen.close()
69-
70-
@property
71-
def gi_frame(self):
72-
return self.gen.gi_frame
73-
74-
@property
75-
def gi_running(self):
76-
return self.gen.gi_running
77-
78-
@property
79-
def gi_code(self):
80-
return self.gen.gi_code
81-
82-
def __await__(self):
83-
return self
84-
85-
@property
86-
def gi_yieldfrom(self):
87-
return self.gen.gi_yieldfrom
88-
89-
def __del__(self):
90-
# Be careful accessing self.gen.frame -- self.gen might not exist.
91-
gen = getattr(self, 'gen', None)
92-
frame = getattr(gen, 'gi_frame', None)
93-
if frame is not None and frame.f_lasti == -1:
94-
msg = f'{self!r} was never yielded from'
95-
tb = getattr(self, '_source_traceback', ())
96-
if tb:
97-
tb = ''.join(traceback.format_list(tb))
98-
msg += (f'\nCoroutine object created at '
99-
f'(most recent call last, truncated to '
100-
f'{constants.DEBUG_STACK_DEPTH} last lines):\n')
101-
msg += tb.rstrip()
102-
logger.error(msg)
103-
104-
105-
def coroutine(func):
106-
"""Decorator to mark coroutines.
107-
108-
If the coroutine is not yielded from before it is destroyed,
109-
an error message is logged.
110-
"""
111-
warnings.warn('"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead',
112-
DeprecationWarning,
113-
stacklevel=2)
114-
if inspect.iscoroutinefunction(func):
115-
# In Python 3.5 that's all we need to do for coroutines
116-
# defined with "async def".
117-
return func
118-
119-
if inspect.isgeneratorfunction(func):
120-
coro = func
121-
else:
122-
@functools.wraps(func)
123-
def coro(*args, **kw):
124-
res = func(*args, **kw)
125-
if (base_futures.isfuture(res) or inspect.isgenerator(res) or
126-
isinstance(res, CoroWrapper)):
127-
res = yield from res
128-
else:
129-
# If 'res' is an awaitable, run it.
130-
try:
131-
await_meth = res.__await__
132-
except AttributeError:
133-
pass
134-
else:
135-
if isinstance(res, collections.abc.Awaitable):
136-
res = yield from await_meth()
137-
return res
138-
139-
coro = types.coroutine(coro)
140-
if not _DEBUG:
141-
wrapper = coro
142-
else:
143-
@functools.wraps(func)
144-
def wrapper(*args, **kwds):
145-
w = CoroWrapper(coro(*args, **kwds), func=func)
146-
if w._source_traceback:
147-
del w._source_traceback[-1]
148-
# Python < 3.5 does not implement __qualname__
149-
# on generator objects, so we set it manually.
150-
# We use getattr as some callables (such as
151-
# functools.partial may lack __qualname__).
152-
w.__name__ = getattr(func, '__name__', None)
153-
w.__qualname__ = getattr(func, '__qualname__', None)
154-
return w
155-
156-
wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction().
157-
return wrapper
158-
159-
16017
# A marker for iscoroutinefunction.
16118
_is_coroutine = object()
16219

@@ -170,7 +27,7 @@ def iscoroutinefunction(func):
17027
# Prioritize native coroutine check to speed-up
17128
# asyncio.iscoroutine.
17229
_COROUTINE_TYPES = (types.CoroutineType, types.GeneratorType,
173-
collections.abc.Coroutine, CoroWrapper)
30+
collections.abc.Coroutine)
17431
_iscoroutine_typecache = set()
17532

17633

@@ -193,16 +50,11 @@ def iscoroutine(obj):
19350
def _format_coroutine(coro):
19451
assert iscoroutine(coro)
19552

196-
is_corowrapper = isinstance(coro, CoroWrapper)
197-
19853
def get_name(coro):
19954
# Coroutines compiled with Cython sometimes don't have
20055
# proper __qualname__ or __name__. While that is a bug
20156
# in Cython, asyncio shouldn't crash with an AttributeError
20257
# in its __repr__ functions.
203-
if is_corowrapper:
204-
return format_helpers._format_callback(coro.func, (), {})
205-
20658
if hasattr(coro, '__qualname__') and coro.__qualname__:
20759
coro_name = coro.__qualname__
20860
elif hasattr(coro, '__name__') and coro.__name__:
@@ -247,18 +99,8 @@ def is_running(coro):
24799
filename = coro_code.co_filename or '<empty co_filename>'
248100

249101
lineno = 0
250-
if (is_corowrapper and
251-
coro.func is not None and
252-
not inspect.isgeneratorfunction(coro.func)):
253-
source = format_helpers._get_function_source(coro.func)
254-
if source is not None:
255-
filename, lineno = source
256-
if coro_frame is None:
257-
coro_repr = f'{coro_name} done, defined at {filename}:{lineno}'
258-
else:
259-
coro_repr = f'{coro_name} running, defined at {filename}:{lineno}'
260102

261-
elif coro_frame is not None:
103+
if coro_frame is not None:
262104
lineno = coro_frame.f_lineno
263105
coro_repr = f'{coro_name} running at {filename}:{lineno}'
264106

Lib/test/test_asyncio/test_base_events.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1884,10 +1884,8 @@ def test_accept_connection_exception(self, m_log):
18841884
MyProto, sock, None, None, mock.ANY, mock.ANY)
18851885

18861886
def test_call_coroutine(self):
1887-
with self.assertWarns(DeprecationWarning):
1888-
@asyncio.coroutine
1889-
def simple_coroutine():
1890-
pass
1887+
async def simple_coroutine():
1888+
pass
18911889

18921890
self.loop.set_debug(True)
18931891
coro_func = simple_coroutine

Lib/test/test_asyncio/test_events.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import sys
1818
import threading
1919
import time
20+
import types
2021
import errno
2122
import unittest
2223
from unittest import mock
@@ -2163,8 +2164,7 @@ def test_handle_repr(self):
21632164
'<Handle cancelled>')
21642165

21652166
# decorated function
2166-
with self.assertWarns(DeprecationWarning):
2167-
cb = asyncio.coroutine(noop)
2167+
cb = types.coroutine(noop)
21682168
h = asyncio.Handle(cb, (), self.loop)
21692169
self.assertEqual(repr(h),
21702170
'<Handle noop() at %s:%s>'

0 commit comments

Comments
 (0)