mirror of https://github.com/python/cpython
Issue #16630: Make Idle calltips work even when __getattr__ raises.
Initial patch by Roger Serwy.
This commit is contained in:
parent
65fd0592fb
commit
715476d8e3
|
@ -118,47 +118,49 @@ def get_entity(expression):
|
||||||
|
|
||||||
# The following are used in both get_argspec and tests
|
# The following are used in both get_argspec and tests
|
||||||
_first_param = re.compile('(?<=\()\w*\,?\s*')
|
_first_param = re.compile('(?<=\()\w*\,?\s*')
|
||||||
_default_callable_argspec = "No docstring, see docs."
|
_default_callable_argspec = "See source or doc"
|
||||||
|
|
||||||
def get_argspec(ob):
|
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.
|
For Python-coded functions and methods, the first line is introspected.
|
||||||
Delete 'self' parameter for classes (.__init__) and bound methods.
|
Delete 'self' parameter for classes (.__init__) and bound methods.
|
||||||
The last line is the first line of the doc string. For builtins, this typically
|
The last line is the first line of the doc string. For builtins, this typically
|
||||||
includes the arguments in addition to the return value.
|
includes the arguments in addition to the return value.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
argspec = ""
|
argspec = ""
|
||||||
if hasattr(ob, '__call__'):
|
try:
|
||||||
if isinstance(ob, type):
|
ob_call = ob.__call__
|
||||||
fob = getattr(ob, '__init__', None)
|
except BaseException:
|
||||||
elif isinstance(ob.__call__, types.MethodType):
|
return argspec
|
||||||
fob = ob.__call__
|
if isinstance(ob, type):
|
||||||
else:
|
fob = ob.__init__
|
||||||
fob = ob
|
elif isinstance(ob_call, types.MethodType):
|
||||||
if isinstance(fob, (types.FunctionType, types.MethodType)):
|
fob = ob_call
|
||||||
argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
|
else:
|
||||||
if (isinstance(ob, (type, types.MethodType)) or
|
fob = ob
|
||||||
isinstance(ob.__call__, types.MethodType)):
|
if isinstance(fob, (types.FunctionType, types.MethodType)):
|
||||||
argspec = _first_param.sub("", argspec)
|
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):
|
if isinstance(ob_call, types.MethodType):
|
||||||
doc = ob.__call__.__doc__
|
doc = ob_call.__doc__
|
||||||
else:
|
else:
|
||||||
doc = getattr(ob, "__doc__", "")
|
doc = getattr(ob, "__doc__", "")
|
||||||
if doc:
|
if doc:
|
||||||
doc = doc.lstrip()
|
doc = doc.lstrip()
|
||||||
pos = doc.find("\n")
|
pos = doc.find("\n")
|
||||||
if pos < 0 or pos > 70:
|
if pos < 0 or pos > 70:
|
||||||
pos = 70
|
pos = 70
|
||||||
if argspec:
|
if argspec:
|
||||||
argspec += "\n"
|
argspec += "\n"
|
||||||
argspec += doc[:pos]
|
argspec += doc[:pos]
|
||||||
if not argspec:
|
if not argspec:
|
||||||
argspec = _default_callable_argspec
|
argspec = _default_callable_argspec
|
||||||
return argspec
|
return argspec
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from unittest import main
|
from unittest import main
|
||||||
main('idlelib.idle_test.test_calltips', verbosity=2, exit=False)
|
main('idlelib.idle_test.test_calltips', verbosity=2)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import unittest
|
||||||
import idlelib.CallTips as ct
|
import idlelib.CallTips as ct
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
default_tip = ct._default_callable_argspec
|
||||||
|
|
||||||
# Test Class TC is used in multiple get_argspec test methods
|
# Test Class TC is used in multiple get_argspec test methods
|
||||||
class TC():
|
class TC():
|
||||||
|
@ -63,7 +64,7 @@ class Get_signatureTest(unittest.TestCase):
|
||||||
gtest(List.append, append_doc)
|
gtest(List.append, append_doc)
|
||||||
|
|
||||||
gtest(types.MethodType, "method(function, instance)")
|
gtest(types.MethodType, "method(function, instance)")
|
||||||
gtest(SB(), ct._default_callable_argspec)
|
gtest(SB(), default_tip)
|
||||||
|
|
||||||
def test_functions(self):
|
def test_functions(self):
|
||||||
def t1(): 'doc'
|
def t1(): 'doc'
|
||||||
|
@ -88,13 +89,13 @@ class Get_signatureTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_bound_methods(self):
|
def test_bound_methods(self):
|
||||||
# test that first parameter is correctly removed from argspec
|
# 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)"),
|
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(meth), mtip + "\ndoc")
|
||||||
self.assertEqual(signature(tc), "(ci)\ndoc")
|
|
||||||
# directly test that re works to delete first parameter even when it
|
def test_non_ascii_name(self):
|
||||||
# non-ascii chars, such as various forms of A.
|
# 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)"
|
uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
|
||||||
assert ct._first_param.sub('', uni) == '(a)'
|
assert ct._first_param.sub('', uni) == '(a)'
|
||||||
|
|
||||||
|
@ -105,6 +106,17 @@ class Get_signatureTest(unittest.TestCase):
|
||||||
self.assertEqual(signature(TC.nd), "(s)")
|
self.assertEqual(signature(TC.nd), "(s)")
|
||||||
self.assertEqual(signature(tc.nd), "()")
|
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):
|
def test_non_callables(self):
|
||||||
for obj in (0, 0.0, '0', b'0', [], {}):
|
for obj in (0, 0.0, '0', b'0', [], {}):
|
||||||
self.assertEqual(signature(obj), '')
|
self.assertEqual(signature(obj), '')
|
||||||
|
|
Loading…
Reference in New Issue