"""Support for python object introspection and exploration.
Note that Explorers, what with their list of attributes, are much like
manhole.coil.Configurables. Someone should investigate this further. (TODO)
Also TODO: Determine how much code in here (particularly the function
signature stuff) can be replaced with functions available in the
L{inspect} module available in Python 2.1.
"""
import inspect, new, string, sys, types
import UserDict
from twisted.spread import pb
from twisted.python import reflect
True=(1==1)
False=not True
class Pool(UserDict.UserDict):
def getExplorer(self, object, identifier):
oid = id(object)
if self.data.has_key(oid):
return self.data[oid]
else:
klass = typeTable.get(type(object), ExplorerGeneric)
e = new.instance(klass, {})
self.data[oid] = e
klass.__init__(e, object, identifier)
return e
explorerPool = Pool()
class Explorer(pb.Cacheable):
properties = ["id", "identifier"]
attributeGroups = []
accessors = ["get_refcount"]
id = None
identifier = None
def __init__(self, object, identifier):
self.object = object
self.identifier = identifier
self.id = id(object)
self.properties = []
reflect.accumulateClassList(self.__class__, 'properties',
self.properties)
self.attributeGroups = []
reflect.accumulateClassList(self.__class__, 'attributeGroups',
self.attributeGroups)
self.accessors = []
reflect.accumulateClassList(self.__class__, 'accessors',
self.accessors)
def getStateToCopyFor(self, perspective):
all = ["properties", "attributeGroups", "accessors"]
all.extend(self.properties)
all.extend(self.attributeGroups)
state = {}
for key in all:
state[key] = getattr(self, key)
state['view'] = pb.ViewPoint(perspective, self)
state['explorerClass'] = self.__class__.__name__
return state
def view_get_refcount(self, perspective):
return sys.getrefcount(self)
class ExplorerGeneric(Explorer):
properties = ["str", "repr", "typename"]
def __init__(self, object, identifier):
Explorer.__init__(self, object, identifier)
self.str = str(object)
self.repr = repr(object)
self.typename = type(object).__name__
class ExplorerImmutable(Explorer):
properties = ["value"]
def __init__(self, object, identifier):
Explorer.__init__(self, object, identifier)
self.value = object
class ExplorerSequence(Explorer):
properties = ["len"]
attributeGroups = ["elements"]
accessors = ["get_elements"]
def __init__(self, seq, identifier):
Explorer.__init__(self, seq, identifier)
self.seq = seq
self.len = len(seq)
self.elements = []
def get_elements(self):
self.len = len(self.seq)
l = []
for i in xrange(self.len):
identifier = "%s[%s]" % (self.identifier, i)
l.append(explorerPool.getExplorer(self.seq[i], identifier))
return l
def view_get_elements(self, perspective):
return self.get_elements()
class ExplorerMapping(Explorer):
properties = ["len"]
attributeGroups = ["keys"]
accessors = ["get_keys", "get_item"]
def __init__(self, dct, identifier):
Explorer.__init__(self, dct, identifier)
self.dct = dct
self.len = len(dct)
self.keys = []
def get_keys(self):
keys = self.dct.keys()
self.len = len(keys)
l = []
for i in xrange(self.len):
identifier = "%s.keys()[%s]" % (self.identifier, i)
l.append(explorerPool.getExplorer(keys[i], identifier))
return l
def view_get_keys(self, perspective):
return self.get_keys()
def view_get_item(self, perspective, key):
if type(key) is types.InstanceType:
key = key.object
item = self.dct[key]
identifier = "%s[%s]" % (self.identifier, repr(key))
item = explorerPool.getExplorer(item, identifier)
return item
class ExplorerBuiltin(Explorer):
"""
@ivar name: the name the function was defined as
@ivar doc: function's docstring, or C{None} if unavailable
@ivar self: if not C{None}, the function is a method of this object.
"""
properties = ["doc", "name", "self"]
def __init__(self, function, identifier):
Explorer.__init__(self, function, identifier)
self.doc = function.__doc__
self.name = function.__name__
self.self = function.__self__
class ExplorerInstance(Explorer):
"""
Attribute groups:
- B{methods} -- dictionary of methods
- B{data} -- dictionary of data members
Note these are only the *instance* methods and members --
if you want the class methods, you'll have to look up the class.
TODO: Detail levels (me, me & class, me & class ancestory)
@ivar klass: the class this is an instance of.
"""
properties = ["klass"]
attributeGroups = ["methods", "data"]
def __init__(self, instance, identifier):
Explorer.__init__(self, instance, identifier)
members = {}
methods = {}
for i in dir(instance):
if i[0] == '_':
continue
mIdentifier = string.join([identifier, i], ".")
member = getattr(instance, i)
mType = type(member)
if mType is types.MethodType:
methods[i] = explorerPool.getExplorer(member, mIdentifier)
else:
members[i] = explorerPool.getExplorer(member, mIdentifier)
self.klass = explorerPool.getExplorer(instance.__class__,
self.identifier +
'.__class__')
self.data = members
self.methods = methods
class ExplorerClass(Explorer):
"""
@ivar name: the name the class was defined with
@ivar doc: the class's docstring
@ivar bases: a list of this class's base classes.
@ivar module: the module the class is defined in
Attribute groups:
- B{methods} -- class methods
- B{data} -- other members of the class
"""
properties = ["name", "doc", "bases", "module"]
attributeGroups = ["methods", "data"]
def __init__(self, theClass, identifier):
Explorer.__init__(self, theClass, identifier)
if not identifier:
identifier = theClass.__name__
members = {}
methods = {}
for i in dir(theClass):
if (i[0] == '_') and (i != '__init__'):
continue
mIdentifier = string.join([identifier, i], ".")
member = getattr(theClass, i)
mType = type(member)
if mType is types.MethodType:
methods[i] = explorerPool.getExplorer(member, mIdentifier)
else:
members[i] = explorerPool.getExplorer(member, mIdentifier)
self.name = theClass.__name__
self.doc = inspect.getdoc(theClass)
self.data = members
self.methods = methods
self.bases = explorerPool.getExplorer(theClass.__bases__,
identifier + ".__bases__")
self.module = getattr(theClass, '__module__', None)
class ExplorerFunction(Explorer):
properties = ["name", "doc", "file", "line","signature"]
"""
name -- the name the function was defined as
signature -- the function's calling signature (Signature instance)
doc -- the function's docstring
file -- the file the function is defined in
line -- the line in the file the function begins on
"""
def __init__(self, function, identifier):
Explorer.__init__(self, function, identifier)
code = function.func_code
argcount = code.co_argcount
takesList = (code.co_flags & 0x04) and 1
takesKeywords = (code.co_flags & 0x08) and 1
n = (argcount + takesList + takesKeywords)
signature = Signature(code.co_varnames[:n])
if function.func_defaults:
i_d = 0
for i in xrange(argcount - len(function.func_defaults),
argcount):
default = function.func_defaults[i_d]
default = explorerPool.getExplorer(
default, '%s.func_defaults[%d]' % (identifier, i_d))
signature.set_default(i, default)
i_d = i_d + 1
if takesKeywords:
signature.set_keyword(n - 1)
if takesList:
signature.set_varlist(n - 1 - takesKeywords)
self.name = function.__name__
self.signature = signature
self.doc = inspect.getdoc(function)
self.file = code.co_filename
self.line = code.co_firstlineno
class ExplorerMethod(ExplorerFunction):
properties = ["self", "klass"]
"""
In addition to ExplorerFunction properties:
self -- the object I am bound to, or None if unbound
klass -- the class I am a method of
"""
def __init__(self, method, identifier):
function = method.im_func
if type(function) is types.InstanceType:
function = function.__call__.im_func
ExplorerFunction.__init__(self, function, identifier)
self.id = id(method)
self.klass = explorerPool.getExplorer(method.im_class,
identifier + '.im_class')
self.self = explorerPool.getExplorer(method.im_self,
identifier + '.im_self')
if method.im_self:
self.signature.discardSelf()
class ExplorerModule(Explorer):
"""
@ivar name: the name the module was defined as
@ivar doc: documentation string for the module
@ivar file: the file the module is defined in
Attribute groups:
- B{classes} -- the public classes provided by the module
- B{functions} -- the public functions provided by the module
- B{data} -- the public data members provided by the module
(\"Public\" is taken to be \"anything that doesn't start with _\")
"""
properties = ["name","doc","file"]
attributeGroups = ["classes", "functions", "data"]
def __init__(self, module, identifier):
Explorer.__init__(self, module, identifier)
functions = {}
classes = {}
data = {}
for key, value in module.__dict__.items():
if key[0] == '_':
continue
mIdentifier = "%s.%s" % (identifier, key)
if type(value) is types.ClassType:
classes[key] = explorerPool.getExplorer(value,
mIdentifier)
elif type(value) is types.FunctionType:
functions[key] = explorerPool.getExplorer(value,
mIdentifier)
elif type(value) is types.ModuleType:
pass else:
data[key] = explorerPool.getExplorer(value, mIdentifier)
self.name = module.__name__
self.doc = inspect.getdoc(module)
self.file = getattr(module, '__file__', None)
self.classes = classes
self.functions = functions
self.data = data
typeTable = {types.InstanceType: ExplorerInstance,
types.ClassType: ExplorerClass,
types.MethodType: ExplorerMethod,
types.FunctionType: ExplorerFunction,
types.ModuleType: ExplorerModule,
types.BuiltinFunctionType: ExplorerBuiltin,
types.ListType: ExplorerSequence,
types.TupleType: ExplorerSequence,
types.DictType: ExplorerMapping,
types.StringType: ExplorerImmutable,
types.NoneType: ExplorerImmutable,
types.IntType: ExplorerImmutable,
types.FloatType: ExplorerImmutable,
types.LongType: ExplorerImmutable,
types.ComplexType: ExplorerImmutable,
}
class Signature(pb.Copyable):
"""I represent the signature of a callable.
Signatures are immutable, so don't expect my contents to change once
they've been set.
"""
_FLAVOURLESS = None
_HAS_DEFAULT = 2
_VAR_LIST = 4
_KEYWORD_DICT = 8
def __init__(self, argNames):
self.name = argNames
self.default = [None] * len(argNames)
self.flavour = [None] * len(argNames)
def get_name(self, arg):
return self.name[arg]
def get_default(self, arg):
if arg is types.StringType:
arg = self.name.index(arg)
if self.flavour[arg] == self._HAS_DEFAULT:
return (True, self.default[arg])
else:
return (False, None)
def set_default(self, arg, value):
if arg is types.StringType:
arg = self.name.index(arg)
self.flavour[arg] = self._HAS_DEFAULT
self.default[arg] = value
def set_varlist(self, arg):
if arg is types.StringType:
arg = self.name.index(arg)
self.flavour[arg] = self._VAR_LIST
def set_keyword(self, arg):
if arg is types.StringType:
arg = self.name.index(arg)
self.flavour[arg] = self._KEYWORD_DICT
def is_varlist(self, arg):
if arg is types.StringType:
arg = self.name.index(arg)
return (self.flavour[arg] == self._VAR_LIST)
def is_keyword(self, arg):
if arg is types.StringType:
arg = self.name.index(arg)
return (self.flavour[arg] == self._KEYWORD_DICT)
def discardSelf(self):
"""Invoke me to discard the first argument if this is a bound method.
"""
self.name = self.name[1:]
self.default.pop(0)
self.flavour.pop(0)
def getStateToCopy(self):
return {'name': tuple(self.name),
'flavour': tuple(self.flavour),
'default': tuple(self.default)}
def __len__(self):
return len(self.name)
def __str__(self):
arglist = []
for arg in xrange(len(self)):
name = self.get_name(arg)
hasDefault, default = self.get_default(arg)
if hasDefault:
a = "%s=%s" % (name, default)
elif self.is_varlist(arg):
a = "*%s" % (name,)
elif self.is_keyword(arg):
a = "**%s" % (name,)
else:
a = name
arglist.append(a)
return string.join(arglist,", ")
class CRUFT_WatchyThingie:
def watchIdentifier(self, identifier, callback):
"""Watch the object returned by evaluating the identifier.
Whenever I think the object might have changed, I'll send an
ObjectLink of it to the callback.
WARNING: This calls eval() on its argument!
"""
object = eval(identifier,
self.globalNamespace,
self.localNamespace)
return self.watchObject(object, identifier, callback)
def watchObject(self, object, identifier, callback):
"""Watch the given object.
Whenever I think the object might have changed, I'll send an
ObjectLink of it to the callback.
The identifier argument is used to generate identifiers for
objects which are members of this one.
"""
if type(object) is not types.InstanceType:
raise TypeError, "Sorry, can only place a watch on Instances."
dct = {}
reflect.addMethodNamesToDict(object.__class__, dct, '')
for k in object.__dict__.keys():
dct[k] = 1
members = dct.keys()
clazzNS = {}
clazz = new.classobj('Watching%s%X' %
(object.__class__.__name__, id(object)),
(_MonkeysSetattrMixin, object.__class__,),
clazzNS)
clazzNS['_watchEmitChanged'] = new.instancemethod(
lambda slf, i=identifier, b=self, cb=callback:
cb(b.browseObject(slf, i)),
None, clazz)
object.__class__ = clazz
for name in members:
m = getattr(object, name)
if ((type(m) is types.MethodType)
and (m.im_self is not None)):
monkey = _WatchMonkey(object)
monkey.install(name)
class _WatchMonkey:
"""I hang on a method and tell you what I see.
TODO: Aya! Now I just do browseObject all the time, but I could
tell you what got called with what when and returning what.
"""
oldMethod = None
def __init__(self, instance):
"""Make a monkey to hang on this instance object.
"""
self.instance = instance
def install(self, methodIdentifier):
"""Install myself on my instance in place of this method.
"""
oldMethod = getattr(self.instance, methodIdentifier, None)
if oldMethod is not self:
self.instance.__dict__[methodIdentifier] = (
new.instancemethod(self, self.instance,
self.instance.__class__))
self.oldMethod = (methodIdentifier, oldMethod)
def uninstall(self):
"""Remove myself from this instance and restore the original method.
(I hope.)
"""
if self.oldMethod is None:
return
if self.oldMethod[1] is None:
delattr(self.instance, self.oldMethod[0])
else:
setattr(self.instance, self.oldMethod[0], self.oldMethod[1])
def __call__(self, instance, *a, **kw):
"""Pretend to be the method I replaced, and ring the bell.
"""
if self.oldMethod[1]:
rval = apply(self.oldMethod[1], a, kw)
else:
rval = None
instance._watchEmitChanged()
return rval
class _MonkeysSetattrMixin:
"""A mix-in class providing __setattr__ for objects being watched.
"""
def __setattr__(self, k, v):
"""Set the attribute and ring the bell.
"""
if hasattr(self.__class__.__bases__[1], '__setattr__'):
self.__class__.__bases__[1].__setattr__(self, k, v)
else:
self.__dict__[k] = v
self._watchEmitChanged()