]>
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
17 def getAutoCompleteList(command
='', locals=None, includeMagic
=1,
18 includeSingle
=1, includeDouble
=1):
19 """Return list of auto-completion options for command.
21 The list of options will be based on the locals namespace."""
23 # Get the proper chunk of code from the command.
24 root
= getRoot(command
, terminator
='.')
26 if locals is not None:
27 object = eval(root
, locals)
33 attributes
= getAttributeNames(object, includeMagic
,
34 includeSingle
, includeDouble
)
37 def getAttributeNames(object, includeMagic
=1, includeSingle
=1,
39 """Return list of unique attributes, including inherited, for object."""
42 if not hasattrAlwaysReturnsTrue(object):
43 # Add some attributes that don't always get picked up.
44 special_attrs
= ['__bases__', '__class__', '__dict__', '__name__',
45 'func_closure', 'func_code', 'func_defaults',
46 'func_dict', 'func_doc', 'func_globals', 'func_name']
47 attributes
+= [attr
for attr
in special_attrs \
48 if hasattr(object, attr
)]
50 try: attributes
+= object._getAttributeNames
()
52 # Get all attribute names.
53 str_type
= str(type(object))
54 if str_type
== "<type 'array'>":
55 attributes
+= dir(object)
57 attrdict
= getAllAttributeNames(object)
58 # Store the object's dir.
59 object_dir
= dir(object)
60 for (obj_type_name
, technique
, count
), attrlist
in attrdict
.items():
61 # This complexity is necessary to avoid accessing all the
62 # attributes of the object. This is very handy for objects
63 # whose attributes are lazily evaluated.
64 if type(object).__name
__ == obj_type_name
and technique
== 'dir':
65 attributes
+= attrlist
67 attributes
+= [attr
for attr
in attrlist \
68 if attr
not in object_dir
and hasattr(object, attr
)]
70 # Remove duplicates from the attribute list.
71 for item
in attributes
:
73 attributes
= dict.keys()
74 # new-style swig wrappings can result in non-string attributes
75 # e.g. ITK http://www.itk.org/
76 attributes
= [attribute
for attribute
in attributes \
77 if type(attribute
) == str]
78 attributes
.sort(lambda x
, y
: cmp(x
.upper(), y
.upper()))
80 attributes
= filter(lambda item
: item
[0]!='_' \
81 or item
[1]=='_', attributes
)
83 attributes
= filter(lambda item
: item
[:2]!='__', attributes
)
86 def hasattrAlwaysReturnsTrue(object):
87 return hasattr(object, 'bogu5_123_aTTri8ute')
89 def getAllAttributeNames(object):
90 """Return dict of all attributes, including inherited, for an object.
92 Recursively walk through a class and all base classes.
94 attrdict
= {} # (object, technique, count): [list of attributes]
96 # Do Not use hasattr() as a test anywhere in this function,
97 # because it is unreliable with remote objects: xmlrpc, soap, etc.
98 # They always return true for hasattr().
101 # This could(?) fail if the type is poorly defined without
103 key
= type(object).__name
__
106 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
107 wakeupcall
= dir(object)
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.
114 attributes
= object.__dict
__.keys()
116 except: # Must catch all because object might have __getattr__.
119 attrdict
[(key
, '__dict__', len(attributes
))] = attributes
120 # For a class instance, get the attributes for the class.
122 klass
= object.__class
__
123 except: # Must catch all because object might have __getattr__.
127 # Break a circular reference. This happens with extension
131 attrdict
.update(getAllAttributeNames(klass
))
132 # Also get attributes from any and all parent classes.
134 bases
= object.__bases
__
135 except: # Must catch all because object might have __getattr__.
138 if isinstance(bases
, types
.TupleType
):
140 if type(base
) is types
.TypeType
:
141 # Break a circular reference. Happens in Python 2.2.
144 attrdict
.update(getAllAttributeNames(base
))
147 def getCallTip(command
='', locals=None):
148 """For a command, return a tuple of object name, argspec, tip text.
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
='(')
155 if locals is not None:
156 object = eval(root
, locals)
162 object, dropSelf
= getBaseObject(object)
164 name
= object.__name
__
165 except AttributeError:
169 if inspect
.isbuiltin(object):
170 # Builtin functions don't have an argspec that we can get.
172 elif inspect
.isfunction(object):
173 # tip1 is a string like: "getCallTip(command='', locals=None)"
174 argspec
= apply(inspect
.formatargspec
, inspect
.getargspec(object))
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.
182 elif temp
[0][:2] == '(*': # first param is like *args, not self
184 else: # Drop the first argument.
185 argspec
= '(' + ','.join(temp
[1:]).lstrip()
186 tip1
= name
+ argspec
190 doc
= inspect
.getdoc(object)
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()
199 if tip1
== firstline
or firstline
[:len(name
)+1] == name
+'(':
203 docpieces
= doc
.split('\n\n')
205 tip3
= '\n\n'.join(docpieces
[1:])
206 tip
= '%s%s\n\n%s' % (tip1
, tip2
, tip3
)
209 calltip
= (name
, argspec
[1:-1], tip
.strip())
212 def getRoot(command
, terminator
=None):
213 """Return the rightmost root portion of an arbitrary Python command.
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
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
)
227 if tokens
[-1][0] is tokenize
.ENDMARKER
:
228 # Remove the end marker.
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.
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
)
248 emptyTypes
= ('[]', '()', '{}')
251 tokenstring
= token
[1]
253 if tokentype
is tokenize
.ENDMARKER
:
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.
262 if tokentype
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
263 or (tokentype
is tokenize
.OP
and tokenstring
== '.'):
265 # The prefix isn't valid because it comes after a dot.
269 # start represents the last known good point in the line.
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
280 prefix
= tokenstring
+ prefix
282 # We've reached something that's not part of the root.
284 laststring
= tokenstring
288 if prefix
in emptyTypes
:
289 # Empty types are safe to be eval()'d and introspected.
293 def getTokens(command
):
294 """Return list of token tuples for command."""
296 # In case the command is unicode try encoding it
297 if type(command
) == unicode:
299 command
= command
.encode(wx
.GetDefaultPyEncoding())
300 except UnicodeEncodeError:
301 pass # otherwise leave it alone
303 f
= cStringIO
.StringIO(command
)
304 # tokens is a list of token tuples, each looking like:
305 # (type, string, (srow, scol), (erow, ecol), line)
307 # Can't use list comprehension:
308 # tokens = [token for token in tokenize.generate_tokens(f.readline)]
309 # because of need to append as much as possible before TokenError.
311 ## This code wasn't backward compatible with Python 2.1.3.
313 ## for token in tokenize.generate_tokens(f.readline):
314 ## tokens.append(token)
316 # This works with Python 2.1.3 (with nested_scopes).
319 tokenize
.tokenize_loop(f
.readline
, eater
)
320 except tokenize
.TokenError
:
321 # This is due to a premature EOF, which we expect since we are
322 # feeding in fragments of Python code.
326 def rtrimTerminus(command
, terminator
=None):
327 """Return command minus anything that follows the final terminator."""
329 pieces
= command
.split(terminator
)
331 command
= terminator
.join(pieces
[:-1]) + terminator
334 def getBaseObject(object):
335 """Return base object and dropSelf indicator for an object."""
336 if inspect
.isbuiltin(object):
337 # Builtin functions don't have an argspec that we can get.
339 elif inspect
.ismethod(object):
340 # Get the function from the object otherwise
341 # inspect.getargspec() complains that the object isn't a
344 if object.im_self
is None:
345 # This is an unbound method so we do not drop self
346 # from the argspec, since an instance must be passed
351 object = object.im_func
352 except AttributeError:
354 elif inspect
.isclass(object):
355 # Get the __init__ method function for the class.
356 constructor
= getConstructor(object)
357 if constructor
is not None:
362 elif callable(object):
363 # Get the __call__ method instead.
365 object = object.__call
__.im_func
367 except AttributeError:
371 return object, dropSelf
373 def getConstructor(object):
374 """Return constructor for class object, or None if there isn't one."""
376 return object.__init
__.im_func
377 except AttributeError:
378 for base
in object.__bases
__:
379 constructor
= getConstructor(base
)
380 if constructor
is not None: