]>
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] 
  15 def getAutoCompleteList(command
='', locals=None, includeMagic
=1,  
  16                         includeSingle
=1, includeDouble
=1): 
  17     """Return list of auto-completion options for command. 
  19     The list of options will be based on the locals namespace.""" 
  21     # Get the proper chunk of code from the command. 
  22     root 
= getRoot(command
, terminator
='.') 
  24         if locals is not None: 
  25             object = eval(root
, locals) 
  31         attributes 
= getAttributeNames(object, includeMagic
,  
  32                                        includeSingle
, includeDouble
) 
  35 def getAttributeNames(object, includeMagic
=1, includeSingle
=1, 
  37     """Return list of unique attributes, including inherited, for object.""" 
  40     if not hasattrAlwaysReturnsTrue(object): 
  41         # Add some attributes that don't always get picked up. 
  42         special_attrs 
= ['__bases__', '__class__', '__dict__', '__name__', 
  43                          'func_closure', 'func_code', 'func_defaults', 
  44                          'func_dict', 'func_doc', 'func_globals', 'func_name'] 
  45         attributes 
+= [attr 
for attr 
in special_attrs \
 
  46                        if hasattr(object, attr
)] 
  48         try: attributes 
+= object._getAttributeNames
() 
  50     # Get all attribute names. 
  51     str_type 
= str(type(object)) 
  52     if str_type 
== "<type 'array'>": 
  53         attributes 
+= dir(object) 
  55         attrdict 
= getAllAttributeNames(object) 
  56         # Store the object's dir. 
  57         object_dir 
= dir(object) 
  58         for (obj_type_name
, technique
, count
), attrlist 
in attrdict
.items(): 
  59             # This complexity is necessary to avoid accessing all the 
  60             # attributes of the object.  This is very handy for objects 
  61             # whose attributes are lazily evaluated. 
  62             if type(object).__name
__ == obj_type_name 
and technique 
== 'dir': 
  63                 attributes 
+= attrlist
 
  65                 attributes 
+= [attr 
for attr 
in attrlist \
 
  66                                if attr 
not in object_dir 
and hasattr(object, attr
)] 
  68     # Remove duplicates from the attribute list. 
  69     for item 
in attributes
: 
  71     attributes 
= dict.keys() 
  72     # new-style swig wrappings can result in non-string attributes 
  73     # e.g. ITK http://www.itk.org/ 
  74     attributes 
= [attribute 
for attribute 
in attributes \
 
  75                   if type(attribute
) == str] 
  76     attributes
.sort(lambda x
, y
: cmp(x
.upper(), y
.upper())) 
  78         attributes 
= filter(lambda item
: item
[0]!='_' \
 
  79                             or item
[1:2]=='_', attributes
) 
  81         attributes 
= filter(lambda item
: item
[:2]!='__', attributes
) 
  84 def hasattrAlwaysReturnsTrue(object): 
  85     return hasattr(object, 'bogu5_123_aTTri8ute') 
  87 def getAllAttributeNames(object): 
  88     """Return dict of all attributes, including inherited, for an object. 
  90     Recursively walk through a class and all base classes. 
  92     attrdict 
= {}  # (object, technique, count): [list of attributes] 
  94     # Do Not use hasattr() as a test anywhere in this function, 
  95     # because it is unreliable with remote objects: xmlrpc, soap, etc. 
  96     # They always return true for hasattr(). 
  99         # This could(?) fail if the type is poorly defined without 
 101         key 
= type(object).__name
__ 
 104     # Wake up sleepy objects - a hack for ZODB objects in "ghost" state. 
 105     wakeupcall 
= dir(object) 
 107     # Get attributes available through the normal convention. 
 108     attributes 
= dir(object) 
 109     attrdict
[(key
, 'dir', len(attributes
))] = attributes
 
 110     # Get attributes from the object's dictionary, if it has one. 
 112         attributes 
= object.__dict
__.keys() 
 114     except:  # Must catch all because object might have __getattr__. 
 117         attrdict
[(key
, '__dict__', len(attributes
))] = attributes
 
 118     # For a class instance, get the attributes for the class. 
 120         klass 
= object.__class
__ 
 121     except:  # Must catch all because object might have __getattr__. 
 125             # Break a circular reference. This happens with extension 
 129             attrdict
.update(getAllAttributeNames(klass
)) 
 130     # Also get attributes from any and all parent classes. 
 132         bases 
= object.__bases
__ 
 133     except:  # Must catch all because object might have __getattr__. 
 136         if isinstance(bases
, types
.TupleType
): 
 138                 if type(base
) is types
.TypeType
: 
 139                     # Break a circular reference. Happens in Python 2.2. 
 142                     attrdict
.update(getAllAttributeNames(base
)) 
 145 def getCallTip(command
='', locals=None): 
 146     """For a command, return a tuple of object name, argspec, tip text. 
 148     The call tip information will be based on the locals namespace.""" 
 149     calltip 
= ('', '', '')  # object name, argspec, tip text. 
 150     # Get the proper chunk of code from the command. 
 151     root 
= getRoot(command
, terminator
='(') 
 153         if locals is not None: 
 154             object = eval(root
, locals) 
 160     object, dropSelf 
= getBaseObject(object) 
 162         name 
= object.__name
__ 
 163     except AttributeError: 
 167     if inspect
.isbuiltin(object): 
 168         # Builtin functions don't have an argspec that we can get. 
 170     elif inspect
.isfunction(object): 
 171         # tip1 is a string like: "getCallTip(command='', locals=None)" 
 172         argspec 
= apply(inspect
.formatargspec
, inspect
.getargspec(object)) 
 174             # The first parameter to a method is a reference to an 
 175             # instance, usually coded as "self", and is usually passed 
 176             # automatically by Python; therefore we want to drop it. 
 177             temp 
= argspec
.split(',') 
 178             if len(temp
) == 1:  # No other arguments. 
 180             elif temp
[0][:2] == '(*': # first param is like *args, not self 
 182             else:  # Drop the first argument. 
 183                 argspec 
= '(' + ','.join(temp
[1:]).lstrip() 
 184         tip1 
= name 
+ argspec
 
 188             doc 
= inspect
.getdoc(object) 
 192         # tip2 is the first separated line of the docstring, like: 
 193         # "Return call tip text for a command." 
 194         # tip3 is the rest of the docstring, like: 
 195         # "The call tip information will be based on ... <snip> 
 196         firstline 
= doc
.split('\n')[0].lstrip() 
 197         if tip1 
== firstline 
or firstline
[:len(name
)+1] == name
+'(': 
 201         docpieces 
= doc
.split('\n\n') 
 203         tip3 
= '\n\n'.join(docpieces
[1:]) 
 204         tip 
= '%s%s\n\n%s' % (tip1
, tip2
, tip3
) 
 207     calltip 
= (name
, argspec
[1:-1], tip
.strip()) 
 210 def getRoot(command
, terminator
=None): 
 211     """Return the rightmost root portion of an arbitrary Python command. 
 213     Return only the root portion that can be eval()'d without side 
 214     effects.  The command would normally terminate with a '(' or 
 215     '.'. The terminator and anything after the terminator will be 
 217     command 
= command
.split('\n')[-1] 
 218     if command
.startswith(sys
.ps2
): 
 219         command 
= command
[len(sys
.ps2
):] 
 220     command 
= command
.lstrip() 
 221     command 
= rtrimTerminus(command
, terminator
) 
 222     tokens 
= getTokens(command
) 
 225     if tokens
[-1][0] is tokenize
.ENDMARKER
: 
 226         # Remove the end marker. 
 230     if terminator 
== '.' and \
 
 231            (tokens
[-1][1] <> '.' or tokens
[-1][0] is not tokenize
.OP
): 
 232         # Trap decimals in numbers, versus the dot operator. 
 235         # Strip off the terminator. 
 236         if terminator 
and command
.endswith(terminator
): 
 237             size 
= 0 - len(terminator
) 
 238             command 
= command
[:size
] 
 239     command 
= command
.rstrip() 
 240     tokens 
= getTokens(command
) 
 246     emptyTypes 
= ('[]', '()', '{}') 
 249         tokenstring 
= token
[1] 
 251         if tokentype 
is tokenize
.ENDMARKER
: 
 253         if tokentype 
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
 
 254         and laststring 
!= '.': 
 255             # We've reached something that's not part of the root. 
 256             if prefix 
and line
[token
[3][1]] != ' ': 
 257                 # If it doesn't have a space after it, remove the prefix. 
 260         if tokentype 
in (tokenize
.NAME
, tokenize
.STRING
, tokenize
.NUMBER
) \
 
 261         or (tokentype 
is tokenize
.OP 
and tokenstring 
== '.'): 
 263                 # The prefix isn't valid because it comes after a dot. 
 267                 # start represents the last known good point in the line. 
 269         elif len(tokenstring
) == 1 and tokenstring 
in ('[({])}'): 
 270             # Remember, we're working backwords. 
 271             # So prefix += tokenstring would be wrong. 
 272             if prefix 
in emptyTypes 
and tokenstring 
in ('[({'): 
 273                 # We've already got an empty type identified so now we 
 274                 # are in a nested situation and we can break out with 
 278                 prefix 
= tokenstring 
+ prefix
 
 280             # We've reached something that's not part of the root. 
 282         laststring 
= tokenstring
 
 286     if prefix 
in emptyTypes
: 
 287         # Empty types are safe to be eval()'d and introspected. 
 291 def getTokens(command
): 
 292     """Return list of token tuples for command.""" 
 294     # In case the command is unicode try encoding it 
 295     if type(command
) == unicode: 
 297             command 
= command
.encode(wx
.GetDefaultPyEncoding()) 
 298         except UnicodeEncodeError: 
 299             pass # otherwise leave it alone 
 301     f 
= cStringIO
.StringIO(command
) 
 302     # tokens is a list of token tuples, each looking like:  
 303     # (type, string, (srow, scol), (erow, ecol), line) 
 305     # Can't use list comprehension: 
 306     #   tokens = [token for token in tokenize.generate_tokens(f.readline)] 
 307     # because of need to append as much as possible before TokenError. 
 309 ##        This code wasn't backward compatible with Python 2.1.3. 
 311 ##        for token in tokenize.generate_tokens(f.readline): 
 312 ##            tokens.append(token) 
 314         # This works with Python 2.1.3 (with nested_scopes). 
 317         tokenize
.tokenize_loop(f
.readline
, eater
) 
 318     except tokenize
.TokenError
: 
 319         # This is due to a premature EOF, which we expect since we are 
 320         # feeding in fragments of Python code. 
 324 def rtrimTerminus(command
, terminator
=None): 
 325     """Return command minus anything that follows the final terminator.""" 
 327         pieces 
= command
.split(terminator
) 
 329             command 
= terminator
.join(pieces
[:-1]) + terminator
 
 332 def getBaseObject(object): 
 333     """Return base object and dropSelf indicator for an object.""" 
 334     if inspect
.isbuiltin(object): 
 335         # Builtin functions don't have an argspec that we can get. 
 337     elif inspect
.ismethod(object): 
 338         # Get the function from the object otherwise 
 339         # inspect.getargspec() complains that the object isn't a 
 342             if object.im_self 
is None: 
 343                 # This is an unbound method so we do not drop self 
 344                 # from the argspec, since an instance must be passed 
 349             object = object.im_func
 
 350         except AttributeError: 
 352     elif inspect
.isclass(object): 
 353         # Get the __init__ method function for the class. 
 354         constructor 
= getConstructor(object) 
 355         if constructor 
is not None: 
 360     elif callable(object): 
 361         # Get the __call__ method instead. 
 363             object = object.__call
__.im_func
 
 365         except AttributeError: 
 369     return object, dropSelf
 
 371 def getConstructor(object): 
 372     """Return constructor for class object, or None if there isn't one.""" 
 374         return object.__init
__.im_func
 
 375     except AttributeError: 
 376         for base 
in object.__bases
__: 
 377             constructor 
= getConstructor(base
) 
 378             if constructor 
is not None: