[Python-checkins] cpython (3.3): Issue #16630: Make Idle calltips work even when __getattr__ raises.

terry.reedy python-checkins at python.org
Tue Jan 21 21:37:39 CET 2014


http://hg.python.org/cpython/rev/2fe0b2dcc98c
changeset:   88618:2fe0b2dcc98c
branch:      3.3
parent:      88615:eb7565c212f1
user:        Terry Jan Reedy <tjreedy at udel.edu>
date:        Tue Jan 21 15:36:51 2014 -0500
summary:
  Issue #16630: Make Idle calltips work even when __getattr__ raises.
Initial patch by Roger Serwy.

files:
  Lib/idlelib/CallTips.py                |  62 +++++++------
  Lib/idlelib/idle_test/test_calltips.py |  24 ++++-
  2 files changed, 50 insertions(+), 36 deletions(-)


diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/CallTips.py
--- a/Lib/idlelib/CallTips.py
+++ b/Lib/idlelib/CallTips.py
@@ -118,47 +118,49 @@
 
 # The following are used in both get_argspec and tests
 _first_param = re.compile('(?<=\()\w*\,?\s*')
-_default_callable_argspec = "No docstring, see docs."
+_default_callable_argspec = "See source or doc"
 
 def get_argspec(ob):
-    '''Return a string describing the arguments and return of a callable object.
+    '''Return a string describing the signature of a callable object, or ''.
 
     For Python-coded functions and methods, the first line is introspected.
     Delete 'self' parameter for classes (.__init__) and bound methods.
     The last line is the first line of the doc string.  For builtins, this typically
     includes the arguments in addition to the return value.
-
     '''
     argspec = ""
-    if hasattr(ob, '__call__'):
-        if isinstance(ob, type):
-            fob = getattr(ob, '__init__', None)
-        elif isinstance(ob.__call__, types.MethodType):
-            fob = ob.__call__
-        else:
-            fob = ob
-        if isinstance(fob, (types.FunctionType, types.MethodType)):
-            argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
-            if (isinstance(ob, (type, types.MethodType)) or
-                    isinstance(ob.__call__, types.MethodType)):
-                argspec = _first_param.sub("", argspec)
+    try:
+        ob_call = ob.__call__
+    except BaseException:
+        return argspec
+    if isinstance(ob, type):
+        fob = ob.__init__
+    elif isinstance(ob_call, types.MethodType):
+        fob = ob_call
+    else:
+        fob = ob
+    if isinstance(fob, (types.FunctionType, types.MethodType)):
+        argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
+        if (isinstance(ob, (type, types.MethodType)) or
+                isinstance(ob_call, types.MethodType)):
+            argspec = _first_param.sub("", argspec)
 
-        if isinstance(ob.__call__, types.MethodType):
-            doc = ob.__call__.__doc__
-        else:
-            doc = getattr(ob, "__doc__", "")
-        if doc:
-            doc = doc.lstrip()
-            pos = doc.find("\n")
-            if pos < 0 or pos > 70:
-                pos = 70
-            if argspec:
-                argspec += "\n"
-            argspec += doc[:pos]
-        if not argspec:
-            argspec = _default_callable_argspec
+    if isinstance(ob_call, types.MethodType):
+        doc = ob_call.__doc__
+    else:
+        doc = getattr(ob, "__doc__", "")
+    if doc:
+        doc = doc.lstrip()
+        pos = doc.find("\n")
+        if pos < 0 or pos > 70:
+            pos = 70
+        if argspec:
+            argspec += "\n"
+        argspec += doc[:pos]
+    if not argspec:
+        argspec = _default_callable_argspec
     return argspec
 
 if __name__ == '__main__':
     from unittest import main
-    main('idlelib.idle_test.test_calltips', verbosity=2, exit=False)
+    main('idlelib.idle_test.test_calltips', verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_calltips.py b/Lib/idlelib/idle_test/test_calltips.py
--- a/Lib/idlelib/idle_test/test_calltips.py
+++ b/Lib/idlelib/idle_test/test_calltips.py
@@ -2,6 +2,7 @@
 import idlelib.CallTips as ct
 import types
 
+default_tip = ct._default_callable_argspec
 
 # Test Class TC is used in multiple get_argspec test methods
 class TC():
@@ -63,7 +64,7 @@
         gtest(List.append, append_doc)
 
         gtest(types.MethodType, "method(function, instance)")
-        gtest(SB(), ct._default_callable_argspec)
+        gtest(SB(), default_tip)
 
     def test_functions(self):
         def t1(): 'doc'
@@ -88,13 +89,13 @@
 
     def test_bound_methods(self):
         # test that first parameter is correctly removed from argspec
-        # using _first_param re to calculate expected masks re errors
         for meth, mtip  in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"),
-                            (TC.cm, "(a)"),):
+                            (tc.__call__, '(ci)'), (tc, '(ci)'), (TC.cm, "(a)"),):
             self.assertEqual(signature(meth), mtip + "\ndoc")
-        self.assertEqual(signature(tc), "(ci)\ndoc")
-        # directly test that re works to delete first parameter even when it
-        # non-ascii chars, such as various forms of A.
+
+    def test_non_ascii_name(self):
+        # test that re works to delete a first parameter name that
+        # includes non-ascii chars, such as various forms of A.
         uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
         assert ct._first_param.sub('', uni) == '(a)'
 
@@ -105,6 +106,17 @@
         self.assertEqual(signature(TC.nd), "(s)")
         self.assertEqual(signature(tc.nd), "()")
 
+    def test_attribute_exception(self):
+        class NoCall:
+            def __getattr__(self, name):
+                raise BaseException
+        class Call(NoCall):
+            def __call__(self, ci):
+                pass
+        for meth, mtip  in ((NoCall, default_tip), (Call, default_tip),
+                            (NoCall(), ''), (Call(), '(ci)')):
+            self.assertEqual(signature(meth), mtip)
+
     def test_non_callables(self):
         for obj in (0, 0.0, '0', b'0', [], {}):
             self.assertEqual(signature(obj), '')

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list