Fix @coroutine for functions without __name__
Issue #222: Fix the @coroutine decorator for functions without __name__ attribute like functools.partial(). Enhance also the representation of a CoroWrapper if the coroutine function is a functools.partial().
This commit is contained in:
		@@ -151,6 +151,7 @@ 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]
 | 
				
			||||||
 | 
					            if hasattr(func, '__name__'):
 | 
				
			||||||
                w.__name__ = func.__name__
 | 
					                w.__name__ = func.__name__
 | 
				
			||||||
            if hasattr(func, '__qualname__'):
 | 
					            if hasattr(func, '__qualname__'):
 | 
				
			||||||
                w.__qualname__ = func.__qualname__
 | 
					                w.__qualname__ = func.__qualname__
 | 
				
			||||||
@@ -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=''):
 | 
				
			|||||||
            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:
 | 
				
			|||||||
        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:
 | 
				
			|||||||
        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:
 | 
				
			|||||||
            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])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
"""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():
 | 
				
			|||||||
    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):
 | 
				
			|||||||
        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):
 | 
				
			|||||||
            # The frame should have changed.
 | 
					            # The frame should have changed.
 | 
				
			||||||
            self.assertIsNone(gen.gi_frame)
 | 
					            self.assertIsNone(gen.gi_frame)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Save debug flag.
 | 
					 | 
				
			||||||
        old_debug = asyncio.coroutines._DEBUG
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
        # Test with debug flag cleared.
 | 
					        # Test with debug flag cleared.
 | 
				
			||||||
            asyncio.coroutines._DEBUG = False
 | 
					        with set_coroutine_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):
 | 
				
			|||||||
            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):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user