From ca26b61ac28cfb49252a6103c1196dc364ca3625 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 15 Dec 2014 17:05:35 +0100 Subject: Support the PEP 479 --- doc/changelog.rst | 3 ++ tests/test_tasks.py | 2 +- trollius/coroutines.py | 111 +++++++++++++++++++++++++++++++------------------ trollius/tasks.py | 7 ++++ 4 files changed, 82 insertions(+), 41 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 51d4d2e..9679a71 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -14,6 +14,9 @@ Changes: functions. Patch by Donald Stufft. * Add run_aiotest.py: run the aiotest test suite. * tox now also run the aiotest test suite +* On Python 3.3 and newer, Return now also logs an error when used without + raise. +* Support Python 3.5 with the PEP 479. Bugfixes: diff --git a/tests/test_tasks.py b/tests/test_tasks.py index e1a8a27..6d04c4f 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -1585,7 +1585,7 @@ class TaskTests(test_utils.TestCase): cw.send(None) try: cw.send(arg) - except StopIteration as ex: + except Return as ex: ex.raised = True return ex.value else: diff --git a/trollius/coroutines.py b/trollius/coroutines.py index 650bc13..81a44f6 100644 --- a/trollius/coroutines.py +++ b/trollius/coroutines.py @@ -6,6 +6,7 @@ import inspect import opcode import os import sys +import textwrap import traceback import types @@ -59,51 +60,34 @@ else: _YIELD_FROM_BUG = False -if compat.PY33: - # Don't use the Return class on Python 3.3 and later to support asyncio - # coroutines (to avoid the warning emited in Return destructor). - # - # The problem is that Return inherits from StopIteration. "yield from - # trollius_coroutine". Task._step() does not receive the Return exception, - # because "yield from" handles it internally. So it's not possible to set - # the raised attribute to True to avoid the warning in Return destructor. - def Return(*args): +class Return(Exception): + def __init__(self, *args): + Exception.__init__(self) if not args: - value = None + self.value = None elif len(args) == 1: - value = args[0] + self.value = args[0] else: - value = args - return StopIteration(value) -else: - class Return(StopIteration): - def __init__(self, *args): - StopIteration.__init__(self) - if not args: - self.value = None - elif len(args) == 1: - self.value = args[0] - else: - self.value = args - self.raised = False - if _DEBUG: - frame = sys._getframe(1) - self._source_traceback = traceback.extract_stack(frame) - # explicitly clear the reference to avoid reference cycles - frame = None - else: - self._source_traceback = None + self.value = args + self.raised = False + if _DEBUG: + frame = sys._getframe(1) + self._source_traceback = traceback.extract_stack(frame) + # explicitly clear the reference to avoid reference cycles + frame = None + else: + self._source_traceback = None - def __del__(self): - if self.raised: - return + def __del__(self): + if self.raised: + return - fmt = 'Return(%r) used without raise' - if self._source_traceback: - fmt += '\nReturn created at (most recent call last):\n' - tb = ''.join(traceback.format_list(self._source_traceback)) - fmt += tb.rstrip() - logger.error(fmt, self.value) + fmt = 'Return(%r) used without raise' + if self._source_traceback: + fmt += '\nReturn created at (most recent call last):\n' + tb = ''.join(traceback.format_list(self._source_traceback)) + fmt += tb.rstrip() + logger.error(fmt, self.value) def _coroutine_at_yield_from(coro): @@ -245,6 +229,48 @@ if not compat.PY34: else: _wraps = functools.wraps +_PEP479 = (sys.version_info >= (3,)) +if _PEP479: + exec(textwrap.dedent(''' + def pep479_wrapper(func, coro_func): + @_wraps(func) + def pep479_wrapped(*args, **kw): + coro = coro_func(*args, **kw) + value = None + error = None + while True: + try: + if error is not None: + value = coro.throw(error) + elif value is not None: + value = coro.send(value) + else: + value = next(coro) + except RuntimeError as exc: + if isinstance(exc.__context__, StopIteration): + # a trollius coroutine uses "raise Return" + raise + return exc.__context__.value + else: + raise + except StopIteration as exc: + return exc.value + except Return as exc: + exc.raised = True + return exc.value + except BaseException as exc: + raise + + try: + value = yield value + error = None + except BaseException as exc: + value = None + error = exc + return pep479_wrapped + ''')) + + def coroutine(func): """Decorator to mark coroutines. @@ -262,6 +288,11 @@ def coroutine(func): res = yield From(res) raise Return(res) + if _PEP479: + # FIXME: use @_wraps + coro = pep479_wrapper(func, coro) + coro = _wraps(func)(coro) + if not _DEBUG: wrapper = coro else: diff --git a/trollius/tasks.py b/trollius/tasks.py index 4d91de8..ec34f58 100644 --- a/trollius/tasks.py +++ b/trollius/tasks.py @@ -253,6 +253,13 @@ class Task(futures.Future): result = coro.send(value) else: result = next(coro) + except coroutines.Return as exc: + if isinstance(exc, Return): + exc.raised = True + result = exc.value + else: + result = None + self.set_result(result) except StopIteration as exc: if compat.PY33: # asyncio Task object? get the result of the coroutine -- cgit v1.2.1