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) 2001-2004 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, new, 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 = new.instance(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 = new.classobj('Watching%s%X' %
553
(object.__class__.__name__, id(object)),
554
(_MonkeysSetattrMixin, object.__class__,),
557
clazzNS['_watchEmitChanged'] = new.instancemethod(
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] = (
608
new.instancemethod(self, self.instance,
609
self.instance.__class__))
610
self.oldMethod = (methodIdentifier, oldMethod)
613
"""Remove myself from this instance and restore the original method.
617
if self.oldMethod is None:
620
# XXX: This probably doesn't work if multiple monkies are hanging
621
# on a method and they're not removed in order.
622
if self.oldMethod[1] is None:
623
delattr(self.instance, self.oldMethod[0])
625
setattr(self.instance, self.oldMethod[0], self.oldMethod[1])
627
def __call__(self, instance, *a, **kw):
628
"""Pretend to be the method I replaced, and ring the bell.
630
if self.oldMethod[1]:
631
rval = apply(self.oldMethod[1], a, kw)
635
instance._watchEmitChanged()
639
class _MonkeysSetattrMixin:
640
"""A mix-in class providing __setattr__ for objects being watched.
642
def __setattr__(self, k, v):
643
"""Set the attribute and ring the bell.
645
if hasattr(self.__class__.__bases__[1], '__setattr__'):
646
# Hack! Using __bases__[1] is Bad, but since we created
647
# this class, we can be reasonably sure it'll work.
648
self.__class__.__bases__[1].__setattr__(self, k, v)
652
# XXX: Hey, waitasec, did someone just hang a new method on me?
653
# Do I need to put a monkey on it?
655
self._watchEmitChanged()