]>
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.
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
)]
49 try: attributes
+= object._getAttributeNames
()
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
62 attributes
+= [attr
for attr
in attrlist \
63 if attr
not in object_dir
and hasattr(object, attr
)]
65 # Remove duplicates from the attribute list.
66 for item
in attributes
:
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()))
75 attributes
= filter(lambda item
: item
[0]!='_' \
76 or item
[1]=='_', attributes
)
78 attributes
= filter(lambda item
: item
[:2]!='__', attributes
)
81 def hasattrAlwaysReturnsTrue(object):
82 return hasattr(object, 'bogu5_123_aTTri8ute')
84 def getAllAttributeNames(object):
85 """Return dict of all attributes, including inherited, for an object.
87 Recursively walk through a class and all base classes.
89 attrdict
= {} # (object, technique, count): [list of attributes]
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().
96 # Yes, this can fail if object is an instance of a class with
97 # __str__ (or __repr__) having a bug or raising an
102 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
103 wakeupcall
= dir(object)
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.
110 attributes
= object.__dict
__.keys()
112 except: # Must catch all because object might have __getattr__.
115 attrdict
[(key
, '__dict__', len(attributes
))] = attributes
116 # For a class instance, get the attributes for the class.
118 klass
= object.__class
__
119 except: # Must catch all because object might have __getattr__.
123 # Break a circular reference. This happens with extension
127 attrdict
.update(getAllAttributeNames(klass
))
128 # Also get attributes from any and all parent classes.
130 bases
= object.__bases
__
131 except: # Must catch all because object might have __getattr__.
134 if isinstance(bases
, types
.TupleType
):
136 if type(base
) is types
.TypeType
:
137 # Break a circular reference. Happens in Python 2.2.
140 attrdict
.update(getAllAttributeNames(base
))
143 def getCallTip(command
='', locals=None):
144 """For a command, return a tuple of object name, argspec, tip text.
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
='(')
151 if locals is not None:
152 object = eval(root
, locals)
158 object, dropSelf
= getBaseObject(object)
160 name
= object.__name
__
161 except AttributeError:
165 if inspect
.isbuiltin(object):
166 # Builtin functions don't have an argspec that we can get.
168 elif inspect
.isfunction(object):
169 # tip1 is a string like: "getCallTip(command='', locals=None)"
170 argspec
= apply(inspect
.formatargspec
, inspect
.getargspec(object))
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.
178 elif temp
[0][:2] == '(*': # first param is like *args, not self
180 else: # Drop the first argument.
181 argspec
= '(' + ','.join(temp
[1:]).lstrip()
182 tip1
= name
+ argspec
186 doc
= inspect
.getdoc(object)
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
+'(':
199 docpieces
= doc
.split('\n\n')
201 tip3
= '\n\n'.join(docpieces
[1:])
202 tip
= '%s%s\n\n%s' % (tip1
, tip2
, tip3
)
205 calltip
= (name
, argspec
[1:-1], tip
.strip())
208 def getRoot(command
, terminator
=None):
209 """Return the rightmost root portion of an arbitrary Python command.
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
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
)
223 if tokens
[-1][0] is tokenize
.ENDMARKER
:
224 # Remove the end marker.
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.
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
)
244 emptyTypes
= ('[]', '()', '{}')
247 tokenstring
= token
[1]
249 if tokentype
is tokenize
.ENDMARKER
:
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.
258 if tokentype
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
259 or (tokentype
is tokenize
.OP
and tokenstring
== '.'):
261 # The prefix isn't valid because it comes after a dot.
265 # start represents the last known good point in the line.
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
276 prefix
= tokenstring
+ prefix
278 # We've reached something that's not part of the root.
280 laststring
= tokenstring
284 if prefix
in emptyTypes
:
285 # Empty types are safe to be eval()'d and introspected.
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)
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.
300 ## This code wasn't backward compatible with Python 2.1.3.
302 ## for token in tokenize.generate_tokens(f.readline):
303 ## tokens.append(token)
305 # This works with Python 2.1.3 (with nested_scopes).
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.
315 def rtrimTerminus(command
, terminator
=None):
316 """Return command minus anything that follows the final terminator."""
318 pieces
= command
.split(terminator
)
320 command
= terminator
.join(pieces
[:-1]) + terminator
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.
328 elif inspect
.ismethod(object):
329 # Get the function from the object otherwise
330 # inspect.getargspec() complains that the object isn't a
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
340 object = object.im_func
341 except AttributeError:
343 elif inspect
.isclass(object):
344 # Get the __init__ method function for the class.
345 constructor
= getConstructor(object)
346 if constructor
is not None:
351 elif callable(object):
352 # Get the __call__ method instead.
354 object = object.__call
__.im_func
356 except AttributeError:
360 return object, dropSelf
362 def getConstructor(object):
363 """Return constructor for class object, or None if there isn't one."""
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: