/site-packages/wx-2.8-msw-unicode/wx/py/introspect.py
Python | 380 lines | 340 code | 7 blank | 33 comment | 39 complexity | 16276424307c3617040120bd208abdcf MD5 | raw file
- """Provides a variety of introspective-type support functions for
- things like call tips and command auto completion."""
-
- __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
- __cvsid__ = "$Id: introspect.py 39896 2006-06-29 22:24:00Z RD $"
- __revision__ = "$Revision: 39896 $"[11:-2]
-
- import cStringIO
- import inspect
- import sys
- import tokenize
- import types
- import wx
-
- def getAutoCompleteList(command='', locals=None, includeMagic=1,
- includeSingle=1, includeDouble=1):
- """Return list of auto-completion options for command.
-
- The list of options will be based on the locals namespace."""
- attributes = []
- # Get the proper chunk of code from the command.
- root = getRoot(command, terminator='.')
- try:
- if locals is not None:
- object = eval(root, locals)
- else:
- object = eval(root)
- except:
- pass
- else:
- attributes = getAttributeNames(object, includeMagic,
- includeSingle, includeDouble)
- return attributes
-
- def getAttributeNames(object, includeMagic=1, includeSingle=1,
- includeDouble=1):
- """Return list of unique attributes, including inherited, for object."""
- attributes = []
- dict = {}
- if not hasattrAlwaysReturnsTrue(object):
- # Add some attributes that don't always get picked up.
- special_attrs = ['__bases__', '__class__', '__dict__', '__name__',
- 'func_closure', 'func_code', 'func_defaults',
- 'func_dict', 'func_doc', 'func_globals', 'func_name']
- attributes += [attr for attr in special_attrs \
- if hasattr(object, attr)]
- if includeMagic:
- try: attributes += object._getAttributeNames()
- except: pass
- # Get all attribute names.
- str_type = str(type(object))
- if str_type == "<type 'array'>":
- attributes += dir(object)
- else:
- attrdict = getAllAttributeNames(object)
- # Store the object's dir.
- object_dir = dir(object)
- for (obj_type_name, technique, count), attrlist in attrdict.items():
- # This complexity is necessary to avoid accessing all the
- # attributes of the object. This is very handy for objects
- # whose attributes are lazily evaluated.
- if type(object).__name__ == obj_type_name and technique == 'dir':
- attributes += attrlist
- else:
- attributes += [attr for attr in attrlist \
- if attr not in object_dir and hasattr(object, attr)]
-
- # Remove duplicates from the attribute list.
- for item in attributes:
- dict[item] = None
- attributes = dict.keys()
- # new-style swig wrappings can result in non-string attributes
- # e.g. ITK http://www.itk.org/
- attributes = [attribute for attribute in attributes \
- if type(attribute) == str]
- attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
- if not includeSingle:
- attributes = filter(lambda item: item[0]!='_' \
- or item[1:2]=='_', attributes)
- if not includeDouble:
- attributes = filter(lambda item: item[:2]!='__', attributes)
- return attributes
-
- def hasattrAlwaysReturnsTrue(object):
- return hasattr(object, 'bogu5_123_aTTri8ute')
-
- def getAllAttributeNames(object):
- """Return dict of all attributes, including inherited, for an object.
-
- Recursively walk through a class and all base classes.
- """
- attrdict = {} # (object, technique, count): [list of attributes]
- # !!!
- # Do Not use hasattr() as a test anywhere in this function,
- # because it is unreliable with remote objects: xmlrpc, soap, etc.
- # They always return true for hasattr().
- # !!!
- try:
- # This could(?) fail if the type is poorly defined without
- # even a name.
- key = type(object).__name__
- except:
- key = 'anonymous'
- # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
- wakeupcall = dir(object)
- del wakeupcall
- # Get attributes available through the normal convention.
- attributes = dir(object)
- attrdict[(key, 'dir', len(attributes))] = attributes
- # Get attributes from the object's dictionary, if it has one.
- try:
- attributes = object.__dict__.keys()
- attributes.sort()
- except: # Must catch all because object might have __getattr__.
- pass
- else:
- attrdict[(key, '__dict__', len(attributes))] = attributes
- # For a class instance, get the attributes for the class.
- try:
- klass = object.__class__
- except: # Must catch all because object might have __getattr__.
- pass
- else:
- if klass is object:
- # Break a circular reference. This happens with extension
- # classes.
- pass
- else:
- attrdict.update(getAllAttributeNames(klass))
- # Also get attributes from any and all parent classes.
- try:
- bases = object.__bases__
- except: # Must catch all because object might have __getattr__.
- pass
- else:
- if isinstance(bases, types.TupleType):
- for base in bases:
- if type(base) is types.TypeType:
- # Break a circular reference. Happens in Python 2.2.
- pass
- else:
- attrdict.update(getAllAttributeNames(base))
- return attrdict
-
- def getCallTip(command='', locals=None):
- """For a command, return a tuple of object name, argspec, tip text.
-
- The call tip information will be based on the locals namespace."""
- calltip = ('', '', '') # object name, argspec, tip text.
- # Get the proper chunk of code from the command.
- root = getRoot(command, terminator='(')
- try:
- if locals is not None:
- object = eval(root, locals)
- else:
- object = eval(root)
- except:
- return calltip
- name = ''
- object, dropSelf = getBaseObject(object)
- try:
- name = object.__name__
- except AttributeError:
- pass
- tip1 = ''
- argspec = ''
- if inspect.isbuiltin(object):
- # Builtin functions don't have an argspec that we can get.
- pass
- elif inspect.isfunction(object):
- # tip1 is a string like: "getCallTip(command='', locals=None)"
- argspec = apply(inspect.formatargspec, inspect.getargspec(object))
- if dropSelf:
- # The first parameter to a method is a reference to an
- # instance, usually coded as "self", and is usually passed
- # automatically by Python; therefore we want to drop it.
- temp = argspec.split(',')
- if len(temp) == 1: # No other arguments.
- argspec = '()'
- elif temp[0][:2] == '(*': # first param is like *args, not self
- pass
- else: # Drop the first argument.
- argspec = '(' + ','.join(temp[1:]).lstrip()
- tip1 = name + argspec
- doc = ''
- if callable(object):
- try:
- doc = inspect.getdoc(object)
- except:
- pass
- if doc:
- # tip2 is the first separated line of the docstring, like:
- # "Return call tip text for a command."
- # tip3 is the rest of the docstring, like:
- # "The call tip information will be based on ... <snip>
- firstline = doc.split('\n')[0].lstrip()
- if tip1 == firstline or firstline[:len(name)+1] == name+'(':
- tip1 = ''
- else:
- tip1 += '\n\n'
- docpieces = doc.split('\n\n')
- tip2 = docpieces[0]
- tip3 = '\n\n'.join(docpieces[1:])
- tip = '%s%s\n\n%s' % (tip1, tip2, tip3)
- else:
- tip = tip1
- calltip = (name, argspec[1:-1], tip.strip())
- return calltip
-
- def getRoot(command, terminator=None):
- """Return the rightmost root portion of an arbitrary Python command.
-
- Return only the root portion that can be eval()'d without side
- effects. The command would normally terminate with a '(' or
- '.'. The terminator and anything after the terminator will be
- dropped."""
- command = command.split('\n')[-1]
- if command.startswith(sys.ps2):
- command = command[len(sys.ps2):]
- command = command.lstrip()
- command = rtrimTerminus(command, terminator)
- tokens = getTokens(command)
- if not tokens:
- return ''
- if tokens[-1][0] is tokenize.ENDMARKER:
- # Remove the end marker.
- del tokens[-1]
- if not tokens:
- return ''
- if terminator == '.' and \
- (tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP):
- # Trap decimals in numbers, versus the dot operator.
- return ''
- else:
- # Strip off the terminator.
- if terminator and command.endswith(terminator):
- size = 0 - len(terminator)
- command = command[:size]
- command = command.rstrip()
- tokens = getTokens(command)
- tokens.reverse()
- line = ''
- start = None
- prefix = ''
- laststring = '.'
- emptyTypes = ('[]', '()', '{}')
- for token in tokens:
- tokentype = token[0]
- tokenstring = token[1]
- line = token[4]
- if tokentype is tokenize.ENDMARKER:
- continue
- if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
- and laststring != '.':
- # We've reached something that's not part of the root.
- if prefix and line[token[3][1]] != ' ':
- # If it doesn't have a space after it, remove the prefix.
- prefix = ''
- break
- if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
- or (tokentype is tokenize.OP and tokenstring == '.'):
- if prefix:
- # The prefix isn't valid because it comes after a dot.
- prefix = ''
- break
- else:
- # start represents the last known good point in the line.
- start = token[2][1]
- elif len(tokenstring) == 1 and tokenstring in ('[({])}'):
- # Remember, we're working backwords.
- # So prefix += tokenstring would be wrong.
- if prefix in emptyTypes and tokenstring in ('[({'):
- # We've already got an empty type identified so now we
- # are in a nested situation and we can break out with
- # what we've got.
- break
- else:
- prefix = tokenstring + prefix
- else:
- # We've reached something that's not part of the root.
- break
- laststring = tokenstring
- if start is None:
- start = len(line)
- root = line[start:]
- if prefix in emptyTypes:
- # Empty types are safe to be eval()'d and introspected.
- root = prefix + root
- return root
-
- def getTokens(command):
- """Return list of token tuples for command."""
-
- # In case the command is unicode try encoding it
- if type(command) == unicode:
- try:
- command = command.encode(wx.GetDefaultPyEncoding())
- except UnicodeEncodeError:
- pass # otherwise leave it alone
-
- f = cStringIO.StringIO(command)
- # tokens is a list of token tuples, each looking like:
- # (type, string, (srow, scol), (erow, ecol), line)
- tokens = []
- # Can't use list comprehension:
- # tokens = [token for token in tokenize.generate_tokens(f.readline)]
- # because of need to append as much as possible before TokenError.
- try:
- ## This code wasn't backward compatible with Python 2.1.3.
- ##
- ## for token in tokenize.generate_tokens(f.readline):
- ## tokens.append(token)
-
- # This works with Python 2.1.3 (with nested_scopes).
- def eater(*args):
- tokens.append(args)
- tokenize.tokenize_loop(f.readline, eater)
- except tokenize.TokenError:
- # This is due to a premature EOF, which we expect since we are
- # feeding in fragments of Python code.
- pass
- return tokens
-
- def rtrimTerminus(command, terminator=None):
- """Return command minus anything that follows the final terminator."""
- if terminator:
- pieces = command.split(terminator)
- if len(pieces) > 1:
- command = terminator.join(pieces[:-1]) + terminator
- return command
-
- def getBaseObject(object):
- """Return base object and dropSelf indicator for an object."""
- if inspect.isbuiltin(object):
- # Builtin functions don't have an argspec that we can get.
- dropSelf = 0
- elif inspect.ismethod(object):
- # Get the function from the object otherwise
- # inspect.getargspec() complains that the object isn't a
- # Python function.
- try:
- if object.im_self is None:
- # This is an unbound method so we do not drop self
- # from the argspec, since an instance must be passed
- # as the first arg.
- dropSelf = 0
- else:
- dropSelf = 1
- object = object.im_func
- except AttributeError:
- dropSelf = 0
- elif inspect.isclass(object):
- # Get the __init__ method function for the class.
- constructor = getConstructor(object)
- if constructor is not None:
- object = constructor
- dropSelf = 1
- else:
- dropSelf = 0
- elif callable(object):
- # Get the __call__ method instead.
- try:
- object = object.__call__.im_func
- dropSelf = 1
- except AttributeError:
- dropSelf = 0
- else:
- dropSelf = 0
- return object, dropSelf
-
- def getConstructor(object):
- """Return constructor for class object, or None if there isn't one."""
- try:
- return object.__init__.im_func
- except AttributeError:
- for base in object.__bases__:
- constructor = getConstructor(base)
- if constructor is not None:
- return constructor
- return None