1 # -*- test-case-name: twisted.test.test_explorer -*-
2 # $Id: explorer.py,v 1.6 2003/02/18 21:15:30 acapnotic Exp $
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
7 """Support for python object introspection and exploration.
9 Note that Explorers, what with their list of attributes, are much like
10 manhole.coil.Configurables. Someone should investigate this further. (TODO)
12 Also TODO: Determine how much code in here (particularly the function
13 signature stuff) can be replaced with functions available in the
14 L{inspect} module available in Python 2.1.
18 import inspect, string, sys, types
22 from twisted.spread import pb
23 from twisted.python import reflect
29 class Pool(UserDict.UserDict):
30 def getExplorer(self, object, identifier):
32 if self.data.has_key(oid):
33 # XXX: This potentially returns something with
34 # 'identifier' set to a different value.
37 klass = typeTable.get(type(object), ExplorerGeneric)
38 e = types.InstanceType(klass, {})
40 klass.__init__(e, object, identifier)
45 class Explorer(pb.Cacheable):
46 properties = ["id", "identifier"]
48 accessors = ["get_refcount"]
53 def __init__(self, object, identifier):
55 self.identifier = identifier
59 reflect.accumulateClassList(self.__class__, 'properties',
62 self.attributeGroups = []
63 reflect.accumulateClassList(self.__class__, 'attributeGroups',
67 reflect.accumulateClassList(self.__class__, 'accessors',
70 def getStateToCopyFor(self, perspective):
71 all = ["properties", "attributeGroups", "accessors"]
72 all.extend(self.properties)
73 all.extend(self.attributeGroups)
77 state[key] = getattr(self, key)
79 state['view'] = pb.ViewPoint(perspective, self)
80 state['explorerClass'] = self.__class__.__name__
83 def view_get_refcount(self, perspective):
84 return sys.getrefcount(self)
86 class ExplorerGeneric(Explorer):
87 properties = ["str", "repr", "typename"]
89 def __init__(self, object, identifier):
90 Explorer.__init__(self, object, identifier)
91 self.str = str(object)
92 self.repr = repr(object)
93 self.typename = type(object).__name__
96 class ExplorerImmutable(Explorer):
97 properties = ["value"]
99 def __init__(self, object, identifier):
100 Explorer.__init__(self, object, identifier)
104 class ExplorerSequence(Explorer):
106 attributeGroups = ["elements"]
107 accessors = ["get_elements"]
109 def __init__(self, seq, identifier):
110 Explorer.__init__(self, seq, identifier)
114 # Use accessor method to fill me in.
117 def get_elements(self):
118 self.len = len(self.seq)
120 for i in xrange(self.len):
121 identifier = "%s[%s]" % (self.identifier, i)
123 # GLOBAL: using global explorerPool
124 l.append(explorerPool.getExplorer(self.seq[i], identifier))
128 def view_get_elements(self, perspective):
129 # XXX: set the .elements member of all my remoteCaches
130 return self.get_elements()
133 class ExplorerMapping(Explorer):
135 attributeGroups = ["keys"]
136 accessors = ["get_keys", "get_item"]
138 def __init__(self, dct, identifier):
139 Explorer.__init__(self, dct, identifier)
144 # Use accessor method to fill me in.
148 keys = self.dct.keys()
151 for i in xrange(self.len):
152 identifier = "%s.keys()[%s]" % (self.identifier, i)
154 # GLOBAL: using global explorerPool
155 l.append(explorerPool.getExplorer(keys[i], identifier))
159 def view_get_keys(self, perspective):
160 # XXX: set the .keys member of all my remoteCaches
161 return self.get_keys()
163 def view_get_item(self, perspective, key):
164 if type(key) is types.InstanceType:
169 identifier = "%s[%s]" % (self.identifier, repr(key))
170 # GLOBAL: using global explorerPool
171 item = explorerPool.getExplorer(item, identifier)
175 class ExplorerBuiltin(Explorer):
177 @ivar name: the name the function was defined as
178 @ivar doc: function's docstring, or C{None} if unavailable
179 @ivar self: if not C{None}, the function is a method of this object.
181 properties = ["doc", "name", "self"]
182 def __init__(self, function, identifier):
183 Explorer.__init__(self, function, identifier)
184 self.doc = function.__doc__
185 self.name = function.__name__
186 self.self = function.__self__
189 class ExplorerInstance(Explorer):
192 - B{methods} -- dictionary of methods
193 - B{data} -- dictionary of data members
195 Note these are only the *instance* methods and members --
196 if you want the class methods, you'll have to look up the class.
198 TODO: Detail levels (me, me & class, me & class ancestory)
200 @ivar klass: the class this is an instance of.
202 properties = ["klass"]
203 attributeGroups = ["methods", "data"]
205 def __init__(self, instance, identifier):
206 Explorer.__init__(self, instance, identifier)
209 for i in dir(instance):
210 # TODO: Make screening of private attributes configurable.
213 mIdentifier = string.join([identifier, i], ".")
214 member = getattr(instance, i)
217 if mType is types.MethodType:
218 methods[i] = explorerPool.getExplorer(member, mIdentifier)
220 members[i] = explorerPool.getExplorer(member, mIdentifier)
222 self.klass = explorerPool.getExplorer(instance.__class__,
226 self.methods = methods
229 class ExplorerClass(Explorer):
231 @ivar name: the name the class was defined with
232 @ivar doc: the class's docstring
233 @ivar bases: a list of this class's base classes.
234 @ivar module: the module the class is defined in
237 - B{methods} -- class methods
238 - B{data} -- other members of the class
240 properties = ["name", "doc", "bases", "module"]
241 attributeGroups = ["methods", "data"]
242 def __init__(self, theClass, identifier):
243 Explorer.__init__(self, theClass, identifier)
245 identifier = theClass.__name__
248 for i in dir(theClass):
249 if (i[0] == '_') and (i != '__init__'):
252 mIdentifier = string.join([identifier, i], ".")
253 member = getattr(theClass, i)
256 if mType is types.MethodType:
257 methods[i] = explorerPool.getExplorer(member, mIdentifier)
259 members[i] = explorerPool.getExplorer(member, mIdentifier)
261 self.name = theClass.__name__
262 self.doc = inspect.getdoc(theClass)
264 self.methods = methods
265 self.bases = explorerPool.getExplorer(theClass.__bases__,
266 identifier + ".__bases__")
267 self.module = getattr(theClass, '__module__', None)
270 class ExplorerFunction(Explorer):
271 properties = ["name", "doc", "file", "line","signature"]
273 name -- the name the function was defined as
274 signature -- the function's calling signature (Signature instance)
275 doc -- the function's docstring
276 file -- the file the function is defined in
277 line -- the line in the file the function begins on
279 def __init__(self, function, identifier):
280 Explorer.__init__(self, function, identifier)
281 code = function.func_code
282 argcount = code.co_argcount
283 takesList = (code.co_flags & 0x04) and 1
284 takesKeywords = (code.co_flags & 0x08) and 1
286 n = (argcount + takesList + takesKeywords)
287 signature = Signature(code.co_varnames[:n])
289 if function.func_defaults:
291 for i in xrange(argcount - len(function.func_defaults),
293 default = function.func_defaults[i_d]
294 default = explorerPool.getExplorer(
295 default, '%s.func_defaults[%d]' % (identifier, i_d))
296 signature.set_default(i, default)
301 signature.set_keyword(n - 1)
304 signature.set_varlist(n - 1 - takesKeywords)
306 # maybe also: function.func_globals,
307 # or at least func_globals.__name__?
308 # maybe the bytecode, for disassembly-view?
310 self.name = function.__name__
311 self.signature = signature
312 self.doc = inspect.getdoc(function)
313 self.file = code.co_filename
314 self.line = code.co_firstlineno
317 class ExplorerMethod(ExplorerFunction):
318 properties = ["self", "klass"]
320 In addition to ExplorerFunction properties:
321 self -- the object I am bound to, or None if unbound
322 klass -- the class I am a method of
324 def __init__(self, method, identifier):
326 function = method.im_func
327 if type(function) is types.InstanceType:
328 function = function.__call__.im_func
330 ExplorerFunction.__init__(self, function, identifier)
332 self.klass = explorerPool.getExplorer(method.im_class,
333 identifier + '.im_class')
334 self.self = explorerPool.getExplorer(method.im_self,
335 identifier + '.im_self')
338 # I'm a bound method -- eat the 'self' arg.
339 self.signature.discardSelf()
342 class ExplorerModule(Explorer):
344 @ivar name: the name the module was defined as
345 @ivar doc: documentation string for the module
346 @ivar file: the file the module is defined in
349 - B{classes} -- the public classes provided by the module
350 - B{functions} -- the public functions provided by the module
351 - B{data} -- the public data members provided by the module
353 (\"Public\" is taken to be \"anything that doesn't start with _\")
355 properties = ["name","doc","file"]
356 attributeGroups = ["classes", "functions", "data"]
358 def __init__(self, module, identifier):
359 Explorer.__init__(self, module, identifier)
363 for key, value in module.__dict__.items():
367 mIdentifier = "%s.%s" % (identifier, key)
369 if type(value) is types.ClassType:
370 classes[key] = explorerPool.getExplorer(value,
372 elif type(value) is types.FunctionType:
373 functions[key] = explorerPool.getExplorer(value,
375 elif type(value) is types.ModuleType:
376 pass # pass on imported modules
378 data[key] = explorerPool.getExplorer(value, mIdentifier)
380 self.name = module.__name__
381 self.doc = inspect.getdoc(module)
382 self.file = getattr(module, '__file__', None)
383 self.classes = classes
384 self.functions = functions
387 typeTable = {types.InstanceType: ExplorerInstance,
388 types.ClassType: ExplorerClass,
389 types.MethodType: ExplorerMethod,
390 types.FunctionType: ExplorerFunction,
391 types.ModuleType: ExplorerModule,
392 types.BuiltinFunctionType: ExplorerBuiltin,
393 types.ListType: ExplorerSequence,
394 types.TupleType: ExplorerSequence,
395 types.DictType: ExplorerMapping,
396 types.StringType: ExplorerImmutable,
397 types.NoneType: ExplorerImmutable,
398 types.IntType: ExplorerImmutable,
399 types.FloatType: ExplorerImmutable,
400 types.LongType: ExplorerImmutable,
401 types.ComplexType: ExplorerImmutable,
404 class Signature(pb.Copyable):
405 """I represent the signature of a callable.
407 Signatures are immutable, so don't expect my contents to change once
415 def __init__(self, argNames):
417 self.default = [None] * len(argNames)
418 self.flavour = [None] * len(argNames)
420 def get_name(self, arg):
421 return self.name[arg]
423 def get_default(self, arg):
424 if arg is types.StringType:
425 arg = self.name.index(arg)
427 # Wouldn't it be nice if we just returned "None" when there
428 # wasn't a default? Well, yes, but often times "None" *is*
429 # the default, so return a tuple instead.
430 if self.flavour[arg] == self._HAS_DEFAULT:
431 return (True, self.default[arg])
435 def set_default(self, arg, value):
436 if arg is types.StringType:
437 arg = self.name.index(arg)
439 self.flavour[arg] = self._HAS_DEFAULT
440 self.default[arg] = value
442 def set_varlist(self, arg):
443 if arg is types.StringType:
444 arg = self.name.index(arg)
446 self.flavour[arg] = self._VAR_LIST
448 def set_keyword(self, arg):
449 if arg is types.StringType:
450 arg = self.name.index(arg)
452 self.flavour[arg] = self._KEYWORD_DICT
454 def is_varlist(self, arg):
455 if arg is types.StringType:
456 arg = self.name.index(arg)
458 return (self.flavour[arg] == self._VAR_LIST)
460 def is_keyword(self, arg):
461 if arg is types.StringType:
462 arg = self.name.index(arg)
464 return (self.flavour[arg] == self._KEYWORD_DICT)
466 def discardSelf(self):
467 """Invoke me to discard the first argument if this is a bound method.
469 ## if self.name[0] != 'self':
470 ## log.msg("Warning: Told to discard self, but name is %s" %
472 self.name = self.name[1:]
476 def getStateToCopy(self):
477 return {'name': tuple(self.name),
478 'flavour': tuple(self.flavour),
479 'default': tuple(self.default)}
482 return len(self.name)
486 for arg in xrange(len(self)):
487 name = self.get_name(arg)
488 hasDefault, default = self.get_default(arg)
490 a = "%s=%s" % (name, default)
491 elif self.is_varlist(arg):
493 elif self.is_keyword(arg):
499 return string.join(arglist,", ")
505 class CRUFT_WatchyThingie:
508 # * an exclude mechanism for the watcher's browser, to avoid
509 # sending back large and uninteresting data structures.
511 # * an exclude mechanism for the watcher's trigger, to avoid
512 # triggering on some frequently-called-method-that-doesn't-
513 # actually-change-anything.
515 # * XXX! need removeWatch()
517 def watchIdentifier(self, identifier, callback):
518 """Watch the object returned by evaluating the identifier.
520 Whenever I think the object might have changed, I'll send an
521 ObjectLink of it to the callback.
523 WARNING: This calls eval() on its argument!
525 object = eval(identifier,
526 self.globalNamespace,
528 return self.watchObject(object, identifier, callback)
530 def watchObject(self, object, identifier, callback):
531 """Watch the given object.
533 Whenever I think the object might have changed, I'll send an
534 ObjectLink of it to the callback.
536 The identifier argument is used to generate identifiers for
537 objects which are members of this one.
539 if type(object) is not types.InstanceType:
540 raise TypeError, "Sorry, can only place a watch on Instances."
545 reflect.addMethodNamesToDict(object.__class__, dct, '')
546 for k in object.__dict__.keys():
552 clazz = types.ClassType('Watching%s%X' %
553 (object.__class__.__name__, id(object)),
554 (_MonkeysSetattrMixin, object.__class__,),
557 clazzNS['_watchEmitChanged'] = types.MethodType(
558 lambda slf, i=identifier, b=self, cb=callback:
559 cb(b.browseObject(slf, i)),
562 # orig_class = object.__class__
563 object.__class__ = clazz
566 m = getattr(object, name)
567 # Only hook bound methods.
568 if ((type(m) is types.MethodType)
569 and (m.im_self is not None)):
570 # What's the use of putting watch monkeys on methods
571 # in addition to __setattr__? Well, um, uh, if the
572 # methods modify their attributes (i.e. add a key to
573 # a dictionary) instead of [re]setting them, then
574 # we wouldn't know about it unless we did this.
575 # (Is that convincing?)
577 monkey = _WatchMonkey(object)
579 # uninstallers.append(monkey.uninstall)
581 # XXX: This probably prevents these objects from ever having a
582 # zero refcount. Leak, Leak!
583 ## self.watchUninstallers[object] = uninstallers
587 """I hang on a method and tell you what I see.
589 TODO: Aya! Now I just do browseObject all the time, but I could
590 tell you what got called with what when and returning what.
594 def __init__(self, instance):
595 """Make a monkey to hang on this instance object.
597 self.instance = instance
599 def install(self, methodIdentifier):
600 """Install myself on my instance in place of this method.
602 oldMethod = getattr(self.instance, methodIdentifier, None)
604 # XXX: this conditional probably isn't effective.
605 if oldMethod is not self:
606 # avoid triggering __setattr__
607 self.instance.__dict__[methodIdentifier] = types.MethodType(
608 self, self.instance, self.instance.__class__)
609 self.oldMethod = (methodIdentifier, oldMethod)
612 """Remove myself from this instance and restore the original method.
616 if self.oldMethod is None:
619 # XXX: This probably doesn't work if multiple monkies are hanging
620 # on a method and they're not removed in order.
621 if self.oldMethod[1] is None:
622 delattr(self.instance, self.oldMethod[0])
624 setattr(self.instance, self.oldMethod[0], self.oldMethod[1])
626 def __call__(self, instance, *a, **kw):
627 """Pretend to be the method I replaced, and ring the bell.
629 if self.oldMethod[1]:
630 rval = apply(self.oldMethod[1], a, kw)
634 instance._watchEmitChanged()
638 class _MonkeysSetattrMixin:
639 """A mix-in class providing __setattr__ for objects being watched.
641 def __setattr__(self, k, v):
642 """Set the attribute and ring the bell.
644 if hasattr(self.__class__.__bases__[1], '__setattr__'):
645 # Hack! Using __bases__[1] is Bad, but since we created
646 # this class, we can be reasonably sure it'll work.
647 self.__class__.__bases__[1].__setattr__(self, k, v)
651 # XXX: Hey, waitasec, did someone just hang a new method on me?
652 # Do I need to put a monkey on it?
654 self._watchEmitChanged()