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

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


http://hg.python.org/cpython/rev/d55d1cbf5f9a
changeset:   88617:d55d1cbf5f9a
branch:      2.7
parent:      88610:6faddc895867
user:        Terry Jan Reedy <tjreedy at udel.edu>
date:        Tue Jan 21 15:36:36 2014 -0500
summary:
  Issue #16630: Make Idle calltips work even when __getattr__ raises.
Initial patch by Roger Serwy.

files:
  Lib/idlelib/CallTips.py                |  100 ++++++------
  Lib/idlelib/idle_test/test_calltips.py |   19 +-
  2 files changed, 66 insertions(+), 53 deletions(-)


diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/CallTips.py
--- a/Lib/idlelib/CallTips.py
+++ b/Lib/idlelib/CallTips.py
@@ -134,56 +134,60 @@
     """Get a string describing the arguments for the given object,
        only if it is callable."""
     arg_text = ""
-    if ob is not None and hasattr(ob, '__call__'):
-        arg_offset = 0
-        if type(ob) in (types.ClassType, types.TypeType):
-            # Look for the highest __init__ in the class chain.
-            fob = _find_constructor(ob)
-            if fob is None:
-                fob = lambda: None
-            else:
-                arg_offset = 1
-        elif type(ob) == types.MethodType:
-            # bit of a hack for methods - turn it into a function
-            # and drop the "self" param for bound methods
-            fob = ob.im_func
-            if ob.im_self:
-                arg_offset = 1
-        elif type(ob.__call__) == types.MethodType:
-            # a callable class instance
-            fob = ob.__call__.im_func
+    try:
+        ob_call = ob.__call__
+    except BaseException:
+        return arg_text
+
+    arg_offset = 0
+    if type(ob) in (types.ClassType, types.TypeType):
+        # Look for the highest __init__ in the class chain.
+        fob = _find_constructor(ob)
+        if fob is None:
+            fob = lambda: None
+        else:
             arg_offset = 1
-        else:
-            fob = ob
-        # Try to build one for Python defined functions
-        if type(fob) in [types.FunctionType, types.LambdaType]:
-            argcount = fob.func_code.co_argcount
-            real_args = fob.func_code.co_varnames[arg_offset:argcount]
-            defaults = fob.func_defaults or []
-            defaults = list(map(lambda name: "=%s" % repr(name), defaults))
-            defaults = [""] * (len(real_args) - len(defaults)) + defaults
-            items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
-            if fob.func_code.co_flags & 0x4:
-                items.append("*args")
-            if fob.func_code.co_flags & 0x8:
-                items.append("**kwds")
-            arg_text = ", ".join(items)
-            arg_text = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", arg_text)
-        # See if we can use the docstring
-        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 arg_text:
-                arg_text += "\n"
-            arg_text += doc[:pos]
+    elif type(ob) == types.MethodType:
+        # bit of a hack for methods - turn it into a function
+        # and drop the "self" param for bound methods
+        fob = ob.im_func
+        if ob.im_self:
+            arg_offset = 1
+    elif type(ob_call) == types.MethodType:
+        # a callable class instance
+        fob = ob_call.im_func
+        arg_offset = 1
+    else:
+        fob = ob
+    # Try to build one for Python defined functions
+    if type(fob) in [types.FunctionType, types.LambdaType]:
+        argcount = fob.func_code.co_argcount
+        real_args = fob.func_code.co_varnames[arg_offset:argcount]
+        defaults = fob.func_defaults or []
+        defaults = list(map(lambda name: "=%s" % repr(name), defaults))
+        defaults = [""] * (len(real_args) - len(defaults)) + defaults
+        items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
+        if fob.func_code.co_flags & 0x4:
+            items.append("*args")
+        if fob.func_code.co_flags & 0x8:
+            items.append("**kwds")
+        arg_text = ", ".join(items)
+        arg_text = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", arg_text)
+    # See if we can use the docstring
+    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 arg_text:
+            arg_text += "\n"
+        arg_text += doc[:pos]
     return arg_text
 
 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
@@ -3,6 +3,7 @@
 CTi = ct.CallTips()  # needed for get_entity test in 2.7
 import types
 
+default_tip = ''
 
 # Test Class TC is used in multiple get_argspec test methods
 class TC(object):
@@ -41,7 +42,6 @@
     # but a red buildbot is better than a user crash (as has happened).
     # For a simple mismatch, change the expected output to the actual.
 
-
     def test_builtins(self):
         # 2.7 puts '()\n' where 3.x does not, other minor differences
 
@@ -65,8 +65,7 @@
         gtest(List.append, append_doc)
 
         gtest(types.MethodType, '()\ninstancemethod(function, instance, class)')
-        gtest(SB(), '')
-
+        gtest(SB(), default_tip)
 
     def test_functions(self):
         def t1(): 'doc'
@@ -92,9 +91,8 @@
     def test_bound_methods(self):
         # test that first parameter is correctly removed from argspec
         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")
 
     def test_no_docstring(self):
         def nd(s): pass
@@ -103,6 +101,17 @@
         self.assertEqual(signature(TC.nd), "(s)")
         self.assertEqual(signature(tc.nd), "()")
 
+    def test_attribute_exception(self):
+        class NoCall(object):
+            def __getattr__(self, name):
+                raise BaseException
+        class Call(NoCall):
+            def __call__(self, ci):
+                pass
+        for meth, mtip  in ((NoCall, '()'), (Call, '()'),
+                            (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