~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/manhole/explorer.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
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, new, 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 = new.instance(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 = new.classobj('Watching%s%X' %
 
553
                             (object.__class__.__name__, id(object)),
 
554
                             (_MonkeysSetattrMixin, object.__class__,),
 
555
                             clazzNS)
 
556
 
 
557
        clazzNS['_watchEmitChanged'] = new.instancemethod(
 
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] = (
 
608
                new.instancemethod(self, self.instance,
 
609
                                   self.instance.__class__))
 
610
            self.oldMethod = (methodIdentifier, oldMethod)
 
611
 
 
612
    def uninstall(self):
 
613
        """Remove myself from this instance and restore the original method.
 
614
 
 
615
        (I hope.)
 
616
        """
 
617
        if self.oldMethod is None:
 
618
            return
 
619
 
 
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])
 
624
        else:
 
625
            setattr(self.instance, self.oldMethod[0], self.oldMethod[1])
 
626
 
 
627
    def __call__(self, instance, *a, **kw):
 
628
        """Pretend to be the method I replaced, and ring the bell.
 
629
        """
 
630
        if self.oldMethod[1]:
 
631
            rval = apply(self.oldMethod[1], a, kw)
 
632
        else:
 
633
            rval = None
 
634
 
 
635
        instance._watchEmitChanged()
 
636
        return rval
 
637
 
 
638
 
 
639
class _MonkeysSetattrMixin:
 
640
    """A mix-in class providing __setattr__ for objects being watched.
 
641
    """
 
642
    def __setattr__(self, k, v):
 
643
        """Set the attribute and ring the bell.
 
644
        """
 
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)
 
649
        else:
 
650
            self.__dict__[k] = v
 
651
 
 
652
        # XXX: Hey, waitasec, did someone just hang a new method on me?
 
653
        #  Do I need to put a monkey on it?
 
654
 
 
655
        self._watchEmitChanged()