Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3969228

Browse files
committedAug 3, 2017
bpo-19903: Change to inspect.signature for calltips
This commit change the get_argspec from using inspect.getfullargspec to inspect.signature. It will improve the tip message for use. Also, if object is not callable, now will return this message for user, not blank tips. If the methods has an invalid method signature, it will also return message to user.
1 parent d4069de commit 3969228

File tree

2 files changed

+61
-20
lines changed

2 files changed

+61
-20
lines changed
 

‎Lib/idlelib/calltips.py

+24-8
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ def get_entity(expression):
122122
_INDENT = ' '*4 # for wrapped signatures
123123
_first_param = re.compile(r'(?<=\()\w*\,?\s*')
124124
_default_callable_argspec = "See source or doc"
125+
_invalid_method = "invalid method signature"
126+
_argument_positional = "('/' marks preceding arguments as positional-only)"
125127

126128

127129
def get_argspec(ob):
@@ -138,17 +140,31 @@ def get_argspec(ob):
138140
ob_call = ob.__call__
139141
except BaseException:
140142
return argspec
141-
if isinstance(ob, type):
142-
fob = ob.__init__
143-
elif isinstance(ob_call, types.MethodType):
143+
if isinstance(ob_call, types.MethodType):
144144
fob = ob_call
145145
else:
146146
fob = ob
147-
if isinstance(fob, (types.FunctionType, types.MethodType)):
148-
argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
149-
if (isinstance(ob, (type, types.MethodType)) or
150-
isinstance(ob_call, types.MethodType)):
151-
argspec = _first_param.sub("", argspec)
147+
148+
try:
149+
argspec = str(inspect.signature(fob))
150+
except ValueError as err:
151+
msg = str(err)
152+
if msg.startswith(_invalid_method):
153+
argspec = _invalid_method
154+
return argspec
155+
elif msg.startswith('no signature found for'):
156+
"""If no signature found for function or method"""
157+
pass
158+
else:
159+
"""Callable is not supported by signature"""
160+
pass
161+
162+
if '/' in argspec:
163+
"""Using AC's positional argument should add the explain"""
164+
argspec = '\n'.join([argspec, _argument_positional])
165+
if isinstance(fob, type) and argspec == '()':
166+
"""fob with no argument, use default callable argspec"""
167+
argspec = _default_callable_argspec
152168

153169
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
154170
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])

‎Lib/idlelib/idle_test/test_calltips.py

+37-12
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,36 @@ def test_builtins(self):
4646

4747
# Python class that inherits builtin methods
4848
class List(list): "List() doc"
49+
4950
# Simulate builtin with no docstring for default tip test
5051
class SB: __call__ = None
5152

5253
def gtest(obj, out):
5354
self.assertEqual(signature(obj), out)
5455

5556
if List.__doc__ is not None:
56-
gtest(List, List.__doc__)
57+
gtest(List, '(iterable=(), /)\n' + ct._argument_positional + '\n' +
58+
List.__doc__)
5759
gtest(list.__new__,
58-
'Create and return a new object. See help(type) for accurate signature.')
60+
'(*args, **kwargs)\nCreate and return a new object. See help(type) for accurate signature.')
5961
gtest(list.__init__,
62+
'(self, /, *args, **kwargs)\n' + ct._argument_positional + '\n' +
6063
'Initialize self. See help(type(self)) for accurate signature.')
61-
append_doc = "Append object to the end of the list."
62-
gtest(list.append, append_doc)
63-
gtest([].append, append_doc)
64-
gtest(List.append, append_doc)
64+
append_doc = ct._argument_positional + '\n' + "Append object to the end of the list."
65+
gtest(list.append, '(self, object, /)\n' + append_doc)
66+
gtest(List.append, '(self, object, /)\n' + append_doc)
67+
gtest([].append, '(object, /)\n' + append_doc)
6568

6669
gtest(types.MethodType, "method(function, instance)")
6770
gtest(SB(), default_tip)
71+
import re
72+
p = re.compile('')
73+
gtest(re.sub, '''(pattern, repl, string, count=0, flags=0)\nReturn the string obtained by replacing the leftmost
74+
non-overlapping occurrences of the pattern in string by the
75+
replacement repl. repl can be either a string or a callable;
76+
if a string, backslash escapes in it are processed. If it is
77+
a callable, it's passed the match object and must return''')
78+
gtest(p.sub, '''(repl, string, count=0)\nReturn the string obtained by replacing the leftmost non-overlapping occurrences o...''')
6879

6980
def test_signature_wrap(self):
7081
if textwrap.TextWrapper.__doc__ is not None:
@@ -132,12 +143,20 @@ def test_starred_parameter(self):
132143
# test that starred first parameter is *not* removed from argspec
133144
class C:
134145
def m1(*args): pass
135-
def m2(**kwds): pass
136146
c = C()
137-
for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),
138-
(C.m2, "(**kwds)"), (c.m2, "(**kwds)"),):
147+
for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
139148
self.assertEqual(signature(meth), mtip)
140149

150+
def test_invalid_method_signature(self):
151+
class C:
152+
def m2(**kwargs): pass
153+
class Test:
154+
def __call__(*, a): pass
155+
156+
mtip = ct._invalid_method
157+
self.assertEqual(signature(C().m2), mtip)
158+
self.assertEqual(signature(Test()), mtip)
159+
141160
def test_non_ascii_name(self):
142161
# test that re works to delete a first parameter name that
143162
# includes non-ascii chars, such as various forms of A.
@@ -156,17 +175,23 @@ def test_attribute_exception(self):
156175
class NoCall:
157176
def __getattr__(self, name):
158177
raise BaseException
159-
class Call(NoCall):
178+
class CallA(NoCall):
179+
def __call__(oui, a, b, c):
180+
pass
181+
class CallB(NoCall):
160182
def __call__(self, ci):
161183
pass
162-
for meth, mtip in ((NoCall, default_tip), (Call, default_tip),
163-
(NoCall(), ''), (Call(), '(ci)')):
184+
185+
for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
186+
(NoCall(), ''), (CallA(), '(a, b, c)'),
187+
(CallB(), '(ci)')):
164188
self.assertEqual(signature(meth), mtip)
165189

166190
def test_non_callables(self):
167191
for obj in (0, 0.0, '0', b'0', [], {}):
168192
self.assertEqual(signature(obj), '')
169193

194+
170195
class Get_entityTest(unittest.TestCase):
171196
def test_bad_entity(self):
172197
self.assertIsNone(ct.get_entity('1/0'))

0 commit comments

Comments
 (0)
Please sign in to comment.