| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 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 |
|
|---|
| 18 |
import inspect, new, string, sys, types |
|---|
| 19 |
import UserDict |
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 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 |
|
|---|
| 34 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 124 |
l.append(explorerPool.getExplorer(self.seq[i], identifier)) |
|---|
| 125 |
|
|---|
| 126 |
return l |
|---|
| 127 |
|
|---|
| 128 |
def view_get_elements(self, perspective): |
|---|
| 129 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 155 |
l.append(explorerPool.getExplorer(keys[i], identifier)) |
|---|
| 156 |
|
|---|
| 157 |
return l |
|---|
| 158 |
|
|---|
| 159 |
def view_get_keys(self, perspective): |
|---|
| 160 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 307 |
|
|---|
| 308 |
|
|---|
| 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 |
|
|---|
| 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 |
|---|
| 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 |
|
|---|
| 428 |
|
|---|
| 429 |
|
|---|
| 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 |
|
|---|
| 470 |
|
|---|
| 471 |
|
|---|
| 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 |
|
|---|
| 507 |
|
|---|
| 508 |
|
|---|
| 509 |
|
|---|
| 510 |
|
|---|
| 511 |
|
|---|
| 512 |
|
|---|
| 513 |
|
|---|
| 514 |
|
|---|
| 515 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 563 |
object.__class__ = clazz |
|---|
| 564 |
|
|---|
| 565 |
for name in members: |
|---|
| 566 |
m = getattr(object, name) |
|---|
| 567 |
|
|---|
| 568 |
if ((type(m) is types.MethodType) |
|---|
| 569 |
and (m.im_self is not None)): |
|---|
| 570 |
|
|---|
| 571 |
|
|---|
| 572 |
|
|---|
| 573 |
|
|---|
| 574 |
|
|---|
| 575 |
|
|---|
| 576 |
|
|---|
| 577 |
monkey = _WatchMonkey(object) |
|---|
| 578 |
monkey.install(name) |
|---|
| 579 |
|
|---|
| 580 |
|
|---|
| 581 |
|
|---|
| 582 |
|
|---|
| 583 |
|
|---|
| 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 |
|
|---|
| 605 |
if oldMethod is not self: |
|---|
| 606 |
|
|---|
| 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 |
|
|---|
| 621 |
|
|---|
| 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 |
|
|---|
| 647 |
|
|---|
| 648 |
self.__class__.__bases__[1].__setattr__(self, k, v) |
|---|
| 649 |
else: |
|---|
| 650 |
self.__dict__[k] = v |
|---|
| 651 |
|
|---|
| 652 |
|
|---|
| 653 |
|
|---|
| 654 |
|
|---|
| 655 |
self._watchEmitChanged() |
|---|