]> granicus.if.org Git - python/commitdiff
Asyncio issue 222 / PR 231 (Victor Stinner) -- fix @coroutine functions without __name__.
authorGuido van Rossum <guido@python.org>
Sun, 3 May 2015 01:38:24 +0000 (18:38 -0700)
committerGuido van Rossum <guido@python.org>
Sun, 3 May 2015 01:38:24 +0000 (18:38 -0700)
Lib/asyncio/coroutines.py
Lib/asyncio/events.py
Lib/asyncio/futures.py
Lib/test/test_asyncio/test_tasks.py
Misc/NEWS

index a1b28751f700dad0d062e3624802342590139037..c6394610aee7d2c18592a0891d3dcea6f05e7105 100644 (file)
@@ -151,7 +151,8 @@ def coroutine(func):
             w = CoroWrapper(coro(*args, **kwds), func)
             if w._source_traceback:
                 del w._source_traceback[-1]
-            w.__name__ = func.__name__
+            if hasattr(func, '__name__'):
+                w.__name__ = func.__name__
             if hasattr(func, '__qualname__'):
                 w.__qualname__ = func.__qualname__
             w.__doc__ = func.__doc__
@@ -175,25 +176,30 @@ def iscoroutine(obj):
 
 def _format_coroutine(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
     if (isinstance(coro, CoroWrapper)
     and not inspect.isgeneratorfunction(coro.func)):
         filename, lineno = events._get_function_source(coro.func)
         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))
         else:
-            coro_repr = ('%s() running, defined at %s:%s'
+            coro_repr = ('%s running, defined at %s:%s'
                          % (coro_name, filename, lineno))
     elif coro.gi_frame is not None:
         lineno = coro.gi_frame.f_lineno
-        coro_repr = ('%s() running at %s:%s'
+        coro_repr = ('%s running at %s:%s'
                      % (coro_name, filename, lineno))
     else:
         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))
 
     return coro_repr
index 8a7bb814a31939daa8931861d0e71f1af819ce03..3b907c6d9664b17fb65f43db77e70ec9118bec74 100644 (file)
@@ -54,15 +54,21 @@ def _format_callback(func, args, suffix=''):
             suffix = _format_args(args) + suffix
         return _format_callback(func.func, func.args, suffix)
 
-    func_repr = getattr(func, '__qualname__', None)
-    if not func_repr:
+    if hasattr(func, '__qualname__'):
+        func_repr = getattr(func, '__qualname__')
+    elif hasattr(func, '__name__'):
+        func_repr = getattr(func, '__name__')
+    else:
         func_repr = repr(func)
 
     if args is not None:
         func_repr += _format_args(args)
     if suffix:
         func_repr += suffix
+    return func_repr
 
+def _format_callback_source(func, args):
+    func_repr = _format_callback(func, args)
     source = _get_function_source(func)
     if source:
         func_repr += ' at %s:%s' % source
@@ -92,7 +98,7 @@ class Handle:
         if self._cancelled:
             info.append('cancelled')
         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:
             frame = self._source_traceback[-1]
             info.append('created at %s:%s' % (frame[0], frame[1]))
@@ -119,7 +125,7 @@ class Handle:
         try:
             self._callback(*self._args)
         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)
             context = {
                 'message': msg,
index 2c741fd422698c0a956b1fe890636f05a7acdc52..74a99ba03c064552e0895876b64e93a83940dd92 100644 (file)
@@ -162,7 +162,7 @@ class Future:
             cb = ''
 
         def format_cb(callback):
-            return events._format_callback(callback, ())
+            return events._format_callback_source(callback, ())
 
         if size == 1:
             cb = format_cb(cb[0])
index 06447d778b58c9a5eae40f95bbbe1ea5cf61b6dd..ab61462188baa717778ce7ad69f49ef76de71a76 100644 (file)
@@ -1,5 +1,7 @@
 """Tests for tasks.py."""
 
+import contextlib
+import functools
 import os
 import re
 import sys
@@ -28,6 +30,19 @@ def coroutine_function():
     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):
     if generator:
         state = '%s' % state
@@ -279,6 +294,29 @@ class TaskTests(test_utils.TestCase):
         fut.set_result(None)
         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):
         @asyncio.coroutine
         def outer():
@@ -1555,25 +1593,16 @@ class TaskTests(test_utils.TestCase):
             # The frame should have changed.
             self.assertIsNone(gen.gi_frame)
 
-        # Save debug flag.
-        old_debug = asyncio.coroutines._DEBUG
-        try:
-            # Test with debug flag cleared.
-            asyncio.coroutines._DEBUG = False
+        # Test with debug flag cleared.
+        with set_coroutine_debug(False):
             check()
 
-            # Test with debug flag set.
-            asyncio.coroutines._DEBUG = True
+        # Test with debug flag set.
+        with set_coroutine_debug(True):
             check()
 
-        finally:
-            # Restore original debug flag.
-            asyncio.coroutines._DEBUG = old_debug
-
     def test_yield_from_corowrapper(self):
-        old_debug = asyncio.coroutines._DEBUG
-        asyncio.coroutines._DEBUG = True
-        try:
+        with set_coroutine_debug(True):
             @asyncio.coroutine
             def t1():
                 return (yield from t2())
@@ -1591,8 +1620,6 @@ class TaskTests(test_utils.TestCase):
             task = asyncio.Task(t1(), loop=self.loop)
             val = self.loop.run_until_complete(task)
             self.assertEqual(val, (1, 2, 3))
-        finally:
-            asyncio.coroutines._DEBUG = old_debug
 
     def test_yield_from_corowrapper_send(self):
         def foo():
@@ -1663,14 +1690,10 @@ class TaskTests(test_utils.TestCase):
 
     @mock.patch('asyncio.coroutines.logger')
     def test_coroutine_never_yielded(self, m_log):
-        debug = asyncio.coroutines._DEBUG
-        try:
-            asyncio.coroutines._DEBUG = True
+        with set_coroutine_debug(True):
             @asyncio.coroutine
             def coro_noop():
                 pass
-        finally:
-            asyncio.coroutines._DEBUG = debug
 
         tb_filename = __file__
         tb_lineno = sys._getframe().f_lineno + 2
index 9eaaf58155df4aef7216c6f3a4a3988a3d589e58..24fc1507e0a6a7e5aaae1e08dce1c08ad54f72ed 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -39,6 +39,9 @@ Core and Builtins
 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.
   Patch written by William Orr.