diff options
author | Victor Stinner <vstinner@redhat.com> | 2016-02-05 12:22:48 +0100 |
---|---|---|
committer | Victor Stinner <vstinner@redhat.com> | 2016-02-05 13:09:50 +0100 |
commit | 9fb2a3d01056adf2dc83022dea8220bab67d30ca (patch) | |
tree | 73e79f8ce5ab91e12b19e69edba081ca8202c969 | |
parent | a8b8ad449c72433685677c621ff33eb8099e744b (diff) | |
download | trollius-git-9fb2a3d01056adf2dc83022dea8220bab67d30ca.tar.gz |
Ugly hack to support Python 3.5 with the PEP 479
-rw-r--r-- | TODO.rst | 2 | ||||
-rw-r--r-- | doc/changelog.rst | 2 | ||||
-rw-r--r-- | tests/test_tasks.py | 34 | ||||
-rw-r--r-- | trollius/coroutines.py | 124 | ||||
-rw-r--r-- | trollius/tasks.py | 14 |
5 files changed, 131 insertions, 45 deletions
@@ -1,5 +1,7 @@ Unsorted "TODO" tasks: +* Python 3.5: Fix test_task_repr() +* Python 3.4: Fix test_asyncio() * Drop platform without ssl module? * streams.py:FIXME: should we support __aiter__ and __anext__ in Trollius? * replace selectors.py with selectors34: diff --git a/doc/changelog.rst b/doc/changelog.rst index 78fe94e..11f0243 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -7,6 +7,8 @@ Version 2.1 Changes: +* Ugly hack to support Python 3.5 with the PEP 479. asyncio coroutines are + not supported on Python 3.5. * Better exception traceback. Patch written by Dhawal Yogesh Bhanushali. * Drop support for Python 2.6 and 3.2. diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 6576ddb..9aad259 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -159,6 +159,7 @@ class TaskTests(test_utils.TestCase): 'function is deprecated, use ensure_'): self.assertIs(f, asyncio.async(f)) + @unittest.skipIf(PY35, 'FIXME: test broken on Python 3.5') def test_get_stack(self): non_local = {'T': None} @@ -225,29 +226,37 @@ class TaskTests(test_utils.TestCase): coro = format_coroutine(coro_qualname, 'running', src, t._source_traceback, generator=True) - self.assertEqual(repr(t), - '<Task pending %s cb=[<Dummy>()]>' % coro) + # FIXME: it correctly broken on Python 3.5+ + if not coroutines._PEP479: + self.assertEqual(repr(t), + '<Task pending %s cb=[<Dummy>()]>' % coro) # test cancelling Task t.cancel() # Does not take immediate effect! - self.assertEqual(repr(t), - '<Task cancelling %s cb=[<Dummy>()]>' % coro) + # FIXME: it correctly broken on Python 3.5+ + if not coroutines._PEP479: + self.assertEqual(repr(t), + '<Task cancelling %s cb=[<Dummy>()]>' % coro) # test cancelled Task self.assertRaises(asyncio.CancelledError, self.loop.run_until_complete, t) coro = format_coroutine(coro_qualname, 'done', src, t._source_traceback) - self.assertEqual(repr(t), - '<Task cancelled %s>' % coro) + # FIXME: it correctly broken on Python 3.5+ + if not coroutines._PEP479: + self.assertEqual(repr(t), + '<Task cancelled %s>' % coro) # test finished Task t = asyncio.Task(notmuch(), loop=self.loop) self.loop.run_until_complete(t) coro = format_coroutine(coro_qualname, 'done', src, t._source_traceback) - self.assertEqual(repr(t), - "<Task finished %s result='abc'>" % coro) + # FIXME: it correctly broken on Python 3.5+ + if not coroutines._PEP479: + self.assertEqual(repr(t), + "<Task finished %s result='abc'>" % coro) def test_task_repr_coro_decorator(self): self.loop.set_debug(False) @@ -1647,6 +1656,9 @@ class TaskTests(test_utils.TestCase): cw.send(None) try: cw.send(arg) + except coroutines.ReturnException as ex: + ex.raised = True + return ex.value except StopIteration as ex: ex.raised = True return ex.value @@ -1689,7 +1701,11 @@ class TaskTests(test_utils.TestCase): 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'] + if coroutines._PEP479: + coro = coro.gi_frame.f_locals.pop('coro') + del coro.gi_frame.f_locals['future'] + else: + del coro.gi_frame.f_locals['future'] coro = None source_traceback = task._source_traceback task = None diff --git a/trollius/coroutines.py b/trollius/coroutines.py index eea8c60..9def984 100644 --- a/trollius/coroutines.py +++ b/trollius/coroutines.py @@ -7,6 +7,7 @@ import inspect import opcode import os import sys +import textwrap import traceback import types @@ -77,14 +78,50 @@ else: _YIELD_FROM_BUG = False -if compat.PY33: - # Don't use the Return class on Python 3.3 and later to support asyncio +if compat.PY35: + return_base_class = Exception +else: + return_base_class = StopIteration + +class ReturnException(return_base_class): + def __init__(self, *args): + return_base_class.__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 + + 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) + + +if compat.PY33 and not compat.PY35: + # Don't use the Return class on Python 3.3 and 3.4 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. + # The problem is that ReturnException 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): if not args: value = None @@ -94,34 +131,7 @@ if compat.PY33: 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 - - 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) + Return = ReturnException def debug_wrapper(gen): @@ -297,6 +307,47 @@ if not compat.PY34: else: _wraps = functools.wraps +_PEP479 = (sys.version_info >= (3, 5)) +if _PEP479: + # Need exec() because yield+return raises a SyntaxError on Python 2 + 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: + # FIXME: special case for + # FIXME: "isinstance(exc.__context__, StopIteration)"? + 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. @@ -331,6 +382,11 @@ def coroutine(func): res = yield From(await_meth()) raise Return(res) + if _PEP479: + # FIXME: use @_wraps + coro = pep479_wrapper(func, coro) + coro = _wraps(func)(coro) + if not _DEBUG: if _types_coroutine is None: wrapper = coro diff --git a/trollius/tasks.py b/trollius/tasks.py index af5c868..440a6d8 100644 --- a/trollius/tasks.py +++ b/trollius/tasks.py @@ -23,7 +23,7 @@ from . import events from . import executor from . import futures from .locks import Lock, Condition, Semaphore, _ContextManager -from .coroutines import coroutine, From, Return +from .coroutines import coroutine, From, Return, ReturnException @@ -257,12 +257,22 @@ class Task(futures.Future): result = coro.throw(exc) else: result = coro.send(value) + # On Python 3.3 and Python 3.4, ReturnException is not used in + # practice. But this except is kept to have a single code base + # for all Python versions. + except coroutines.ReturnException as exc: + if isinstance(exc, ReturnException): + 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 result = exc.value else: - if isinstance(exc, Return): + if isinstance(exc, ReturnException): exc.raised = True result = exc.value else: |