@@ -100,52 +100,53 @@ def fetch_tip(self, expression):
100100 return rpcclt .remotecall ("exec" , "get_the_calltip" ,
101101 (expression ,), {})
102102 else :
103- entity = self .get_entity (expression )
104- return get_argspec (entity )
103+ return get_argspec (get_entity (expression ))
105104
106- def get_entity (self , expression ):
107- """Return the object corresponding to expression evaluated
108- in a namespace spanning sys.modules and __main.dict__.
109- """
110- if expression :
111- namespace = sys .modules .copy ()
112- namespace .update (__main__ .__dict__ )
113- try :
114- return eval (expression , namespace )
115- except BaseException :
116- # An uncaught exception closes idle, and eval can raise any
117- # exception, especially if user classes are involved.
118- return None
119-
120- def _find_constructor (class_ob ):
121- "Find the nearest __init__() in the class tree."
122- try :
123- return class_ob .__init__ .__func__
124- except AttributeError :
125- for base in class_ob .__bases__ :
126- init = _find_constructor (base )
127- if init :
128- return init
129- return None
105+ def get_entity (expression ):
106+ """Return the object corresponding to expression evaluated
107+ in a namespace spanning sys.modules and __main.dict__.
108+ """
109+ if expression :
110+ namespace = sys .modules .copy ()
111+ namespace .update (__main__ .__dict__ )
112+ try :
113+ return eval (expression , namespace )
114+ except BaseException :
115+ # An uncaught exception closes idle, and eval can raise any
116+ # exception, especially if user classes are involved.
117+ return None
118+
119+ # The following are used in both get_argspec and tests
120+ _self_pat = re .compile ('self\,?\s*' )
121+ _default_callable_argspec = "No docstring, see docs."
130122
131123def get_argspec (ob ):
132- """Get a string describing the arguments for the given object,
133- only if it is callable."""
124+ '''Return a string describing the arguments and return of a callable object.
125+
126+ For Python-coded functions and methods, the first line is introspected.
127+ Delete 'self' parameter for classes (.__init__) and bound methods.
128+ The last line is the first line of the doc string. For builtins, this typically
129+ includes the arguments in addition to the return value.
130+
131+ '''
134132 argspec = ""
135- if ob is not None and hasattr (ob , '__call__' ):
133+ if hasattr (ob , '__call__' ):
136134 if isinstance (ob , type ):
137- fob = _find_constructor (ob )
138- if fob is None :
139- fob = lambda : None
140- elif isinstance (ob , types .MethodType ):
141- fob = ob .__func__
135+ fob = getattr (ob , '__init__' , None )
136+ elif isinstance (ob .__call__ , types .MethodType ):
137+ fob = ob .__call__
142138 else :
143139 fob = ob
144- if isinstance (fob , (types .FunctionType , types .LambdaType )):
140+ if isinstance (fob , (types .FunctionType , types .MethodType )):
145141 argspec = inspect .formatargspec (* inspect .getfullargspec (fob ))
146- pat = re .compile ('self\,?\s*' )
147- argspec = pat .sub ("" , argspec )
148- doc = getattr (ob , "__doc__" , "" )
142+ if (isinstance (ob , (type , types .MethodType )) or
143+ isinstance (ob .__call__ , types .MethodType )):
144+ argspec = _self_pat .sub ("" , argspec )
145+
146+ if isinstance (ob .__call__ , types .MethodType ):
147+ doc = ob .__call__ .__doc__
148+ else :
149+ doc = getattr (ob , "__doc__" , "" )
149150 if doc :
150151 doc = doc .lstrip ()
151152 pos = doc .find ("\n " )
@@ -154,13 +155,16 @@ def get_argspec(ob):
154155 if argspec :
155156 argspec += "\n "
156157 argspec += doc [:pos ]
158+ if not argspec :
159+ argspec = _default_callable_argspec
157160 return argspec
158161
159162#################################################
160163#
161- # Test code
162- #
164+ # Test code tests CallTips.fetch_tip, get_entity, and get_argspec
165+
163166def main ():
167+ # Putting expected in docstrings results in doubled tips for test
164168 def t1 (): "()"
165169 def t2 (a , b = None ): "(a, b=None)"
166170 def t3 (a , * args ): "(a, *args)"
@@ -170,39 +174,88 @@ def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
170174
171175 class TC (object ):
172176 "(ai=None, *b)"
173- def __init__ (self , ai = None , * b ): "(ai=None, *b)"
174- def t1 (self ): "()"
175- def t2 (self , ai , b = None ): "(ai, b=None)"
176- def t3 (self , ai , * args ): "(ai, *args)"
177- def t4 (self , * args ): "(*args)"
178- def t5 (self , ai , * args ): "(ai, *args)"
179- def t6 (self , ai , b = None , * args , ** kw ): "(ai, b=None, *args, **kw)"
180-
181- __main__ .__dict__ .update (locals ())
182-
183- def test (tests ):
184- ct = CallTips ()
185- failed = []
186- for t in tests :
187- expected = t .__doc__ + "\n " + t .__doc__
188- name = t .__name__
189- # exercise fetch_tip(), not just get_argspec()
190- try :
191- qualified_name = "%s.%s" % (t .__self__ .__class__ .__name__ , name )
192- except AttributeError :
193- qualified_name = name
194- argspec = ct .fetch_tip (qualified_name )
195- if argspec != expected :
196- failed .append (t )
197- fmt = "%s - expected %s, but got %s"
198- print (fmt % (t .__name__ , expected , get_argspec (t )))
199- print ("%d of %d tests failed" % (len (failed ), len (tests )))
177+ def __init__ (self , ai = None , * b ): "(self, ai=None, *b)"
178+ def t1 (self ): "(self)"
179+ def t2 (self , ai , b = None ): "(self, ai, b=None)"
180+ def t3 (self , ai , * args ): "(self, ai, *args)"
181+ def t4 (self , * args ): "(self, *args)"
182+ def t5 (self , ai , * args ): "(self, ai, *args)"
183+ def t6 (self , ai , b = None , * args , ** kw ): "(self, ai, b=None, *args, **kw)"
184+ @classmethod
185+ def cm (cls , a ): "(cls, a)"
186+ @staticmethod
187+ def sm (b ): "(b)"
188+ def __call__ (self , ci ): "(ci)"
200189
201190 tc = TC ()
202- tests = (t1 , t2 , t3 , t4 , t5 , t6 ,
203- TC , tc .t1 , tc .t2 , tc .t3 , tc .t4 , tc .t5 , tc .t6 )
204191
205- test (tests )
192+ # Python classes that inherit builtin methods
193+ class Int (int ): "Int(x[, base]) -> integer"
194+ class List (list ): "List() -> new empty list"
195+ # Simulate builtin with no docstring for default argspec test
196+ class SB : __call__ = None
197+
198+ __main__ .__dict__ .update (locals ()) # required for get_entity eval()
199+
200+ num_tests = num_fail = 0
201+ tip = CallTips ().fetch_tip
202+
203+ def test (expression , expected ):
204+ nonlocal num_tests , num_fail
205+ num_tests += 1
206+ argspec = tip (expression )
207+ if argspec != expected :
208+ num_fail += 1
209+ fmt = "%s - expected\n %r\n - but got\n %r"
210+ print (fmt % (expression , expected , argspec ))
211+
212+ def test_builtins ():
213+ # if first line of a possibly multiline compiled docstring changes,
214+ # must change corresponding test string
215+ test ('int' , "int(x[, base]) -> integer" )
216+ test ('Int' , Int .__doc__ )
217+ test ('types.MethodType' , "method(function, instance)" )
218+ test ('list' , "list() -> new empty list" )
219+ test ('List' , List .__doc__ )
220+ test ('list.__new__' ,
221+ 'T.__new__(S, ...) -> a new object with type S, a subtype of T' )
222+ test ('list.__init__' ,
223+ 'x.__init__(...) initializes x; see help(type(x)) for signature' )
224+ append_doc = "L.append(object) -> None -- append object to end"
225+ test ('list.append' , append_doc )
226+ test ('[].append' , append_doc )
227+ test ('List.append' , append_doc )
228+ test ('SB()' , _default_callable_argspec )
229+
230+ def test_funcs ():
231+ for func in (t1 , t2 , t3 , t4 , t5 , t6 , TC ,):
232+ fdoc = func .__doc__
233+ test (func .__name__ , fdoc + "\n " + fdoc )
234+ for func in (TC .t1 , TC .t2 , TC .t3 , TC .t4 , TC .t5 , TC .t6 , TC .cm , TC .sm ):
235+ fdoc = func .__doc__
236+ test ('TC.' + func .__name__ , fdoc + "\n " + fdoc )
237+
238+ def test_methods ():
239+ for func in (tc .t1 , tc .t2 , tc .t3 , tc .t4 , tc .t5 , tc .t6 ):
240+ fdoc = func .__doc__
241+ test ('tc.' + func .__name__ , _self_pat .sub ("" , fdoc ) + "\n " + fdoc )
242+ fdoc = tc .__call__ .__doc__
243+ test ('tc' , fdoc + "\n " + fdoc )
244+
245+ def test_non_callables ():
246+ # expression evaluates, but not to a callable
247+ for expr in ('0' , '0.0' 'num_tests' , b'num_tests' , '[]' , '{}' ):
248+ test (expr , '' )
249+ # expression does not evaluate, but raises an exception
250+ for expr in ('1a' , 'xyx' , 'num_tests.xyz' , '[int][1]' , '{0:int}[1]' ):
251+ test (expr , '' )
252+
253+ test_builtins ()
254+ test_funcs ()
255+ test_non_callables ()
256+ test_methods ()
257+
258+ print ("%d of %d tests failed" % (num_fail , num_tests ))
206259
207260if __name__ == '__main__' :
208261 main ()
0 commit comments