]>
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
 
  22 def getAutoCompleteList(command
='', locals=None, includeMagic
=1,  
  23                         includeSingle
=1, includeDouble
=1): 
  24     """Return list of auto-completion options for command. 
  26     The list of options will be based on the locals namespace.""" 
  28     # Get the proper chunk of code from the command. 
  29     root 
= getRoot(command
, terminator
='.') 
  31         if locals is not None: 
  32             object = eval(root
, locals) 
  38         attributes 
= getAttributeNames(object, includeMagic
,  
  39                                        includeSingle
, includeDouble
) 
  42 def getAttributeNames(object, includeMagic
=1, includeSingle
=1, 
  44     """Return list of unique attributes, including inherited, for object.""" 
  47     if not hasattrAlwaysReturnsTrue(object): 
  48         # Add some attributes that don't always get picked up.  If 
  49         # they don't apply, they'll get filtered out at the end. 
  50         attributes 
+= ['__bases__', '__class__', '__dict__', '__name__',  
  51                        'func_closure', 'func_code', 'func_defaults',  
  52                        'func_dict', 'func_doc', 'func_globals', 'func_name'] 
  54         try: attributes 
+= object._getAttributeNames
() 
  56     # Get all attribute names. 
  57     attrdict 
= getAllAttributeNames(object) 
  58     for attrlist 
in attrdict
.values(): 
  59         attributes 
+= attrlist
 
  60     # Remove duplicates from the attribute list. 
  61     for item 
in attributes
: 
  63     attributes 
= dict.keys() 
  64     attributes
.sort(lambda x
, y
: cmp(x
.upper(), y
.upper())) 
  66         attributes 
= filter(lambda item
: item
[0]!='_' \
 
  67                             or item
[1]=='_', attributes
) 
  69         attributes 
= filter(lambda item
: item
[:2]!='__', attributes
) 
  70     # Make sure we haven't picked up any bogus attributes somehow. 
  71     attributes 
= [attribute 
for attribute 
in attributes \
 
  72                   if hasattr(object, attribute
)] 
  75 def hasattrAlwaysReturnsTrue(object): 
  76     return hasattr(object, 'bogu5_123_aTTri8ute') 
  78 def getAllAttributeNames(object): 
  79     """Return dict of all attributes, including inherited, for an object. 
  81     Recursively walk through a class and all base classes. 
  83     attrdict 
= {}  # (object, technique, count): [list of attributes] 
  85     # Do Not use hasattr() as a test anywhere in this function, 
  86     # because it is unreliable with remote objects: xmlrpc, soap, etc. 
  87     # They always return true for hasattr(). 
  90         # Yes, this can fail if object is an instance of a class with 
  91         # __str__ (or __repr__) having a bug or raising an 
  96     # Wake up sleepy objects - a hack for ZODB objects in "ghost" state. 
  97     wakeupcall 
= dir(object) 
  99     # Get attributes available through the normal convention. 
 100     attributes 
= dir(object) 
 101     attrdict
[(key
, 'dir', len(attributes
))] = attributes
 
 102     # Get attributes from the object's dictionary, if it has one. 
 104         attributes 
= object.__dict
__.keys() 
 106     except:  # Must catch all because object might have __getattr__. 
 109         attrdict
[(key
, '__dict__', len(attributes
))] = attributes
 
 110     # For a class instance, get the attributes for the class. 
 112         klass 
= object.__class
__ 
 113     except:  # Must catch all because object might have __getattr__. 
 117             # Break a circular reference. This happens with extension 
 121             attrdict
.update(getAllAttributeNames(klass
)) 
 122     # Also get attributes from any and all parent classes. 
 124         bases 
= object.__bases
__ 
 125     except:  # Must catch all because object might have __getattr__. 
 128         if isinstance(bases
, types
.TupleType
): 
 130                 if type(base
) is types
.TypeType
: 
 131                     # Break a circular reference. Happens in Python 2.2. 
 134                     attrdict
.update(getAllAttributeNames(base
)) 
 137 def getCallTip(command
='', locals=None): 
 138     """For a command, return a tuple of object name, argspec, tip text. 
 140     The call tip information will be based on the locals namespace.""" 
 141     calltip 
= ('', '', '')  # object name, argspec, tip text. 
 142     # Get the proper chunk of code from the command. 
 143     root 
= getRoot(command
, terminator
='(') 
 145         if locals is not None: 
 146             object = eval(root
, locals) 
 152     object, dropSelf 
= getBaseObject(object) 
 154         name 
= object.__name
__ 
 155     except AttributeError: 
 159     if inspect
.isbuiltin(object): 
 160         # Builtin functions don't have an argspec that we can get. 
 162     elif inspect
.isfunction(object): 
 163         # tip1 is a string like: "getCallTip(command='', locals=None)" 
 164         argspec 
= apply(inspect
.formatargspec
, inspect
.getargspec(object)) 
 166             # The first parameter to a method is a reference to an 
 167             # instance, usually coded as "self", and is usually passed 
 168             # automatically by Python; therefore we want to drop it. 
 169             temp 
= argspec
.split(',') 
 170             if len(temp
) == 1:  # No other arguments. 
 172             elif temp
[0][:2] == '(*': # first param is like *args, not self 
 174             else:  # Drop the first argument. 
 175                 argspec 
= '(' + ','.join(temp
[1:]).lstrip() 
 176         tip1 
= name 
+ argspec
 
 180             doc 
= inspect
.getdoc(object) 
 184         # tip2 is the first separated line of the docstring, like: 
 185         # "Return call tip text for a command." 
 186         # tip3 is the rest of the docstring, like: 
 187         # "The call tip information will be based on ... <snip> 
 188         firstline 
= doc
.split('\n')[0].lstrip() 
 189         if tip1 
== firstline 
or firstline
[:len(name
)+1] == name
+'(': 
 193         docpieces 
= doc
.split('\n\n') 
 195         tip3 
= '\n\n'.join(docpieces
[1:]) 
 196         tip 
= '%s%s\n\n%s' % (tip1
, tip2
, tip3
) 
 199     calltip 
= (name
, argspec
[1:-1], tip
.strip()) 
 202 def getRoot(command
, terminator
=None): 
 203     """Return the rightmost root portion of an arbitrary Python command. 
 205     Return only the root portion that can be eval()'d without side 
 206     effects.  The command would normally terminate with a '(' or 
 207     '.'. The terminator and anything after the terminator will be 
 209     command 
= command
.split('\n')[-1] 
 210     if command
.startswith(sys
.ps2
): 
 211         command 
= command
[len(sys
.ps2
):] 
 212     command 
= command
.lstrip() 
 213     command 
= rtrimTerminus(command
, terminator
) 
 214     tokens 
= getTokens(command
) 
 217     if tokens
[-1][0] is tokenize
.ENDMARKER
: 
 218         # Remove the end marker. 
 222     if terminator 
== '.' and \
 
 223            (tokens
[-1][1] <> '.' or tokens
[-1][0] is not tokenize
.OP
): 
 224         # Trap decimals in numbers, versus the dot operator. 
 227         # Strip off the terminator. 
 228         if terminator 
and command
.endswith(terminator
): 
 229             size 
= 0 - len(terminator
) 
 230             command 
= command
[:size
] 
 231     command 
= command
.rstrip() 
 232     tokens 
= getTokens(command
) 
 238     emptyTypes 
= ('[]', '()', '{}') 
 241         tokenstring 
= token
[1] 
 243         if tokentype 
is tokenize
.ENDMARKER
: 
 245         if tokentype 
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
 
 246         and laststring 
!= '.': 
 247             # We've reached something that's not part of the root. 
 248             if prefix 
and line
[token
[3][1]] != ' ': 
 249                 # If it doesn't have a space after it, remove the prefix. 
 252         if tokentype 
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
 
 253         or (tokentype 
is tokenize
.OP 
and tokenstring 
== '.'): 
 255                 # The prefix isn't valid because it comes after a dot. 
 259                 # start represents the last known good point in the line. 
 261         elif len(tokenstring
) == 1 and tokenstring 
in ('[({])}'): 
 262             # Remember, we're working backwords. 
 263             # So prefix += tokenstring would be wrong. 
 264             if prefix 
in emptyTypes 
and tokenstring 
in ('[({'): 
 265                 # We've already got an empty type identified so now we 
 266                 # are in a nested situation and we can break out with 
 270                 prefix 
= tokenstring 
+ prefix
 
 272             # We've reached something that's not part of the root. 
 274         laststring 
= tokenstring
 
 278     if prefix 
in emptyTypes
: 
 279         # Empty types are safe to be eval()'d and introspected. 
 283 def getTokens(command
): 
 284     """Return list of token tuples for command.""" 
 285     command 
= str(command
)  # In case the command is unicode, which fails. 
 286     f 
= cStringIO
.StringIO(command
) 
 287     # tokens is a list of token tuples, each looking like:  
 288     # (type, string, (srow, scol), (erow, ecol), line) 
 290     # Can't use list comprehension: 
 291     #   tokens = [token for token in tokenize.generate_tokens(f.readline)] 
 292     # because of need to append as much as possible before TokenError. 
 294 ##        This code wasn't backward compatible with Python 2.1.3. 
 296 ##        for token in tokenize.generate_tokens(f.readline): 
 297 ##            tokens.append(token) 
 299         # This works with Python 2.1.3 (with nested_scopes). 
 302         tokenize
.tokenize_loop(f
.readline
, eater
) 
 303     except tokenize
.TokenError
: 
 304         # This is due to a premature EOF, which we expect since we are 
 305         # feeding in fragments of Python code. 
 309 def rtrimTerminus(command
, terminator
=None): 
 310     """Return command minus anything that follows the final terminator.""" 
 312         pieces 
= command
.split(terminator
) 
 314             command 
= terminator
.join(pieces
[:-1]) + terminator
 
 317 def getBaseObject(object): 
 318     """Return base object and dropSelf indicator for an object.""" 
 319     if inspect
.isbuiltin(object): 
 320         # Builtin functions don't have an argspec that we can get. 
 322     elif inspect
.ismethod(object): 
 323         # Get the function from the object otherwise 
 324         # inspect.getargspec() complains that the object isn't a 
 327             if object.im_self 
is None: 
 328                 # This is an unbound method so we do not drop self 
 329                 # from the argspec, since an instance must be passed 
 334             object = object.im_func
 
 335         except AttributeError: 
 337     elif inspect
.isclass(object): 
 338         # Get the __init__ method function for the class. 
 339         constructor 
= getConstructor(object) 
 340         if constructor 
is not None: 
 345     elif callable(object): 
 346         # Get the __call__ method instead. 
 348             object = object.__call
__.im_func
 
 350         except AttributeError: 
 354     return object, dropSelf
 
 356 def getConstructor(object): 
 357     """Return constructor for class object, or None if there isn't one.""" 
 359         return object.__init
__.im_func
 
 360     except AttributeError: 
 361         for base 
in object.__bases
__: 
 362             constructor 
= getConstructor(base
) 
 363             if constructor 
is not None: