Commit e470bc00 authored by Guido van Rossum's avatar Guido van Rossum

Asyncio issue 222 / PR 231 (Victor Stinner) -- fix @coroutine functions without __name__.

parent 34da805f
...@@ -151,7 +151,8 @@ def coroutine(func): ...@@ -151,7 +151,8 @@ def coroutine(func):
w = CoroWrapper(coro(*args, **kwds), func) w = CoroWrapper(coro(*args, **kwds), func)
if w._source_traceback: if w._source_traceback:
del w._source_traceback[-1] del w._source_traceback[-1]
w.__name__ = func.__name__ if hasattr(func, '__name__'):
w.__name__ = func.__name__
if hasattr(func, '__qualname__'): if hasattr(func, '__qualname__'):
w.__qualname__ = func.__qualname__ w.__qualname__ = func.__qualname__
w.__doc__ = func.__doc__ w.__doc__ = func.__doc__
...@@ -175,25 +176,30 @@ def iscoroutine(obj): ...@@ -175,25 +176,30 @@ def iscoroutine(obj):
def _format_coroutine(coro): def _format_coroutine(coro):
assert iscoroutine(coro) assert iscoroutine(coro)
coro_name = getattr(coro, '__qualname__', coro.__name__)
if isinstance(coro, CoroWrapper):
func = coro.func
else:
func = coro
coro_name = events._format_callback(func, ())
filename = coro.gi_code.co_filename filename = coro.gi_code.co_filename
if (isinstance(coro, CoroWrapper) if (isinstance(coro, CoroWrapper)
and not inspect.isgeneratorfunction(coro.func)): and not inspect.isgeneratorfunction(coro.func)):
filename, lineno = events._get_function_source(coro.func) filename, lineno = events._get_function_source(coro.func)
if coro.gi_frame is None: if coro.gi_frame is None:
coro_repr = ('%s() done, defined at %s:%s' coro_repr = ('%s done, defined at %s:%s'
% (coro_name, filename, lineno)) % (coro_name, filename, lineno))
else: else:
coro_repr = ('%s() running, defined at %s:%s' coro_repr = ('%s running, defined at %s:%s'
% (coro_name, filename, lineno)) % (coro_name, filename, lineno))
elif coro.gi_frame is not None: elif coro.gi_frame is not None:
lineno = coro.gi_frame.f_lineno lineno = coro.gi_frame.f_lineno
coro_repr = ('%s() running at %s:%s' coro_repr = ('%s running at %s:%s'
% (coro_name, filename, lineno)) % (coro_name, filename, lineno))
else: else:
lineno = coro.gi_code.co_firstlineno lineno = coro.gi_code.co_firstlineno
coro_repr = ('%s() done, defined at %s:%s' coro_repr = ('%s done, defined at %s:%s'
% (coro_name, filename, lineno)) % (coro_name, filename, lineno))
return coro_repr return coro_repr
...@@ -54,15 +54,21 @@ def _format_callback(func, args, suffix=''): ...@@ -54,15 +54,21 @@ def _format_callback(func, args, suffix=''):
suffix = _format_args(args) + suffix suffix = _format_args(args) + suffix
return _format_callback(func.func, func.args, suffix) return _format_callback(func.func, func.args, suffix)
func_repr = getattr(func, '__qualname__', None) if hasattr(func, '__qualname__'):
if not func_repr: func_repr = getattr(func, '__qualname__')
elif hasattr(func, '__name__'):
func_repr = getattr(func, '__name__')
else:
func_repr = repr(func) func_repr = repr(func)
if args is not None: if args is not None:
func_repr += _format_args(args) func_repr += _format_args(args)
if suffix: if suffix:
func_repr += suffix func_repr += suffix
return func_repr
def _format_callback_source(func, args):
func_repr = _format_callback(func, args)
source = _get_function_source(func) source = _get_function_source(func)
if source: if source:
func_repr += ' at %s:%s' % source func_repr += ' at %s:%s' % source
...@@ -92,7 +98,7 @@ class Handle: ...@@ -92,7 +98,7 @@ class Handle:
if self._cancelled: if self._cancelled:
info.append('cancelled') info.append('cancelled')
if self._callback is not None: if self._callback is not None:
info.append(_format_callback(self._callback, self._args)) info.append(_format_callback_source(self._callback, self._args))
if self._source_traceback: if self._source_traceback:
frame = self._source_traceback[-1] frame = self._source_traceback[-1]
info.append('created at %s:%s' % (frame[0], frame[1])) info.append('created at %s:%s' % (frame[0], frame[1]))
...@@ -119,7 +125,7 @@ class Handle: ...@@ -119,7 +125,7 @@ class Handle:
try: try:
self._callback(*self._args) self._callback(*self._args)
except Exception as exc: except Exception as exc:
cb = _format_callback(self._callback, self._args) cb = _format_callback_source(self._callback, self._args)
msg = 'Exception in callback {}'.format(cb) msg = 'Exception in callback {}'.format(cb)
context = { context = {
'message': msg, 'message': msg,
......
...@@ -162,7 +162,7 @@ class Future: ...@@ -162,7 +162,7 @@ class Future:
cb = '' cb = ''
def format_cb(callback): def format_cb(callback):
return events._format_callback(callback, ()) return events._format_callback_source(callback, ())
if size == 1: if size == 1:
cb = format_cb(cb[0]) cb = format_cb(cb[0])
......
"""Tests for tasks.py.""" """Tests for tasks.py."""
import contextlib
import functools
import os import os
import re import re
import sys import sys
...@@ -28,6 +30,19 @@ def coroutine_function(): ...@@ -28,6 +30,19 @@ def coroutine_function():
pass 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): def format_coroutine(qualname, state, src, source_traceback, generator=False):
if generator: if generator:
state = '%s' % state state = '%s' % state
...@@ -279,6 +294,29 @@ class TaskTests(test_utils.TestCase): ...@@ -279,6 +294,29 @@ class TaskTests(test_utils.TestCase):
fut.set_result(None) fut.set_result(None)
self.loop.run_until_complete(task) 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)
@asyncio.coroutine
def func(x, y):
yield from asyncio.sleep(0)
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 = ('<CoroWrapper TaskTests.test_task_repr_partial_corowrapper'
'.<locals>.func(1)() running, ')
self.assertTrue(coro_repr.startswith(expected),
coro_repr)
def test_task_basics(self): def test_task_basics(self):
@asyncio.coroutine @asyncio.coroutine
def outer(): def outer():
...@@ -1555,25 +1593,16 @@ class TaskTests(test_utils.TestCase): ...@@ -1555,25 +1593,16 @@ class TaskTests(test_utils.TestCase):
# The frame should have changed. # The frame should have changed.
self.assertIsNone(gen.gi_frame) self.assertIsNone(gen.gi_frame)
# Save debug flag. # Test with debug flag cleared.
old_debug = asyncio.coroutines._DEBUG with set_coroutine_debug(False):
try:
# Test with debug flag cleared.
asyncio.coroutines._DEBUG = False
check() check()
# Test with debug flag set. # Test with debug flag set.
asyncio.coroutines._DEBUG = True with set_coroutine_debug(True):
check() check()
finally:
# Restore original debug flag.
asyncio.coroutines._DEBUG = old_debug
def test_yield_from_corowrapper(self): def test_yield_from_corowrapper(self):
old_debug = asyncio.coroutines._DEBUG with set_coroutine_debug(True):
asyncio.coroutines._DEBUG = True
try:
@asyncio.coroutine @asyncio.coroutine
def t1(): def t1():
return (yield from t2()) return (yield from t2())
...@@ -1591,8 +1620,6 @@ class TaskTests(test_utils.TestCase): ...@@ -1591,8 +1620,6 @@ class TaskTests(test_utils.TestCase):
task = asyncio.Task(t1(), loop=self.loop) task = asyncio.Task(t1(), loop=self.loop)
val = self.loop.run_until_complete(task) val = self.loop.run_until_complete(task)
self.assertEqual(val, (1, 2, 3)) self.assertEqual(val, (1, 2, 3))
finally:
asyncio.coroutines._DEBUG = old_debug
def test_yield_from_corowrapper_send(self): def test_yield_from_corowrapper_send(self):
def foo(): def foo():
...@@ -1663,14 +1690,10 @@ class TaskTests(test_utils.TestCase): ...@@ -1663,14 +1690,10 @@ class TaskTests(test_utils.TestCase):
@mock.patch('asyncio.coroutines.logger') @mock.patch('asyncio.coroutines.logger')
def test_coroutine_never_yielded(self, m_log): def test_coroutine_never_yielded(self, m_log):
debug = asyncio.coroutines._DEBUG with set_coroutine_debug(True):
try:
asyncio.coroutines._DEBUG = True
@asyncio.coroutine @asyncio.coroutine
def coro_noop(): def coro_noop():
pass pass
finally:
asyncio.coroutines._DEBUG = debug
tb_filename = __file__ tb_filename = __file__
tb_lineno = sys._getframe().f_lineno + 2 tb_lineno = sys._getframe().f_lineno + 2
......
...@@ -39,6 +39,9 @@ Core and Builtins ...@@ -39,6 +39,9 @@ Core and Builtins
Library Library
------- -------
- Asyncio issue 222 / PR 231 (Victor Stinner) -- fix @coroutine
functions without __name__.
- Issue #9246: On POSIX, os.getcwd() now supports paths longer than 1025 bytes. - Issue #9246: On POSIX, os.getcwd() now supports paths longer than 1025 bytes.
Patch written by William Orr. Patch written by William Orr.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment