]>
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: