diff options
-rw-r--r-- | Doc/library/asyncio-task.rst | 60 | ||||
-rw-r--r-- | Doc/library/collections.abc.rst | 4 | ||||
-rw-r--r-- | Doc/reference/datamodel.rst | 2 | ||||
-rw-r--r-- | Doc/whatsnew/3.11.rst | 10 | ||||
-rw-r--r-- | Lib/asyncio/coroutines.py | 166 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_base_events.py | 6 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_events.py | 4 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_locks.py | 38 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_pep492.py | 14 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_tasks.py | 540 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst | 6 |
11 files changed, 86 insertions, 764 deletions
diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index bbdef33..44ec3cc 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -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>`. diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index 2345e78..924d0b5 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -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. diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index eefdc3d..2f8ed70 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -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) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 8e3d5f3..94d8bef 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 ====================== diff --git a/Lib/asyncio/coroutines.py b/Lib/asyncio/coroutines.py index 9664ea7..0e4b489 100644 --- a/Lib/asyncio/coroutines.py +++ b/Lib/asyncio/coroutines.py @@ -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 _format_coroutine(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}' diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 5691d42..47a9fb9 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1884,10 +1884,8 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase): 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 diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 55fc266..e781769 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -17,6 +17,7 @@ import subprocess import sys import threading import time +import types import errno import unittest from unittest import mock @@ -2163,8 +2164,7 @@ class HandleTests(test_utils.TestCase): '<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>' diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 6194cd0..441adee 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -38,14 +38,12 @@ class LockTests(test_utils.TestCase): 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 @@ class LockTests(test_utils.TestCase): 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 @@ class SemaphoreTests(test_utils.TestCase): 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()) diff --git a/Lib/test/test_asyncio/test_pep492.py b/Lib/test/test_asyncio/test_pep492.py index 4bd50f4..f833f78 100644 --- a/Lib/test/test_asyncio/test_pep492.py +++ b/Lib/test/test_asyncio/test_pep492.py @@ -123,20 +123,6 @@ class CoroutineTests(BaseTest): 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' diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a9e4cf5..8671087 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -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 @@ class BaseTaskTests: 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 BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: (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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: 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 @@ class BaseTaskTests: "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 @@ class GatherTestsBase: 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 @@ class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): 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 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase): # 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 @@ class CompatibilityTests(test_utils.TestCase): 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() diff --git a/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst b/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst new file mode 100644 index 0000000..845ef95 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-25-23-26-38.bpo-43216.xTUyyX.rst @@ -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. |