bpo-38689: avoid IDLE hanging when calltip fails getting a signature (GH-17152)

Inspect.signature failed on the test case because its isinstance call raised.
This commit is contained in:
Tal Einat 2020-04-04 06:05:58 +03:00 committed by GitHub
parent 6e623ff9d2
commit 52013e5b6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 32 additions and 15 deletions

View File

@ -3,6 +3,9 @@ Released on 2020-10-05?
======================================
bpo-38689: IDLE will no longer freeze when inspect.signature fails
when fetching a calltip.
bpo-27115: For 'Go to Line', use a Query entry box subclass with
IDLE standard behavior and improved error checking.

View File

@ -129,20 +129,22 @@ def get_argspec(ob):
empty line or _MAX_LINES. For builtins, this typically includes
the arguments in addition to the return value.
'''
argspec = default = ""
# Determine function object fob to inspect.
try:
ob_call = ob.__call__
except BaseException:
return default
except BaseException: # Buggy user object could raise anything.
return '' # No popup for non-callables.
fob = ob_call if isinstance(ob_call, types.MethodType) else ob
# Initialize argspec and wrap it to get lines.
try:
argspec = str(inspect.signature(fob))
except ValueError as err:
except Exception as err:
msg = str(err)
if msg.startswith(_invalid_method):
return _invalid_method
else:
argspec = ''
if '/' in argspec and len(argspec) < _MAX_COLS - len(_argument_positional):
# Add explanation TODO remove after 3.7, before 3.9.
@ -154,6 +156,7 @@ def get_argspec(ob):
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
# Augment lines from docstring, if any, and join to get argspec.
if isinstance(ob_call, types.MethodType):
doc = ob_call.__doc__
else:
@ -167,9 +170,8 @@ def get_argspec(ob):
line = line[: _MAX_COLS - 3] + '...'
lines.append(line)
argspec = '\n'.join(lines)
if not argspec:
argspec = _default_callable_argspec
return argspec
return argspec or _default_callable_argspec
if __name__ == '__main__':

View File

@ -219,20 +219,30 @@ bytes() -> empty bytes object''')
with self.subTest(meth=meth, mtip=mtip):
self.assertEqual(get_spec(meth), mtip)
def test_attribute_exception(self):
def test_buggy_getattr_class(self):
class NoCall:
def __getattr__(self, name):
raise BaseException
def __getattr__(self, name): # Not invoked for class attribute.
raise IndexError # Bug.
class CallA(NoCall):
def __call__(oui, a, b, c):
def __call__(self, ci): # Bug does not matter.
pass
class CallB(NoCall):
def __call__(self, ci):
def __call__(oui, a, b, c): # Non-standard 'self'.
pass
for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
(NoCall(), ''), (CallA(), '(a, b, c)'),
(CallB(), '(ci)')):
(NoCall(), ''), (CallA(), '(ci)'),
(CallB(), '(a, b, c)')):
with self.subTest(meth=meth, mtip=mtip):
self.assertEqual(get_spec(meth), mtip)
def test_metaclass_class(self): # Failure case for issue 38689.
class Type(type): # Type() requires 3 type args, returns class.
__class__ = property({}.__getitem__, {}.__setitem__)
class Object(metaclass=Type):
__slots__ = '__class__'
for meth, mtip in ((Type, default_tip), (Object, default_tip),
(Object(), '')):
with self.subTest(meth=meth, mtip=mtip):
self.assertEqual(get_spec(meth), mtip)

View File

@ -0,0 +1,2 @@
IDLE will no longer freeze when inspect.signature fails when fetching
a calltip.