]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/py/introspect.py
Patch from Prabhu Ramachandran to make introspection of lazily
[wxWidgets.git] / wxPython / wx / py / introspect.py
1 """Provides a variety of introspective-type support functions for
2 things like call tips and command auto completion."""
3
4 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
5 __cvsid__ = "$Id$"
6 __revision__ = "$Revision$"[11:-2]
7
8 from __future__ import nested_scopes
9
10 import cStringIO
11 import inspect
12 import sys
13 import tokenize
14 import types
15
16 def getAutoCompleteList(command='', locals=None, includeMagic=1,
17 includeSingle=1, includeDouble=1):
18 """Return list of auto-completion options for command.
19
20 The list of options will be based on the locals namespace."""
21 attributes = []
22 # Get the proper chunk of code from the command.
23 root = getRoot(command, terminator='.')
24 try:
25 if locals is not None:
26 object = eval(root, locals)
27 else:
28 object = eval(root)
29 except:
30 pass
31 else:
32 attributes = getAttributeNames(object, includeMagic,
33 includeSingle, includeDouble)
34 return attributes
35
36 def getAttributeNames(object, includeMagic=1, includeSingle=1,
37 includeDouble=1):
38 """Return list of unique attributes, including inherited, for object."""
39 attributes = []
40 dict = {}
41 if not hasattrAlwaysReturnsTrue(object):
42 # Add some attributes that don't always get picked up.
43 special_attrs = ['__bases__', '__class__', '__dict__', '__name__',
44 'func_closure', 'func_code', 'func_defaults',
45 'func_dict', 'func_doc', 'func_globals', 'func_name']
46 attributes += [attr for attr in special_attrs \
47 if hasattr(object, attr)]
48 if includeMagic:
49 try: attributes += object._getAttributeNames()
50 except: pass
51 # Get all attribute names.
52 attrdict = getAllAttributeNames(object)
53 # Store the object's dir.
54 object_dir = dir(object)
55 for (str_obj, technique, count), attrlist in attrdict.items():
56 # This complexity is necessary to avoid accessing all the
57 # attributes of the object. This is very handy for objects
58 # whose attributes are lazily evaluated.
59 if str(object) == str_obj and technique == 'dir':
60 attributes += attrlist
61 else:
62 attributes += [attr for attr in attrlist \
63 if attr not in object_dir and hasattr(object, attr)]
64
65 # Remove duplicates from the attribute list.
66 for item in attributes:
67 dict[item] = None
68 attributes = dict.keys()
69 # new-style swig wrappings can result in non-string attributes
70 # e.g. ITK http://www.itk.org/
71 attributes = [attribute for attribute in attributes \
72 if type(attribute) == str]
73 attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
74 if not includeSingle:
75 attributes = filter(lambda item: item[0]!='_' \
76 or item[1]=='_', attributes)
77 if not includeDouble:
78 attributes = filter(lambda item: item[:2]!='__', attributes)
79 return attributes
80
81 def hasattrAlwaysReturnsTrue(object):
82 return hasattr(object, 'bogu5_123_aTTri8ute')
83
84 def getAllAttributeNames(object):
85 """Return dict of all attributes, including inherited, for an object.
86
87 Recursively walk through a class and all base classes.
88 """
89 attrdict = {} # (object, technique, count): [list of attributes]
90 # !!!
91 # Do Not use hasattr() as a test anywhere in this function,
92 # because it is unreliable with remote objects: xmlrpc, soap, etc.
93 # They always return true for hasattr().
94 # !!!
95 try:
96 # Yes, this can fail if object is an instance of a class with
97 # __str__ (or __repr__) having a bug or raising an
98 # exception. :-(
99 key = str(object)
100 except:
101 key = 'anonymous'
102 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
103 wakeupcall = dir(object)
104 del wakeupcall
105 # Get attributes available through the normal convention.
106 attributes = dir(object)
107 attrdict[(key, 'dir', len(attributes))] = attributes
108 # Get attributes from the object's dictionary, if it has one.
109 try:
110 attributes = object.__dict__.keys()
111 attributes.sort()
112 except: # Must catch all because object might have __getattr__.
113 pass
114 else:
115 attrdict[(key, '__dict__', len(attributes))] = attributes
116 # For a class instance, get the attributes for the class.
117 try:
118 klass = object.__class__
119 except: # Must catch all because object might have __getattr__.
120 pass
121 else:
122 if klass is object:
123 # Break a circular reference. This happens with extension
124 # classes.
125 pass
126 else:
127 attrdict.update(getAllAttributeNames(klass))
128 # Also get attributes from any and all parent classes.
129 try:
130 bases = object.__bases__
131 except: # Must catch all because object might have __getattr__.
132 pass
133 else:
134 if isinstance(bases, types.TupleType):
135 for base in bases:
136 if type(base) is types.TypeType:
137 # Break a circular reference. Happens in Python 2.2.
138 pass
139 else:
140 attrdict.update(getAllAttributeNames(base))
141 return attrdict
142
143 def getCallTip(command='', locals=None):
144 """For a command, return a tuple of object name, argspec, tip text.
145
146 The call tip information will be based on the locals namespace."""
147 calltip = ('', '', '') # object name, argspec, tip text.
148 # Get the proper chunk of code from the command.
149 root = getRoot(command, terminator='(')
150 try:
151 if locals is not None:
152 object = eval(root, locals)
153 else:
154 object = eval(root)
155 except:
156 return calltip
157 name = ''
158 object, dropSelf = getBaseObject(object)
159 try:
160 name = object.__name__
161 except AttributeError:
162 pass
163 tip1 = ''
164 argspec = ''
165 if inspect.isbuiltin(object):
166 # Builtin functions don't have an argspec that we can get.
167 pass
168 elif inspect.isfunction(object):
169 # tip1 is a string like: "getCallTip(command='', locals=None)"
170 argspec = apply(inspect.formatargspec, inspect.getargspec(object))
171 if dropSelf:
172 # The first parameter to a method is a reference to an
173 # instance, usually coded as "self", and is usually passed
174 # automatically by Python; therefore we want to drop it.
175 temp = argspec.split(',')
176 if len(temp) == 1: # No other arguments.
177 argspec = '()'
178 elif temp[0][:2] == '(*': # first param is like *args, not self
179 pass
180 else: # Drop the first argument.
181 argspec = '(' + ','.join(temp[1:]).lstrip()
182 tip1 = name + argspec
183 doc = ''
184 if callable(object):
185 try:
186 doc = inspect.getdoc(object)
187 except:
188 pass
189 if doc:
190 # tip2 is the first separated line of the docstring, like:
191 # "Return call tip text for a command."
192 # tip3 is the rest of the docstring, like:
193 # "The call tip information will be based on ... <snip>
194 firstline = doc.split('\n')[0].lstrip()
195 if tip1 == firstline or firstline[:len(name)+1] == name+'(':
196 tip1 = ''
197 else:
198 tip1 += '\n\n'
199 docpieces = doc.split('\n\n')
200 tip2 = docpieces[0]
201 tip3 = '\n\n'.join(docpieces[1:])
202 tip = '%s%s\n\n%s' % (tip1, tip2, tip3)
203 else:
204 tip = tip1
205 calltip = (name, argspec[1:-1], tip.strip())
206 return calltip
207
208 def getRoot(command, terminator=None):
209 """Return the rightmost root portion of an arbitrary Python command.
210
211 Return only the root portion that can be eval()'d without side
212 effects. The command would normally terminate with a '(' or
213 '.'. The terminator and anything after the terminator will be
214 dropped."""
215 command = command.split('\n')[-1]
216 if command.startswith(sys.ps2):
217 command = command[len(sys.ps2):]
218 command = command.lstrip()
219 command = rtrimTerminus(command, terminator)
220 tokens = getTokens(command)
221 if not tokens:
222 return ''
223 if tokens[-1][0] is tokenize.ENDMARKER:
224 # Remove the end marker.
225 del tokens[-1]
226 if not tokens:
227 return ''
228 if terminator == '.' and \
229 (tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP):
230 # Trap decimals in numbers, versus the dot operator.
231 return ''
232 else:
233 # Strip off the terminator.
234 if terminator and command.endswith(terminator):
235 size = 0 - len(terminator)
236 command = command[:size]
237 command = command.rstrip()
238 tokens = getTokens(command)
239 tokens.reverse()
240 line = ''
241 start = None
242 prefix = ''
243 laststring = '.'
244 emptyTypes = ('[]', '()', '{}')
245 for token in tokens:
246 tokentype = token[0]
247 tokenstring = token[1]
248 line = token[4]
249 if tokentype is tokenize.ENDMARKER:
250 continue
251 if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
252 and laststring != '.':
253 # We've reached something that's not part of the root.
254 if prefix and line[token[3][1]] != ' ':
255 # If it doesn't have a space after it, remove the prefix.
256 prefix = ''
257 break
258 if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
259 or (tokentype is tokenize.OP and tokenstring == '.'):
260 if prefix:
261 # The prefix isn't valid because it comes after a dot.
262 prefix = ''
263 break
264 else:
265 # start represents the last known good point in the line.
266 start = token[2][1]
267 elif len(tokenstring) == 1 and tokenstring in ('[({])}'):
268 # Remember, we're working backwords.
269 # So prefix += tokenstring would be wrong.
270 if prefix in emptyTypes and tokenstring in ('[({'):
271 # We've already got an empty type identified so now we
272 # are in a nested situation and we can break out with
273 # what we've got.
274 break
275 else:
276 prefix = tokenstring + prefix
277 else:
278 # We've reached something that's not part of the root.
279 break
280 laststring = tokenstring
281 if start is None:
282 start = len(line)
283 root = line[start:]
284 if prefix in emptyTypes:
285 # Empty types are safe to be eval()'d and introspected.
286 root = prefix + root
287 return root
288
289 def getTokens(command):
290 """Return list of token tuples for command."""
291 command = str(command) # In case the command is unicode, which fails.
292 f = cStringIO.StringIO(command)
293 # tokens is a list of token tuples, each looking like:
294 # (type, string, (srow, scol), (erow, ecol), line)
295 tokens = []
296 # Can't use list comprehension:
297 # tokens = [token for token in tokenize.generate_tokens(f.readline)]
298 # because of need to append as much as possible before TokenError.
299 try:
300 ## This code wasn't backward compatible with Python 2.1.3.
301 ##
302 ## for token in tokenize.generate_tokens(f.readline):
303 ## tokens.append(token)
304
305 # This works with Python 2.1.3 (with nested_scopes).
306 def eater(*args):
307 tokens.append(args)
308 tokenize.tokenize_loop(f.readline, eater)
309 except tokenize.TokenError:
310 # This is due to a premature EOF, which we expect since we are
311 # feeding in fragments of Python code.
312 pass
313 return tokens
314
315 def rtrimTerminus(command, terminator=None):
316 """Return command minus anything that follows the final terminator."""
317 if terminator:
318 pieces = command.split(terminator)
319 if len(pieces) > 1:
320 command = terminator.join(pieces[:-1]) + terminator
321 return command
322
323 def getBaseObject(object):
324 """Return base object and dropSelf indicator for an object."""
325 if inspect.isbuiltin(object):
326 # Builtin functions don't have an argspec that we can get.
327 dropSelf = 0
328 elif inspect.ismethod(object):
329 # Get the function from the object otherwise
330 # inspect.getargspec() complains that the object isn't a
331 # Python function.
332 try:
333 if object.im_self is None:
334 # This is an unbound method so we do not drop self
335 # from the argspec, since an instance must be passed
336 # as the first arg.
337 dropSelf = 0
338 else:
339 dropSelf = 1
340 object = object.im_func
341 except AttributeError:
342 dropSelf = 0
343 elif inspect.isclass(object):
344 # Get the __init__ method function for the class.
345 constructor = getConstructor(object)
346 if constructor is not None:
347 object = constructor
348 dropSelf = 1
349 else:
350 dropSelf = 0
351 elif callable(object):
352 # Get the __call__ method instead.
353 try:
354 object = object.__call__.im_func
355 dropSelf = 1
356 except AttributeError:
357 dropSelf = 0
358 else:
359 dropSelf = 0
360 return object, dropSelf
361
362 def getConstructor(object):
363 """Return constructor for class object, or None if there isn't one."""
364 try:
365 return object.__init__.im_func
366 except AttributeError:
367 for base in object.__bases__:
368 constructor = getConstructor(base)
369 if constructor is not None:
370 return constructor
371 return None