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.
This commit is contained in:
Illia Volochii 2021-07-01 16:13:59 +03:00 committed by GitHub
parent 3623aaa78c
commit a1092f6249
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 85 additions and 763 deletions

View file

@ -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>`.

View file

@ -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.

View file

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

View file

@ -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
======================

View file

@ -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}'

View file

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

View file

@ -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>'

View file

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

View file

@ -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'

View file

@ -33,18 +33,6 @@ async def coroutine_function():
pass
@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()

View file

@ -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.