From 8d3e02ef5a452b59fc909bf45d1d18bfd916c596 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 18 Jun 2014 01:14:59 +0200 Subject: [PATCH] asyncio: Set __qualname__ attribute of CoroWrapper in @coroutine decorator on Python 3.5 - Drop __slots__ optimization of CoroWrapper to be able to set the __qualname__ attribute. - Add tests on __name__, __qualname__ and __module__ of a coroutine function and coroutine object. - Fix test_tasks when run in debug mode (PYTHONASYNCIODEBUG env var set) on Python 3.3 or 3.4 --- Lib/asyncio/tasks.py | 10 +++--- Lib/test/test_asyncio/test_tasks.py | 48 ++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 281bf608e87..eaf93f88732 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -32,12 +32,12 @@ from .log import logger _DEBUG = (not sys.flags.ignore_environment and bool(os.environ.get('PYTHONASYNCIODEBUG'))) +_PY35 = (sys.version_info >= (3, 5)) + class CoroWrapper: # Wrapper for coroutine in _DEBUG mode. - __slots__ = ['gen', 'func', '__name__', '__doc__', '__weakref__'] - def __init__(self, gen, func): assert inspect.isgenerator(gen), gen self.gen = gen @@ -111,8 +111,10 @@ def coroutine(func): @functools.wraps(func) def wrapper(*args, **kwds): w = CoroWrapper(coro(*args, **kwds), func) - w.__name__ = coro.__name__ - w.__doc__ = coro.__doc__ + w.__name__ = func.__name__ + if _PY35: + w.__qualname__ = func.__qualname__ + w.__doc__ = func.__doc__ return w wrapper._is_coroutine = True # For iscoroutinefunction(). diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 4e239ecbcbd..dcc81234d67 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -9,9 +9,13 @@ import weakref from test.script_helper import assert_python_ok import asyncio +from asyncio import tasks from asyncio import test_utils +PY35 = (sys.version_info >= (3, 5)) + + @asyncio.coroutine def coroutine_function(): pass @@ -117,10 +121,22 @@ class TaskTests(unittest.TestCase): yield from [] return 'abc' + self.assertEqual(notmuch.__name__, 'notmuch') + if PY35: + self.assertEqual(notmuch.__qualname__, + 'TaskTests.test_task_repr..notmuch') + self.assertEqual(notmuch.__module__, __name__) + filename, lineno = test_utils.get_function_source(notmuch) src = "%s:%s" % (filename, lineno) - t = asyncio.Task(notmuch(), loop=self.loop) + gen = notmuch() + self.assertEqual(gen.__name__, 'notmuch') + if PY35: + self.assertEqual(gen.__qualname__, + 'TaskTests.test_task_repr..notmuch') + + t = asyncio.Task(gen, loop=self.loop) t.add_done_callback(Dummy()) self.assertEqual(repr(t), 'Task()' % src) @@ -143,6 +159,12 @@ class TaskTests(unittest.TestCase): def notmuch(): pass + self.assertEqual(notmuch.__name__, 'notmuch') + self.assertEqual(notmuch.__module__, __name__) + if PY35: + self.assertEqual(notmuch.__qualname__, + 'TaskTests.test_task_repr_custom..notmuch') + class T(asyncio.Future): def __repr__(self): return 'T[]' @@ -152,16 +174,26 @@ class TaskTests(unittest.TestCase): return super().__repr__() gen = notmuch() - t = MyTask(gen, loop=self.loop) - filename = gen.gi_code.co_filename - lineno = gen.gi_frame.f_lineno - if sys.version_info >= (3, 5): - name = 'notmuch' + if PY35 or tasks._DEBUG: + # On Python >= 3.5, generators now inherit the name of the + # function, as expected, and have a qualified name (__qualname__ + # attribute). In debug mode, @coroutine decorator uses CoroWrapper + # which gets its name (__name__ attribute) from the wrapped + # coroutine function. + coro_name = 'notmuch' else: # On Python < 3.5, generators inherit the name of the code, not of # the function. See: http://bugs.python.org/issue21205 - name = 'coro' - self.assertEqual(repr(t), 'T[](<%s at %s:%s>)' % (name, filename, lineno)) + coro_name = 'coro' + self.assertEqual(gen.__name__, coro_name) + if PY35: + self.assertEqual(gen.__qualname__, + 'TaskTests.test_task_repr_custom..notmuch') + + t = MyTask(gen, loop=self.loop) + filename = gen.gi_code.co_filename + lineno = gen.gi_frame.f_lineno + self.assertEqual(repr(t), 'T[](<%s at %s:%s>)' % (coro_name, filename, lineno)) def test_task_basics(self): @asyncio.coroutine