]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/py/introspect.py
1 """Provides a variety of introspective-type support functions for
2 things like call tips and command auto completion."""
4 __author__
= "Patrick K. O'Brien <pobrien@orbtech.com>"
6 __revision__
= "$Revision$"[11:-2]
8 from __future__
import nested_scopes
16 def getAutoCompleteList(command
='', locals=None, includeMagic
=1,
17 includeSingle
=1, includeDouble
=1):
18 """Return list of auto-completion options for command.
20 The list of options will be based on the locals namespace."""
22 # Get the proper chunk of code from the command.
23 root
= getRoot(command
, terminator
='.')
25 if locals is not None:
26 object = eval(root
, locals)
32 attributes
= getAttributeNames(object, includeMagic
,
33 includeSingle
, includeDouble
)
36 def getAttributeNames(object, includeMagic
=1, includeSingle
=1,
38 """Return list of unique attributes, including inherited, for object."""
41 if not hasattrAlwaysReturnsTrue(object):
42 # Add some attributes that don't always get picked up. If
43 # they don't apply, they'll get filtered out at the end.
44 attributes
+= ['__bases__', '__class__', '__dict__', '__name__',
45 'func_closure', 'func_code', 'func_defaults',
46 'func_dict', 'func_doc', 'func_globals', 'func_name']
48 try: attributes
+= object._getAttributeNames
()
50 # Get all attribute names.
51 attrdict
= getAllAttributeNames(object)
52 for attrlist
in attrdict
.values():
53 attributes
+= attrlist
54 # Remove duplicates from the attribute list.
55 for item
in attributes
:
57 attributes
= dict.keys()
58 attributes
.sort(lambda x
, y
: cmp(x
.upper(), y
.upper()))
60 attributes
= filter(lambda item
: item
[0]!='_' \
61 or item
[1]=='_', attributes
)
63 attributes
= filter(lambda item
: item
[:2]!='__', attributes
)
64 # Make sure we haven't picked up any bogus attributes somehow.
65 attributes
= [attribute
for attribute
in attributes \
66 if hasattr(object, attribute
)]
69 def hasattrAlwaysReturnsTrue(object):
70 return hasattr(object, 'bogu5_123_aTTri8ute')
72 def getAllAttributeNames(object):
73 """Return dict of all attributes, including inherited, for an object.
75 Recursively walk through a class and all base classes.
77 attrdict
= {} # (object, technique, count): [list of attributes]
79 # Do Not use hasattr() as a test anywhere in this function,
80 # because it is unreliable with remote objects: xmlrpc, soap, etc.
81 # They always return true for hasattr().
84 # Yes, this can fail if object is an instance of a class with
85 # __str__ (or __repr__) having a bug or raising an
90 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
91 wakeupcall
= dir(object)
93 # Get attributes available through the normal convention.
94 attributes
= dir(object)
95 attrdict
[(key
, 'dir', len(attributes
))] = attributes
96 # Get attributes from the object's dictionary, if it has one.
98 attributes
= object.__dict
__.keys()
100 except: # Must catch all because object might have __getattr__.
103 attrdict
[(key
, '__dict__', len(attributes
))] = attributes
104 # For a class instance, get the attributes for the class.
106 klass
= object.__class
__
107 except: # Must catch all because object might have __getattr__.
111 # Break a circular reference. This happens with extension
115 attrdict
.update(getAllAttributeNames(klass
))
116 # Also get attributes from any and all parent classes.
118 bases
= object.__bases
__
119 except: # Must catch all because object might have __getattr__.
122 if isinstance(bases
, types
.TupleType
):
124 if type(base
) is types
.TypeType
:
125 # Break a circular reference. Happens in Python 2.2.
128 attrdict
.update(getAllAttributeNames(base
))
131 def getCallTip(command
='', locals=None):
132 """For a command, return a tuple of object name, argspec, tip text.
134 The call tip information will be based on the locals namespace."""
135 calltip
= ('', '', '') # object name, argspec, tip text.
136 # Get the proper chunk of code from the command.
137 root
= getRoot(command
, terminator
='(')
139 if locals is not None:
140 object = eval(root
, locals)
146 object, dropSelf
= getBaseObject(object)
148 name
= object.__name
__
149 except AttributeError:
153 if inspect
.isbuiltin(object):
154 # Builtin functions don't have an argspec that we can get.
156 elif inspect
.isfunction(object):
157 # tip1 is a string like: "getCallTip(command='', locals=None)"
158 argspec
= apply(inspect
.formatargspec
, inspect
.getargspec(object))
160 # The first parameter to a method is a reference to an
161 # instance, usually coded as "self", and is usually passed
162 # automatically by Python; therefore we want to drop it.
163 temp
= argspec
.split(',')
164 if len(temp
) == 1: # No other arguments.
166 elif temp
[0][:2] == '(*': # first param is like *args, not self
168 else: # Drop the first argument.
169 argspec
= '(' + ','.join(temp
[1:]).lstrip()
170 tip1
= name
+ argspec
174 doc
= inspect
.getdoc(object)
178 # tip2 is the first separated line of the docstring, like:
179 # "Return call tip text for a command."
180 # tip3 is the rest of the docstring, like:
181 # "The call tip information will be based on ... <snip>
182 firstline
= doc
.split('\n')[0].lstrip()
183 if tip1
== firstline
or firstline
[:len(name
)+1] == name
+'(':
187 docpieces
= doc
.split('\n\n')
189 tip3
= '\n\n'.join(docpieces
[1:])
190 tip
= '%s%s\n\n%s' % (tip1
, tip2
, tip3
)
193 calltip
= (name
, argspec
[1:-1], tip
.strip())
196 def getRoot(command
, terminator
=None):
197 """Return the rightmost root portion of an arbitrary Python command.
199 Return only the root portion that can be eval()'d without side
200 effects. The command would normally terminate with a '(' or
201 '.'. The terminator and anything after the terminator will be
203 command
= command
.split('\n')[-1]
204 if command
.startswith(sys
.ps2
):
205 command
= command
[len(sys
.ps2
):]
206 command
= command
.lstrip()
207 command
= rtrimTerminus(command
, terminator
)
208 tokens
= getTokens(command
)
211 if tokens
[-1][0] is tokenize
.ENDMARKER
:
212 # Remove the end marker.
216 if terminator
== '.' and \
217 (tokens
[-1][1] <> '.' or tokens
[-1][0] is not tokenize
.OP
):
218 # Trap decimals in numbers, versus the dot operator.
221 # Strip off the terminator.
222 if terminator
and command
.endswith(terminator
):
223 size
= 0 - len(terminator
)
224 command
= command
[:size
]
225 command
= command
.rstrip()
226 tokens
= getTokens(command
)
232 emptyTypes
= ('[]', '()', '{}')
235 tokenstring
= token
[1]
237 if tokentype
is tokenize
.ENDMARKER
:
239 if tokentype
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
240 and laststring
!= '.':
241 # We've reached something that's not part of the root.
242 if prefix
and line
[token
[3][1]] != ' ':
243 # If it doesn't have a space after it, remove the prefix.
246 if tokentype
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
247 or (tokentype
is tokenize
.OP
and tokenstring
== '.'):
249 # The prefix isn't valid because it comes after a dot.
253 # start represents the last known good point in the line.
255 elif len(tokenstring
) == 1 and tokenstring
in ('[({])}'):
256 # Remember, we're working backwords.
257 # So prefix += tokenstring would be wrong.
258 if prefix
in emptyTypes
and tokenstring
in ('[({'):
259 # We've already got an empty type identified so now we
260 # are in a nested situation and we can break out with
264 prefix
= tokenstring
+ prefix
266 # We've reached something that's not part of the root.
268 laststring
= tokenstring
272 if prefix
in emptyTypes
:
273 # Empty types are safe to be eval()'d and introspected.
277 def getTokens(command
):
278 """Return list of token tuples for command."""
279 command
= str(command
) # In case the command is unicode, which fails.
280 f
= cStringIO
.StringIO(command
)
281 # tokens is a list of token tuples, each looking like:
282 # (type, string, (srow, scol), (erow, ecol), line)
284 # Can't use list comprehension:
285 # tokens = [token for token in tokenize.generate_tokens(f.readline)]
286 # because of need to append as much as possible before TokenError.
288 ## This code wasn't backward compatible with Python 2.1.3.
290 ## for token in tokenize.generate_tokens(f.readline):
291 ## tokens.append(token)
293 # This works with Python 2.1.3 (with nested_scopes).
296 tokenize
.tokenize_loop(f
.readline
, eater
)
297 except tokenize
.TokenError
:
298 # This is due to a premature EOF, which we expect since we are
299 # feeding in fragments of Python code.
303 def rtrimTerminus(command
, terminator
=None):
304 """Return command minus anything that follows the final terminator."""
306 pieces
= command
.split(terminator
)
308 command
= terminator
.join(pieces
[:-1]) + terminator
311 def getBaseObject(object):
312 """Return base object and dropSelf indicator for an object."""
313 if inspect
.isbuiltin(object):
314 # Builtin functions don't have an argspec that we can get.
316 elif inspect
.ismethod(object):
317 # Get the function from the object otherwise
318 # inspect.getargspec() complains that the object isn't a
321 if object.im_self
is None:
322 # This is an unbound method so we do not drop self
323 # from the argspec, since an instance must be passed
328 object = object.im_func
329 except AttributeError:
331 elif inspect
.isclass(object):
332 # Get the __init__ method function for the class.
333 constructor
= getConstructor(object)
334 if constructor
is not None:
339 elif callable(object):
340 # Get the __call__ method instead.
342 object = object.__call
__.im_func
344 except AttributeError:
348 return object, dropSelf
350 def getConstructor(object):
351 """Return constructor for class object, or None if there isn't one."""
353 return object.__init
__.im_func
354 except AttributeError:
355 for base
in object.__bases
__:
356 constructor
= getConstructor(base
)
357 if constructor
is not None: