| OLD | NEW |
| (Empty) |
| 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() | |
| OLD | NEW |