mirror of
https://github.com/python/cpython
synced 2024-09-15 23:06:25 +00:00
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:
parent
3623aaa78c
commit
a1092f6249
|
@ -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>`.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
======================
|
||||
|
|
|
@ -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}'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>'
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
Loading…
Reference in a new issue