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
This commit is contained in:
Victor Stinner 2014-06-18 01:14:59 +02:00
parent 66dc6b0f53
commit 8d3e02ef5a
2 changed files with 46 additions and 12 deletions

View File

@ -32,12 +32,12 @@ from .log import logger
_DEBUG = (not sys.flags.ignore_environment _DEBUG = (not sys.flags.ignore_environment
and bool(os.environ.get('PYTHONASYNCIODEBUG'))) and bool(os.environ.get('PYTHONASYNCIODEBUG')))
_PY35 = (sys.version_info >= (3, 5))
class CoroWrapper: class CoroWrapper:
# Wrapper for coroutine in _DEBUG mode. # Wrapper for coroutine in _DEBUG mode.
__slots__ = ['gen', 'func', '__name__', '__doc__', '__weakref__']
def __init__(self, gen, func): def __init__(self, gen, func):
assert inspect.isgenerator(gen), gen assert inspect.isgenerator(gen), gen
self.gen = gen self.gen = gen
@ -111,8 +111,10 @@ def coroutine(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwds): def wrapper(*args, **kwds):
w = CoroWrapper(coro(*args, **kwds), func) w = CoroWrapper(coro(*args, **kwds), func)
w.__name__ = coro.__name__ w.__name__ = func.__name__
w.__doc__ = coro.__doc__ if _PY35:
w.__qualname__ = func.__qualname__
w.__doc__ = func.__doc__
return w return w
wrapper._is_coroutine = True # For iscoroutinefunction(). wrapper._is_coroutine = True # For iscoroutinefunction().

View File

@ -9,9 +9,13 @@ import weakref
from test.script_helper import assert_python_ok from test.script_helper import assert_python_ok
import asyncio import asyncio
from asyncio import tasks
from asyncio import test_utils from asyncio import test_utils
PY35 = (sys.version_info >= (3, 5))
@asyncio.coroutine @asyncio.coroutine
def coroutine_function(): def coroutine_function():
pass pass
@ -117,10 +121,22 @@ class TaskTests(unittest.TestCase):
yield from [] yield from []
return 'abc' return 'abc'
self.assertEqual(notmuch.__name__, 'notmuch')
if PY35:
self.assertEqual(notmuch.__qualname__,
'TaskTests.test_task_repr.<locals>.notmuch')
self.assertEqual(notmuch.__module__, __name__)
filename, lineno = test_utils.get_function_source(notmuch) filename, lineno = test_utils.get_function_source(notmuch)
src = "%s:%s" % (filename, lineno) 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.<locals>.notmuch')
t = asyncio.Task(gen, loop=self.loop)
t.add_done_callback(Dummy()) t.add_done_callback(Dummy())
self.assertEqual(repr(t), self.assertEqual(repr(t),
'Task(<notmuch at %s>)<PENDING, [Dummy()]>' % src) 'Task(<notmuch at %s>)<PENDING, [Dummy()]>' % src)
@ -143,6 +159,12 @@ class TaskTests(unittest.TestCase):
def notmuch(): def notmuch():
pass pass
self.assertEqual(notmuch.__name__, 'notmuch')
self.assertEqual(notmuch.__module__, __name__)
if PY35:
self.assertEqual(notmuch.__qualname__,
'TaskTests.test_task_repr_custom.<locals>.notmuch')
class T(asyncio.Future): class T(asyncio.Future):
def __repr__(self): def __repr__(self):
return 'T[]' return 'T[]'
@ -152,16 +174,26 @@ class TaskTests(unittest.TestCase):
return super().__repr__() return super().__repr__()
gen = notmuch() gen = notmuch()
t = MyTask(gen, loop=self.loop) if PY35 or tasks._DEBUG:
filename = gen.gi_code.co_filename # On Python >= 3.5, generators now inherit the name of the
lineno = gen.gi_frame.f_lineno # function, as expected, and have a qualified name (__qualname__
if sys.version_info >= (3, 5): # attribute). In debug mode, @coroutine decorator uses CoroWrapper
name = 'notmuch' # which gets its name (__name__ attribute) from the wrapped
# coroutine function.
coro_name = 'notmuch'
else: else:
# On Python < 3.5, generators inherit the name of the code, not of # On Python < 3.5, generators inherit the name of the code, not of
# the function. See: http://bugs.python.org/issue21205 # the function. See: http://bugs.python.org/issue21205
name = 'coro' coro_name = 'coro'
self.assertEqual(repr(t), 'T[](<%s at %s:%s>)' % (name, filename, lineno)) self.assertEqual(gen.__name__, coro_name)
if PY35:
self.assertEqual(gen.__qualname__,
'TaskTests.test_task_repr_custom.<locals>.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): def test_task_basics(self):
@asyncio.coroutine @asyncio.coroutine