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