Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / manhole / explorer.py
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.
5
6
7 """Support for python object introspection and exploration.
8
9 Note that Explorers, what with their list of attributes, are much like
10 manhole.coil.Configurables.  Someone should investigate this further. (TODO)
11
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.
15 """
16
17 # System Imports
18 import inspect, string, sys, types
19 import UserDict
20
21 # Twisted Imports
22 from twisted.spread import pb
23 from twisted.python import reflect
24
25
26 True=(1==1)
27 False=not True
28
29 class Pool(UserDict.UserDict):
30     def getExplorer(self, object, identifier):
31         oid = id(object)
32         if self.data.has_key(oid):
33             # XXX: This potentially returns something with
34             # 'identifier' set to a different value.
35             return self.data[oid]
36         else:
37             klass = typeTable.get(type(object), ExplorerGeneric)
38             e = types.InstanceType(klass, {})
39             self.data[oid] = e
40             klass.__init__(e, object, identifier)
41             return e
42
43 explorerPool = Pool()
44
45 class Explorer(pb.Cacheable):
46     properties = ["id", "identifier"]
47     attributeGroups = []
48     accessors = ["get_refcount"]
49
50     id = None
51     identifier = None
52
53     def __init__(self, object, identifier):
54         self.object = object
55         self.identifier = identifier
56         self.id = id(object)
57
58         self.properties = []
59         reflect.accumulateClassList(self.__class__, 'properties',
60                                     self.properties)
61
62         self.attributeGroups = []
63         reflect.accumulateClassList(self.__class__, 'attributeGroups',
64                                     self.attributeGroups)
65
66         self.accessors = []
67         reflect.accumulateClassList(self.__class__, 'accessors',
68                                     self.accessors)
69
70     def getStateToCopyFor(self, perspective):
71         all = ["properties", "attributeGroups", "accessors"]
72         all.extend(self.properties)
73         all.extend(self.attributeGroups)
74
75         state = {}
76         for key in all:
77             state[key] = getattr(self, key)
78
79         state['view'] = pb.ViewPoint(perspective, self)
80         state['explorerClass'] = self.__class__.__name__
81         return state
82
83     def view_get_refcount(self, perspective):
84         return sys.getrefcount(self)
85
86 class ExplorerGeneric(Explorer):
87     properties = ["str", "repr", "typename"]
88
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__
94
95
96 class ExplorerImmutable(Explorer):
97     properties = ["value"]
98
99     def __init__(self, object, identifier):
100         Explorer.__init__(self, object, identifier)
101         self.value = object
102
103
104 class ExplorerSequence(Explorer):
105     properties = ["len"]
106     attributeGroups = ["elements"]
107     accessors = ["get_elements"]
108
109     def __init__(self, seq, identifier):
110         Explorer.__init__(self, seq, identifier)
111         self.seq = seq
112         self.len = len(seq)
113
114         # Use accessor method to fill me in.
115         self.elements = []
116
117     def get_elements(self):
118         self.len = len(self.seq)
119         l = []
120         for i in xrange(self.len):
121             identifier = "%s[%s]" % (self.identifier, i)
122
123             # GLOBAL: using global explorerPool
124             l.append(explorerPool.getExplorer(self.seq[i], identifier))
125
126         return l
127
128     def view_get_elements(self, perspective):
129         # XXX: set the .elements member of all my remoteCaches
130         return self.get_elements()
131
132
133 class ExplorerMapping(Explorer):
134     properties = ["len"]
135     attributeGroups = ["keys"]
136     accessors = ["get_keys", "get_item"]
137
138     def __init__(self, dct, identifier):
139         Explorer.__init__(self, dct, identifier)
140
141         self.dct = dct
142         self.len = len(dct)
143
144         # Use accessor method to fill me in.
145         self.keys = []
146
147     def get_keys(self):
148         keys = self.dct.keys()
149         self.len = len(keys)
150         l = []
151         for i in xrange(self.len):
152             identifier = "%s.keys()[%s]" % (self.identifier, i)
153
154             # GLOBAL: using global explorerPool
155             l.append(explorerPool.getExplorer(keys[i], identifier))
156
157         return l
158
159     def view_get_keys(self, perspective):
160         # XXX: set the .keys member of all my remoteCaches
161         return self.get_keys()
162
163     def view_get_item(self, perspective, key):
164         if type(key) is types.InstanceType:
165             key = key.object
166
167         item = self.dct[key]
168
169         identifier = "%s[%s]" % (self.identifier, repr(key))
170         # GLOBAL: using global explorerPool
171         item = explorerPool.getExplorer(item, identifier)
172         return item
173
174
175 class ExplorerBuiltin(Explorer):
176     """
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.
180     """
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__
187
188
189 class ExplorerInstance(Explorer):
190     """
191     Attribute groups:
192         - B{methods} -- dictionary of methods
193         - B{data} -- dictionary of data members
194
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.
197
198     TODO: Detail levels (me, me & class, me & class ancestory)
199
200     @ivar klass: the class this is an instance of.
201     """
202     properties = ["klass"]
203     attributeGroups = ["methods", "data"]
204
205     def __init__(self, instance, identifier):
206         Explorer.__init__(self, instance, identifier)
207         members = {}
208         methods = {}
209         for i in dir(instance):
210             # TODO: Make screening of private attributes configurable.
211             if i[0] == '_':
212                 continue
213             mIdentifier = string.join([identifier, i], ".")
214             member = getattr(instance, i)
215             mType = type(member)
216
217             if mType is types.MethodType:
218                 methods[i] = explorerPool.getExplorer(member, mIdentifier)
219             else:
220                 members[i] = explorerPool.getExplorer(member, mIdentifier)
221
222         self.klass = explorerPool.getExplorer(instance.__class__,
223                                               self.identifier +
224                                               '.__class__')
225         self.data = members
226         self.methods = methods
227
228
229 class ExplorerClass(Explorer):
230     """
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
235
236     Attribute groups:
237         - B{methods} -- class methods
238         - B{data} -- other members of the class
239     """
240     properties = ["name", "doc", "bases", "module"]
241     attributeGroups = ["methods", "data"]
242     def __init__(self, theClass, identifier):
243         Explorer.__init__(self, theClass, identifier)
244         if not identifier:
245             identifier = theClass.__name__
246         members = {}
247         methods = {}
248         for i in dir(theClass):
249             if (i[0] == '_') and (i != '__init__'):
250                 continue
251
252             mIdentifier = string.join([identifier, i], ".")
253             member = getattr(theClass, i)
254             mType = type(member)
255
256             if mType is types.MethodType:
257                 methods[i] = explorerPool.getExplorer(member, mIdentifier)
258             else:
259                 members[i] = explorerPool.getExplorer(member, mIdentifier)
260
261         self.name = theClass.__name__
262         self.doc = inspect.getdoc(theClass)
263         self.data = members
264         self.methods = methods
265         self.bases = explorerPool.getExplorer(theClass.__bases__,
266                                               identifier + ".__bases__")
267         self.module = getattr(theClass, '__module__', None)
268
269
270 class ExplorerFunction(Explorer):
271     properties = ["name", "doc", "file", "line","signature"]
272     """
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
278     """
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
285
286         n = (argcount + takesList + takesKeywords)
287         signature = Signature(code.co_varnames[:n])
288
289         if function.func_defaults:
290             i_d = 0
291             for i in xrange(argcount - len(function.func_defaults),
292                             argcount):
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)
297
298                 i_d = i_d + 1
299
300         if takesKeywords:
301             signature.set_keyword(n - 1)
302
303         if takesList:
304             signature.set_varlist(n - 1 - takesKeywords)
305
306         # maybe also: function.func_globals,
307         # or at least func_globals.__name__?
308         # maybe the bytecode, for disassembly-view?
309
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
315
316
317 class ExplorerMethod(ExplorerFunction):
318     properties = ["self", "klass"]
319     """
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
323     """
324     def __init__(self, method, identifier):
325
326         function = method.im_func
327         if type(function) is types.InstanceType:
328             function = function.__call__.im_func
329
330         ExplorerFunction.__init__(self, function, identifier)
331         self.id = id(method)
332         self.klass = explorerPool.getExplorer(method.im_class,
333                                               identifier + '.im_class')
334         self.self = explorerPool.getExplorer(method.im_self,
335                                              identifier + '.im_self')
336
337         if method.im_self:
338             # I'm a bound method -- eat the 'self' arg.
339             self.signature.discardSelf()
340
341
342 class ExplorerModule(Explorer):
343     """
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
347
348     Attribute groups:
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
352
353     (\"Public\" is taken to be \"anything that doesn't start with _\")
354     """
355     properties = ["name","doc","file"]
356     attributeGroups = ["classes", "functions", "data"]
357
358     def __init__(self, module, identifier):
359         Explorer.__init__(self, module, identifier)
360         functions = {}
361         classes = {}
362         data = {}
363         for key, value in module.__dict__.items():
364             if key[0] == '_':
365                 continue
366
367             mIdentifier = "%s.%s" % (identifier, key)
368
369             if type(value) is types.ClassType:
370                 classes[key] = explorerPool.getExplorer(value,
371                                                         mIdentifier)
372             elif type(value) is types.FunctionType:
373                 functions[key] = explorerPool.getExplorer(value,
374                                                           mIdentifier)
375             elif type(value) is types.ModuleType:
376                 pass # pass on imported modules
377             else:
378                 data[key] = explorerPool.getExplorer(value, mIdentifier)
379
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
385         self.data = data
386
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,
402              }
403
404 class Signature(pb.Copyable):
405     """I represent the signature of a callable.
406
407     Signatures are immutable, so don't expect my contents to change once
408     they've been set.
409     """
410     _FLAVOURLESS = None
411     _HAS_DEFAULT = 2
412     _VAR_LIST = 4
413     _KEYWORD_DICT = 8
414
415     def __init__(self, argNames):
416         self.name = argNames
417         self.default = [None] * len(argNames)
418         self.flavour = [None] * len(argNames)
419
420     def get_name(self, arg):
421         return self.name[arg]
422
423     def get_default(self, arg):
424         if arg is types.StringType:
425             arg = self.name.index(arg)
426
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])
432         else:
433             return (False, None)
434
435     def set_default(self, arg, value):
436         if arg is types.StringType:
437             arg = self.name.index(arg)
438
439         self.flavour[arg] = self._HAS_DEFAULT
440         self.default[arg] = value
441
442     def set_varlist(self, arg):
443         if arg is types.StringType:
444             arg = self.name.index(arg)
445
446         self.flavour[arg] = self._VAR_LIST
447
448     def set_keyword(self, arg):
449         if arg is types.StringType:
450             arg = self.name.index(arg)
451
452         self.flavour[arg] = self._KEYWORD_DICT
453
454     def is_varlist(self, arg):
455         if arg is types.StringType:
456             arg = self.name.index(arg)
457
458         return (self.flavour[arg] == self._VAR_LIST)
459
460     def is_keyword(self, arg):
461         if arg is types.StringType:
462             arg = self.name.index(arg)
463
464         return (self.flavour[arg] == self._KEYWORD_DICT)
465
466     def discardSelf(self):
467         """Invoke me to discard the first argument if this is a bound method.
468         """
469         ## if self.name[0] != 'self':
470         ##    log.msg("Warning: Told to discard self, but name is %s" %
471         ##            self.name[0])
472         self.name = self.name[1:]
473         self.default.pop(0)
474         self.flavour.pop(0)
475
476     def getStateToCopy(self):
477         return {'name': tuple(self.name),
478                 'flavour': tuple(self.flavour),
479                 'default': tuple(self.default)}
480
481     def __len__(self):
482         return len(self.name)
483
484     def __str__(self):
485         arglist = []
486         for arg in xrange(len(self)):
487             name = self.get_name(arg)
488             hasDefault, default = self.get_default(arg)
489             if hasDefault:
490                 a = "%s=%s" % (name, default)
491             elif self.is_varlist(arg):
492                 a = "*%s" % (name,)
493             elif self.is_keyword(arg):
494                 a = "**%s" % (name,)
495             else:
496                 a = name
497             arglist.append(a)
498
499         return string.join(arglist,", ")
500
501
502
503
504
505 class CRUFT_WatchyThingie:
506     # TODO:
507     #
508     #  * an exclude mechanism for the watcher's browser, to avoid
509     #    sending back large and uninteresting data structures.
510     #
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.
514     #
515     #  * XXX! need removeWatch()
516
517     def watchIdentifier(self, identifier, callback):
518         """Watch the object returned by evaluating the identifier.
519
520         Whenever I think the object might have changed, I'll send an
521         ObjectLink of it to the callback.
522
523         WARNING: This calls eval() on its argument!
524         """
525         object = eval(identifier,
526                       self.globalNamespace,
527                       self.localNamespace)
528         return self.watchObject(object, identifier, callback)
529
530     def watchObject(self, object, identifier, callback):
531         """Watch the given object.
532
533         Whenever I think the object might have changed, I'll send an
534         ObjectLink of it to the callback.
535
536         The identifier argument is used to generate identifiers for
537         objects which are members of this one.
538         """
539         if type(object) is not types.InstanceType:
540             raise TypeError, "Sorry, can only place a watch on Instances."
541
542         # uninstallers = []
543
544         dct = {}
545         reflect.addMethodNamesToDict(object.__class__, dct, '')
546         for k in object.__dict__.keys():
547             dct[k] = 1
548
549         members = dct.keys()
550
551         clazzNS = {}
552         clazz = types.ClassType('Watching%s%X' %
553                                 (object.__class__.__name__, id(object)),
554                                 (_MonkeysSetattrMixin, object.__class__,),
555                                 clazzNS)
556
557         clazzNS['_watchEmitChanged'] = types.MethodType(
558             lambda slf, i=identifier, b=self, cb=callback:
559             cb(b.browseObject(slf, i)),
560             None, clazz)
561
562         # orig_class = object.__class__
563         object.__class__ = clazz
564
565         for name in members:
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?)
576
577                 monkey = _WatchMonkey(object)
578                 monkey.install(name)
579                 # uninstallers.append(monkey.uninstall)
580
581         # XXX: This probably prevents these objects from ever having a
582         # zero refcount.  Leak, Leak!
583         ## self.watchUninstallers[object] = uninstallers
584
585
586 class _WatchMonkey:
587     """I hang on a method and tell you what I see.
588
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.
591     """
592     oldMethod = None
593
594     def __init__(self, instance):
595         """Make a monkey to hang on this instance object.
596         """
597         self.instance = instance
598
599     def install(self, methodIdentifier):
600         """Install myself on my instance in place of this method.
601         """
602         oldMethod = getattr(self.instance, methodIdentifier, None)
603
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)
610
611     def uninstall(self):
612         """Remove myself from this instance and restore the original method.
613
614         (I hope.)
615         """
616         if self.oldMethod is None:
617             return
618
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])
623         else:
624             setattr(self.instance, self.oldMethod[0], self.oldMethod[1])
625
626     def __call__(self, instance, *a, **kw):
627         """Pretend to be the method I replaced, and ring the bell.
628         """
629         if self.oldMethod[1]:
630             rval = apply(self.oldMethod[1], a, kw)
631         else:
632             rval = None
633
634         instance._watchEmitChanged()
635         return rval
636
637
638 class _MonkeysSetattrMixin:
639     """A mix-in class providing __setattr__ for objects being watched.
640     """
641     def __setattr__(self, k, v):
642         """Set the attribute and ring the bell.
643         """
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)
648         else:
649             self.__dict__[k] = v
650
651         # XXX: Hey, waitasec, did someone just hang a new method on me?
652         #  Do I need to put a monkey on it?
653
654         self._watchEmitChanged()