]>
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 # new-style swig wrappings can result in non-string attributes
59 # e.g. ITK http://www.itk.org/
60 attributes
= [attribute
for attribute
in attributes \
61 if type(attribute
) == str]
62 attributes
.sort(lambda x
, y
: cmp(x
.upper(), y
.upper()))
64 attributes
= filter(lambda item
: item
[0]!='_' \
65 or item
[1]=='_', attributes
)
67 attributes
= filter(lambda item
: item
[:2]!='__', attributes
)
68 # Make sure we haven't picked up any bogus attributes somehow.
69 attributes
= [attribute
for attribute
in attributes \
70 if hasattr(object, attribute
)]
73 def hasattrAlwaysReturnsTrue(object):
74 return hasattr(object, 'bogu5_123_aTTri8ute')
76 def getAllAttributeNames(object):
77 """Return dict of all attributes, including inherited, for an object.
79 Recursively walk through a class and all base classes.
81 attrdict
= {} # (object, technique, count): [list of attributes]
83 # Do Not use hasattr() as a test anywhere in this function,
84 # because it is unreliable with remote objects: xmlrpc, soap, etc.
85 # They always return true for hasattr().
88 # Yes, this can fail if object is an instance of a class with
89 # __str__ (or __repr__) having a bug or raising an
94 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
95 wakeupcall
= dir(object)
97 # Get attributes available through the normal convention.
98 attributes
= dir(object)
99 attrdict
[(key
, 'dir', len(attributes
))] = attributes
100 # Get attributes from the object's dictionary, if it has one.
102 attributes
= object.__dict
__.keys()
104 except: # Must catch all because object might have __getattr__.
107 attrdict
[(key
, '__dict__', len(attributes
))] = attributes
108 # For a class instance, get the attributes for the class.
110 klass
= object.__class
__
111 except: # Must catch all because object might have __getattr__.
115 # Break a circular reference. This happens with extension
119 attrdict
.update(getAllAttributeNames(klass
))
120 # Also get attributes from any and all parent classes.
122 bases
= object.__bases
__
123 except: # Must catch all because object might have __getattr__.
126 if isinstance(bases
, types
.TupleType
):
128 if type(base
) is types
.TypeType
:
129 # Break a circular reference. Happens in Python 2.2.
132 attrdict
.update(getAllAttributeNames(base
))
135 def getCallTip(command
='', locals=None):
136 """For a command, return a tuple of object name, argspec, tip text.
138 The call tip information will be based on the locals namespace."""
139 calltip
= ('', '', '') # object name, argspec, tip text.
140 # Get the proper chunk of code from the command.
141 root
= getRoot(command
, terminator
='(')
143 if locals is not None:
144 object = eval(root
, locals)
150 object, dropSelf
= getBaseObject(object)
152 name
= object.__name
__
153 except AttributeError:
157 if inspect
.isbuiltin(object):
158 # Builtin functions don't have an argspec that we can get.
160 elif inspect
.isfunction(object):
161 # tip1 is a string like: "getCallTip(command='', locals=None)"
162 argspec
= apply(inspect
.formatargspec
, inspect
.getargspec(object))
164 # The first parameter to a method is a reference to an
165 # instance, usually coded as "self", and is usually passed
166 # automatically by Python; therefore we want to drop it.
167 temp
= argspec
.split(',')
168 if len(temp
) == 1: # No other arguments.
170 elif temp
[0][:2] == '(*': # first param is like *args, not self
172 else: # Drop the first argument.
173 argspec
= '(' + ','.join(temp
[1:]).lstrip()
174 tip1
= name
+ argspec
178 doc
= inspect
.getdoc(object)
182 # tip2 is the first separated line of the docstring, like:
183 # "Return call tip text for a command."
184 # tip3 is the rest of the docstring, like:
185 # "The call tip information will be based on ... <snip>
186 firstline
= doc
.split('\n')[0].lstrip()
187 if tip1
== firstline
or firstline
[:len(name
)+1] == name
+'(':
191 docpieces
= doc
.split('\n\n')
193 tip3
= '\n\n'.join(docpieces
[1:])
194 tip
= '%s%s\n\n%s' % (tip1
, tip2
, tip3
)
197 calltip
= (name
, argspec
[1:-1], tip
.strip())
200 def getRoot(command
, terminator
=None):
201 """Return the rightmost root portion of an arbitrary Python command.
203 Return only the root portion that can be eval()'d without side
204 effects. The command would normally terminate with a '(' or
205 '.'. The terminator and anything after the terminator will be
207 command
= command
.split('\n')[-1]
208 if command
.startswith(sys
.ps2
):
209 command
= command
[len(sys
.ps2
):]
210 command
= command
.lstrip()
211 command
= rtrimTerminus(command
, terminator
)
212 tokens
= getTokens(command
)
215 if tokens
[-1][0] is tokenize
.ENDMARKER
:
216 # Remove the end marker.
220 if terminator
== '.' and \
221 (tokens
[-1][1] <> '.' or tokens
[-1][0] is not tokenize
.OP
):
222 # Trap decimals in numbers, versus the dot operator.
225 # Strip off the terminator.
226 if terminator
and command
.endswith(terminator
):
227 size
= 0 - len(terminator
)
228 command
= command
[:size
]
229 command
= command
.rstrip()
230 tokens
= getTokens(command
)
236 emptyTypes
= ('[]', '()', '{}')
239 tokenstring
= token
[1]
241 if tokentype
is tokenize
.ENDMARKER
:
243 if tokentype
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
244 and laststring
!= '.':
245 # We've reached something that's not part of the root.
246 if prefix
and line
[token
[3][1]] != ' ':
247 # If it doesn't have a space after it, remove the prefix.
250 if tokentype
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
251 or (tokentype
is tokenize
.OP
and tokenstring
== '.'):
253 # The prefix isn't valid because it comes after a dot.
257 # start represents the last known good point in the line.
259 elif len(tokenstring
) == 1 and tokenstring
in ('[({])}'):
260 # Remember, we're working backwords.
261 # So prefix += tokenstring would be wrong.
262 if prefix
in emptyTypes
and tokenstring
in ('[({'):
263 # We've already got an empty type identified so now we
264 # are in a nested situation and we can break out with
268 prefix
= tokenstring
+ prefix
270 # We've reached something that's not part of the root.
272 laststring
= tokenstring
276 if prefix
in emptyTypes
:
277 # Empty types are safe to be eval()'d and introspected.
281 def getTokens(command
):
282 """Return list of token tuples for command."""
283 command
= str(command
) # In case the command is unicode, which fails.
284 f
= cStringIO
.StringIO(command
)
285 # tokens is a list of token tuples, each looking like:
286 # (type, string, (srow, scol), (erow, ecol), line)
288 # Can't use list comprehension:
289 # tokens = [token for token in tokenize.generate_tokens(f.readline)]
290 # because of need to append as much as possible before TokenError.
292 ## This code wasn't backward compatible with Python 2.1.3.
294 ## for token in tokenize.generate_tokens(f.readline):
295 ## tokens.append(token)
297 # This works with Python 2.1.3 (with nested_scopes).
300 tokenize
.tokenize_loop(f
.readline
, eater
)
301 except tokenize
.TokenError
:
302 # This is due to a premature EOF, which we expect since we are
303 # feeding in fragments of Python code.
307 def rtrimTerminus(command
, terminator
=None):
308 """Return command minus anything that follows the final terminator."""
310 pieces
= command
.split(terminator
)
312 command
= terminator
.join(pieces
[:-1]) + terminator
315 def getBaseObject(object):
316 """Return base object and dropSelf indicator for an object."""
317 if inspect
.isbuiltin(object):
318 # Builtin functions don't have an argspec that we can get.
320 elif inspect
.ismethod(object):
321 # Get the function from the object otherwise
322 # inspect.getargspec() complains that the object isn't a
325 if object.im_self
is None:
326 # This is an unbound method so we do not drop self
327 # from the argspec, since an instance must be passed
332 object = object.im_func
333 except AttributeError:
335 elif inspect
.isclass(object):
336 # Get the __init__ method function for the class.
337 constructor
= getConstructor(object)
338 if constructor
is not None:
343 elif callable(object):
344 # Get the __call__ method instead.
346 object = object.__call
__.im_func
348 except AttributeError:
352 return object, dropSelf
354 def getConstructor(object):
355 """Return constructor for class object, or None if there isn't one."""
357 return object.__init
__.im_func
358 except AttributeError:
359 for base
in object.__bases
__:
360 constructor
= getConstructor(base
)
361 if constructor
is not None: