| OLD | NEW |
| (Empty) |
| 1 | |
| 2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 | |
| 6 """ | |
| 7 This module represents flavors of remotely acessible objects. | |
| 8 | |
| 9 Currently this is only objects accessible through Perspective Broker, but will | |
| 10 hopefully encompass all forms of remote access which can emulate subsets of PB | |
| 11 (such as XMLRPC or SOAP). | |
| 12 | |
| 13 Future Plans: Optimization. Exploitation of new-style object model. | |
| 14 Optimizations to this module should not affect external-use semantics at all, | |
| 15 but may have a small impact on users who subclass and override methods. | |
| 16 | |
| 17 @author: U{Glyph Lefkowitz<mailto:glyph@twistedmatrix.com>} | |
| 18 """ | |
| 19 | |
| 20 __version__ = "$Revision: 1.32 $"[11:-2] | |
| 21 | |
| 22 # NOTE: this module should NOT import pb; it is supposed to be a module which | |
| 23 # abstractly defines remotely accessible types. Many of these types expect to | |
| 24 # be serialized by Jelly, but they ought to be accessible through other | |
| 25 # mechanisms (like XMLRPC) | |
| 26 | |
| 27 # system imports | |
| 28 import sys | |
| 29 from zope.interface import implements, Interface | |
| 30 | |
| 31 # twisted imports | |
| 32 from twisted.python import log, reflect | |
| 33 | |
| 34 # sibling imports | |
| 35 from jelly import setUnjellyableForClass, setUnjellyableForClassTree, setUnjelly
ableFactoryForClass, unjellyableRegistry | |
| 36 from jelly import Jellyable, Unjellyable, _Dummy, _DummyNewStyle | |
| 37 from jelly import setInstanceState, getInstanceState | |
| 38 | |
| 39 # compatibility | |
| 40 setCopierForClass = setUnjellyableForClass | |
| 41 setCopierForClassTree = setUnjellyableForClassTree | |
| 42 setFactoryForClass = setUnjellyableFactoryForClass | |
| 43 copyTags = unjellyableRegistry | |
| 44 | |
| 45 copy_atom = "copy" | |
| 46 cache_atom = "cache" | |
| 47 cached_atom = "cached" | |
| 48 remote_atom = "remote" | |
| 49 | |
| 50 | |
| 51 class NoSuchMethod(AttributeError): | |
| 52 """Raised if there is no such remote method""" | |
| 53 | |
| 54 | |
| 55 class IPBRoot(Interface): | |
| 56 """Factory for root Referenceable objects for PB servers.""" | |
| 57 | |
| 58 def rootObject(broker): | |
| 59 """Return root Referenceable for broker.""" | |
| 60 | |
| 61 | |
| 62 class Serializable(Jellyable): | |
| 63 """An object that can be passed remotely. | |
| 64 | |
| 65 I am a style of object which can be serialized by Perspective | |
| 66 Broker. Objects which wish to be referenceable or copied remotely | |
| 67 have to subclass Serializable. However, clients of Perspective | |
| 68 Broker will probably not want to directly subclass Serializable; the | |
| 69 Flavors of transferable objects are listed below. | |
| 70 | |
| 71 What it means to be \"Serializable\" is that an object can be | |
| 72 passed to or returned from a remote method. Certain basic types | |
| 73 (dictionaries, lists, tuples, numbers, strings) are serializable by | |
| 74 default; however, classes need to choose a specific serialization | |
| 75 style: L{Referenceable}, L{Viewable}, L{Copyable} or L{Cacheable}. | |
| 76 | |
| 77 You may also pass C{[lists, dictionaries, tuples]} of L{Serializable} | |
| 78 instances to or return them from remote methods, as many levels deep | |
| 79 as you like. | |
| 80 """ | |
| 81 | |
| 82 def processUniqueID(self): | |
| 83 """Return an ID which uniquely represents this object for this process. | |
| 84 | |
| 85 By default, this uses the 'id' builtin, but can be overridden to | |
| 86 indicate that two values are identity-equivalent (such as proxies | |
| 87 for the same object). | |
| 88 """ | |
| 89 | |
| 90 return id(self) | |
| 91 | |
| 92 class Referenceable(Serializable): | |
| 93 perspective = None | |
| 94 """I am an object sent remotely as a direct reference. | |
| 95 | |
| 96 When one of my subclasses is sent as an argument to or returned | |
| 97 from a remote method call, I will be serialized by default as a | |
| 98 direct reference. | |
| 99 | |
| 100 This means that the peer will be able to call methods on me; | |
| 101 a method call xxx() from my peer will be resolved to methods | |
| 102 of the name remote_xxx. | |
| 103 """ | |
| 104 | |
| 105 def remoteMessageReceived(self, broker, message, args, kw): | |
| 106 """A remote message has been received. Dispatch it appropriately. | |
| 107 | |
| 108 The default implementation is to dispatch to a method called | |
| 109 'remote_messagename' and call it with the same arguments. | |
| 110 """ | |
| 111 args = broker.unserialize(args) | |
| 112 kw = broker.unserialize(kw) | |
| 113 method = getattr(self, "remote_%s" % message, None) | |
| 114 if method is None: | |
| 115 raise NoSuchMethod("No such method: remote_%s" % (message,)) | |
| 116 try: | |
| 117 state = method(*args, **kw) | |
| 118 except TypeError: | |
| 119 log.msg("%s didn't accept %s and %s" % (method, args, kw)) | |
| 120 raise | |
| 121 return broker.serialize(state, self.perspective) | |
| 122 | |
| 123 def jellyFor(self, jellier): | |
| 124 """(internal) | |
| 125 | |
| 126 Return a tuple which will be used as the s-expression to | |
| 127 serialize this to a peer. | |
| 128 """ | |
| 129 | |
| 130 return "remote", jellier.invoker.registerReference(self) | |
| 131 | |
| 132 | |
| 133 class Root(Referenceable): | |
| 134 """I provide a root object to L{pb.Broker}s for a L{pb.BrokerFactory}. | |
| 135 | |
| 136 When a L{pb.BrokerFactory} produces a L{pb.Broker}, it supplies that | |
| 137 L{pb.Broker} with an object named \"root\". That object is obtained | |
| 138 by calling my rootObject method. | |
| 139 | |
| 140 See also: L{pb.getObjectAt} | |
| 141 """ | |
| 142 | |
| 143 implements(IPBRoot) | |
| 144 | |
| 145 def rootObject(self, broker): | |
| 146 """A L{pb.BrokerFactory} is requesting to publish me as a root object. | |
| 147 | |
| 148 When a L{pb.BrokerFactory} is sending me as the root object, this | |
| 149 method will be invoked to allow per-broker versions of an | |
| 150 object. By default I return myself. | |
| 151 """ | |
| 152 return self | |
| 153 | |
| 154 | |
| 155 class ViewPoint(Referenceable): | |
| 156 """ | |
| 157 I act as an indirect reference to an object accessed through a | |
| 158 L{pb.Perspective}. | |
| 159 | |
| 160 Simply put, I combine an object with a perspective so that when a | |
| 161 peer calls methods on the object I refer to, the method will be | |
| 162 invoked with that perspective as a first argument, so that it can | |
| 163 know who is calling it. | |
| 164 | |
| 165 While L{Viewable} objects will be converted to ViewPoints by default | |
| 166 when they are returned from or sent as arguments to a remote | |
| 167 method, any object may be manually proxied as well. (XXX: Now that | |
| 168 this class is no longer named C{Proxy}, this is the only occourance | |
| 169 of the term 'proxied' in this docstring, and may be unclear.) | |
| 170 | |
| 171 This can be useful when dealing with L{pb.Perspective}s, L{Copyable}s, | |
| 172 and L{Cacheable}s. It is legal to implement a method as such on | |
| 173 a perspective:: | |
| 174 | |
| 175 | def perspective_getViewPointForOther(self, name): | |
| 176 | defr = self.service.getPerspectiveRequest(name) | |
| 177 | defr.addCallbacks(lambda x, self=self: ViewPoint(self, x), log.msg) | |
| 178 | return defr | |
| 179 | |
| 180 This will allow you to have references to Perspective objects in two | |
| 181 different ways. One is through the initial 'attach' call -- each | |
| 182 peer will have a L{pb.RemoteReference} to their perspective directly. The | |
| 183 other is through this method; each peer can get a L{pb.RemoteReference} to | |
| 184 all other perspectives in the service; but that L{pb.RemoteReference} will | |
| 185 be to a L{ViewPoint}, not directly to the object. | |
| 186 | |
| 187 The practical offshoot of this is that you can implement 2 varieties | |
| 188 of remotely callable methods on this Perspective; view_xxx and | |
| 189 C{perspective_xxx}. C{view_xxx} methods will follow the rules for | |
| 190 ViewPoint methods (see ViewPoint.L{remoteMessageReceived}), and | |
| 191 C{perspective_xxx} methods will follow the rules for Perspective | |
| 192 methods. | |
| 193 """ | |
| 194 | |
| 195 def __init__(self, perspective, object): | |
| 196 """Initialize me with a Perspective and an Object. | |
| 197 """ | |
| 198 self.perspective = perspective | |
| 199 self.object = object | |
| 200 | |
| 201 def processUniqueID(self): | |
| 202 """Return an ID unique to a proxy for this perspective+object combinatio
n. | |
| 203 """ | |
| 204 return (id(self.perspective), id(self.object)) | |
| 205 | |
| 206 def remoteMessageReceived(self, broker, message, args, kw): | |
| 207 """A remote message has been received. Dispatch it appropriately. | |
| 208 | |
| 209 The default implementation is to dispatch to a method called | |
| 210 'C{view_messagename}' to my Object and call it on my object with | |
| 211 the same arguments, modified by inserting my Perspective as | |
| 212 the first argument. | |
| 213 """ | |
| 214 args = broker.unserialize(args, self.perspective) | |
| 215 kw = broker.unserialize(kw, self.perspective) | |
| 216 method = getattr(self.object, "view_%s" % message) | |
| 217 try: | |
| 218 state = apply(method, (self.perspective,)+args, kw) | |
| 219 except TypeError: | |
| 220 log.msg("%s didn't accept %s and %s" % (method, args, kw)) | |
| 221 raise | |
| 222 rv = broker.serialize(state, self.perspective, method, args, kw) | |
| 223 return rv | |
| 224 | |
| 225 | |
| 226 class Viewable(Serializable): | |
| 227 """I will be converted to a L{ViewPoint} when passed to or returned from a r
emote method. | |
| 228 | |
| 229 The beginning of a peer's interaction with a PB Service is always | |
| 230 through a perspective. However, if a C{perspective_xxx} method returns | |
| 231 a Viewable, it will be serialized to the peer as a response to that | |
| 232 method. | |
| 233 """ | |
| 234 | |
| 235 def jellyFor(self, jellier): | |
| 236 """Serialize a L{ViewPoint} for me and the perspective of the given brok
er. | |
| 237 """ | |
| 238 return ViewPoint(jellier.invoker.serializingPerspective, self).jellyFor(
jellier) | |
| 239 | |
| 240 | |
| 241 | |
| 242 class Copyable(Serializable): | |
| 243 """Subclass me to get copied each time you are returned from or passed to a
remote method. | |
| 244 | |
| 245 When I am returned from or passed to a remote method call, I will be | |
| 246 converted into data via a set of callbacks (see my methods for more | |
| 247 info). That data will then be serialized using Jelly, and sent to | |
| 248 the peer. | |
| 249 | |
| 250 The peer will then look up the type to represent this with; see | |
| 251 L{RemoteCopy} for details. | |
| 252 """ | |
| 253 | |
| 254 def getStateToCopy(self): | |
| 255 """Gather state to send when I am serialized for a peer. | |
| 256 | |
| 257 I will default to returning self.__dict__. Override this to | |
| 258 customize this behavior. | |
| 259 """ | |
| 260 | |
| 261 return self.__dict__ | |
| 262 | |
| 263 def getStateToCopyFor(self, perspective): | |
| 264 """ | |
| 265 Gather state to send when I am serialized for a particular | |
| 266 perspective. | |
| 267 | |
| 268 I will default to calling L{getStateToCopy}. Override this to | |
| 269 customize this behavior. | |
| 270 """ | |
| 271 | |
| 272 return self.getStateToCopy() | |
| 273 | |
| 274 def getTypeToCopy(self): | |
| 275 """Determine what type tag to send for me. | |
| 276 | |
| 277 By default, send the string representation of my class | |
| 278 (package.module.Class); normally this is adequate, but | |
| 279 you may override this to change it. | |
| 280 """ | |
| 281 | |
| 282 return reflect.qual(self.__class__) | |
| 283 | |
| 284 def getTypeToCopyFor(self, perspective): | |
| 285 """Determine what type tag to send for me. | |
| 286 | |
| 287 By default, defer to self.L{getTypeToCopy}() normally this is | |
| 288 adequate, but you may override this to change it. | |
| 289 """ | |
| 290 | |
| 291 return self.getTypeToCopy() | |
| 292 | |
| 293 def jellyFor(self, jellier): | |
| 294 """Assemble type tag and state to copy for this broker. | |
| 295 | |
| 296 This will call L{getTypeToCopyFor} and L{getStateToCopy}, and | |
| 297 return an appropriate s-expression to represent me. | |
| 298 """ | |
| 299 | |
| 300 if jellier.invoker is None: | |
| 301 return getInstanceState(self, jellier) | |
| 302 p = jellier.invoker.serializingPerspective | |
| 303 t = self.getTypeToCopyFor(p) | |
| 304 state = self.getStateToCopyFor(p) | |
| 305 sxp = jellier.prepare(self) | |
| 306 sxp.extend([t, jellier.jelly(state)]) | |
| 307 return jellier.preserve(self, sxp) | |
| 308 | |
| 309 | |
| 310 class Cacheable(Copyable): | |
| 311 """A cached instance. | |
| 312 | |
| 313 This means that it's copied; but there is some logic to make sure | |
| 314 that it's only copied once. Additionally, when state is retrieved, | |
| 315 it is passed a "proto-reference" to the state as it will exist on | |
| 316 the client. | |
| 317 | |
| 318 XXX: The documentation for this class needs work, but it's the most | |
| 319 complex part of PB and it is inherently difficult to explain. | |
| 320 """ | |
| 321 | |
| 322 def getStateToCacheAndObserveFor(self, perspective, observer): | |
| 323 """ | |
| 324 Get state to cache on the client and client-cache reference | |
| 325 to observe locally. | |
| 326 | |
| 327 This is similiar to getStateToCopyFor, but it additionally | |
| 328 passes in a reference to the client-side RemoteCache instance | |
| 329 that will be created when it is unserialized. This allows | |
| 330 Cacheable instances to keep their RemoteCaches up to date when | |
| 331 they change, such that no changes can occur between the point | |
| 332 at which the state is initially copied and the client receives | |
| 333 it that are not propogated. | |
| 334 """ | |
| 335 | |
| 336 return self.getStateToCopyFor(perspective) | |
| 337 | |
| 338 def jellyFor(self, jellier): | |
| 339 """Return an appropriate tuple to serialize me. | |
| 340 | |
| 341 Depending on whether this broker has cached me or not, this may | |
| 342 return either a full state or a reference to an existing cache. | |
| 343 """ | |
| 344 if jellier.invoker is None: | |
| 345 return getInstanceState(self, jellier) | |
| 346 luid = jellier.invoker.cachedRemotelyAs(self, 1) | |
| 347 if luid is None: | |
| 348 luid = jellier.invoker.cacheRemotely(self) | |
| 349 p = jellier.invoker.serializingPerspective | |
| 350 type_ = self.getTypeToCopyFor(p) | |
| 351 observer = RemoteCacheObserver(jellier.invoker, self, p) | |
| 352 state = self.getStateToCacheAndObserveFor(p, observer) | |
| 353 l = jellier.prepare(self) | |
| 354 jstate = jellier.jelly(state) | |
| 355 l.extend([type_, luid, jstate]) | |
| 356 return jellier.preserve(self, l) | |
| 357 else: | |
| 358 return cached_atom, luid | |
| 359 | |
| 360 def stoppedObserving(self, perspective, observer): | |
| 361 """This method is called when a client has stopped observing me. | |
| 362 | |
| 363 The 'observer' argument is the same as that passed in to | |
| 364 getStateToCacheAndObserveFor. | |
| 365 """ | |
| 366 | |
| 367 | |
| 368 | |
| 369 class RemoteCopy(Unjellyable): | |
| 370 """I am a remote copy of a Copyable object. | |
| 371 | |
| 372 When the state from a L{Copyable} object is received, an instance will | |
| 373 be created based on the copy tags table (see setUnjellyableForClass) and | |
| 374 sent the L{setCopyableState} message. I provide a reasonable default | |
| 375 implementation of that message; subclass me if you wish to serve as | |
| 376 a copier for remote data. | |
| 377 | |
| 378 NOTE: copiers are invoked with no arguments. Do not implement a | |
| 379 constructor which requires args in a subclass of L{RemoteCopy}! | |
| 380 """ | |
| 381 | |
| 382 def setCopyableState(self, state): | |
| 383 """I will be invoked with the state to copy locally. | |
| 384 | |
| 385 'state' is the data returned from the remote object's | |
| 386 'getStateToCopyFor' method, which will often be the remote | |
| 387 object's dictionary (or a filtered approximation of it depending | |
| 388 on my peer's perspective). | |
| 389 """ | |
| 390 | |
| 391 self.__dict__ = state | |
| 392 | |
| 393 def unjellyFor(self, unjellier, jellyList): | |
| 394 if unjellier.invoker is None: | |
| 395 return setInstanceState(self, unjellier, jellyList) | |
| 396 self.setCopyableState(unjellier.unjelly(jellyList[1])) | |
| 397 return self | |
| 398 | |
| 399 | |
| 400 | |
| 401 class RemoteCache(RemoteCopy, Serializable): | |
| 402 """A cache is a local representation of a remote L{Cacheable} object. | |
| 403 | |
| 404 This represents the last known state of this object. It may | |
| 405 also have methods invoked on it -- in order to update caches, | |
| 406 the cached class generates a L{pb.RemoteReference} to this object as | |
| 407 it is originally sent. | |
| 408 | |
| 409 Much like copy, I will be invoked with no arguments. Do not | |
| 410 implement a constructor that requires arguments in one of my | |
| 411 subclasses. | |
| 412 """ | |
| 413 | |
| 414 def remoteMessageReceived(self, broker, message, args, kw): | |
| 415 """A remote message has been received. Dispatch it appropriately. | |
| 416 | |
| 417 The default implementation is to dispatch to a method called | |
| 418 'C{observe_messagename}' and call it on my with the same arguments. | |
| 419 """ | |
| 420 | |
| 421 args = broker.unserialize(args) | |
| 422 kw = broker.unserialize(kw) | |
| 423 method = getattr(self, "observe_%s" % message) | |
| 424 try: | |
| 425 state = apply(method, args, kw) | |
| 426 except TypeError: | |
| 427 log.msg("%s didn't accept %s and %s" % (method, args, kw)) | |
| 428 raise | |
| 429 return broker.serialize(state, None, method, args, kw) | |
| 430 | |
| 431 def jellyFor(self, jellier): | |
| 432 """serialize me (only for the broker I'm for) as the original cached ref
erence | |
| 433 """ | |
| 434 if jellier.invoker is None: | |
| 435 return getInstanceState(self, jellier) | |
| 436 assert jellier.invoker is self.broker, "You cannot exchange cached proxi
es between brokers." | |
| 437 return 'lcache', self.luid | |
| 438 | |
| 439 | |
| 440 def unjellyFor(self, unjellier, jellyList): | |
| 441 if unjellier.invoker is None: | |
| 442 return setInstanceState(self, unjellier, jellyList) | |
| 443 self.broker = unjellier.invoker | |
| 444 self.luid = jellyList[1] | |
| 445 if isinstance(self.__class__, type): #new-style class | |
| 446 cProxy = _DummyNewStyle() | |
| 447 else: | |
| 448 cProxy = _Dummy() | |
| 449 cProxy.__class__ = self.__class__ | |
| 450 cProxy.__dict__ = self.__dict__ | |
| 451 # XXX questionable whether this was a good design idea... | |
| 452 init = getattr(cProxy, "__init__", None) | |
| 453 if init: | |
| 454 init() | |
| 455 unjellier.invoker.cacheLocally(jellyList[1], self) | |
| 456 cProxy.setCopyableState(unjellier.unjelly(jellyList[2])) | |
| 457 # Might have changed due to setCopyableState method; we'll assume that | |
| 458 # it's bad form to do so afterwards. | |
| 459 self.__dict__ = cProxy.__dict__ | |
| 460 # chomp, chomp -- some existing code uses "self.__dict__ =", some uses | |
| 461 # "__dict__.update". This is here in order to handle both cases. | |
| 462 self.broker = unjellier.invoker | |
| 463 self.luid = jellyList[1] | |
| 464 return cProxy | |
| 465 | |
| 466 ## def __really_del__(self): | |
| 467 ## """Final finalization call, made after all remote references have bee
n lost. | |
| 468 ## """ | |
| 469 | |
| 470 def __cmp__(self, other): | |
| 471 """Compare me [to another RemoteCache. | |
| 472 """ | |
| 473 if isinstance(other, self.__class__): | |
| 474 return cmp(id(self.__dict__), id(other.__dict__)) | |
| 475 else: | |
| 476 return cmp(id(self.__dict__), other) | |
| 477 | |
| 478 def __hash__(self): | |
| 479 """Hash me. | |
| 480 """ | |
| 481 return int(id(self.__dict__) % sys.maxint) | |
| 482 | |
| 483 broker = None | |
| 484 luid = None | |
| 485 | |
| 486 def __del__(self): | |
| 487 """Do distributed reference counting on finalize. | |
| 488 """ | |
| 489 try: | |
| 490 # log.msg( ' --- decache: %s %s' % (self, self.luid) ) | |
| 491 if self.broker: | |
| 492 self.broker.decCacheRef(self.luid) | |
| 493 except: | |
| 494 log.deferr() | |
| 495 | |
| 496 def unjellyCached(unjellier, unjellyList): | |
| 497 luid = unjellyList[1] | |
| 498 cNotProxy = unjellier.invoker.cachedLocallyAs(luid) | |
| 499 | |
| 500 cProxy = _Dummy() | |
| 501 cProxy.__class__ = cNotProxy.__class__ | |
| 502 cProxy.__dict__ = cNotProxy.__dict__ | |
| 503 return cProxy | |
| 504 | |
| 505 setUnjellyableForClass("cached", unjellyCached) | |
| 506 | |
| 507 def unjellyLCache(unjellier, unjellyList): | |
| 508 luid = unjellyList[1] | |
| 509 obj = unjellier.invoker.remotelyCachedForLUID(luid) | |
| 510 return obj | |
| 511 | |
| 512 setUnjellyableForClass("lcache", unjellyLCache) | |
| 513 | |
| 514 def unjellyLocal(unjellier, unjellyList): | |
| 515 obj = unjellier.invoker.localObjectForID(unjellyList[1]) | |
| 516 return obj | |
| 517 | |
| 518 setUnjellyableForClass("local", unjellyLocal) | |
| 519 | |
| 520 class RemoteCacheMethod: | |
| 521 """A method on a reference to a L{RemoteCache}. | |
| 522 """ | |
| 523 | |
| 524 def __init__(self, name, broker, cached, perspective): | |
| 525 """(internal) initialize. | |
| 526 """ | |
| 527 self.name = name | |
| 528 self.broker = broker | |
| 529 self.perspective = perspective | |
| 530 self.cached = cached | |
| 531 | |
| 532 def __cmp__(self, other): | |
| 533 return cmp((self.name, self.broker, self.perspective, self.cached), othe
r) | |
| 534 | |
| 535 def __hash__(self): | |
| 536 return hash((self.name, self.broker, self.perspective, self.cached)) | |
| 537 | |
| 538 def __call__(self, *args, **kw): | |
| 539 """(internal) action method. | |
| 540 """ | |
| 541 cacheID = self.broker.cachedRemotelyAs(self.cached) | |
| 542 if cacheID is None: | |
| 543 from pb import ProtocolError | |
| 544 raise ProtocolError("You can't call a cached method when the object
hasn't been given to the peer yet.") | |
| 545 return self.broker._sendMessage('cache', self.perspective, cacheID, self
.name, args, kw) | |
| 546 | |
| 547 class RemoteCacheObserver: | |
| 548 """I am a reverse-reference to the peer's L{RemoteCache}. | |
| 549 | |
| 550 I am generated automatically when a cache is serialized. I | |
| 551 represent a reference to the client's L{RemoteCache} object that | |
| 552 will represent a particular L{Cacheable}; I am the additional | |
| 553 object passed to getStateToCacheAndObserveFor. | |
| 554 """ | |
| 555 | |
| 556 def __init__(self, broker, cached, perspective): | |
| 557 """(internal) Initialize me. | |
| 558 | |
| 559 @param broker: a L{pb.Broker} instance. | |
| 560 | |
| 561 @param cached: a L{Cacheable} instance that this L{RemoteCacheObserver} | |
| 562 corresponds to. | |
| 563 | |
| 564 @param perspective: a reference to the perspective who is observing this
. | |
| 565 """ | |
| 566 | |
| 567 self.broker = broker | |
| 568 self.cached = cached | |
| 569 self.perspective = perspective | |
| 570 | |
| 571 def __repr__(self): | |
| 572 return "<RemoteCacheObserver(%s, %s, %s) at %s>" % ( | |
| 573 self.broker, self.cached, self.perspective, id(self)) | |
| 574 | |
| 575 def __hash__(self): | |
| 576 """Generate a hash unique to all L{RemoteCacheObserver}s for this broker
/perspective/cached triplet | |
| 577 """ | |
| 578 | |
| 579 return ( (hash(self.broker) % 2**10) | |
| 580 + (hash(self.perspective) % 2**10) | |
| 581 + (hash(self.cached) % 2**10)) | |
| 582 | |
| 583 def __cmp__(self, other): | |
| 584 """Compare me to another L{RemoteCacheObserver}. | |
| 585 """ | |
| 586 | |
| 587 return cmp((self.broker, self.perspective, self.cached), other) | |
| 588 | |
| 589 def callRemote(self, _name, *args, **kw): | |
| 590 """(internal) action method. | |
| 591 """ | |
| 592 cacheID = self.broker.cachedRemotelyAs(self.cached) | |
| 593 if cacheID is None: | |
| 594 from pb import ProtocolError | |
| 595 raise ProtocolError("You can't call a cached method when the " | |
| 596 "object hasn't been given to the peer yet.") | |
| 597 return self.broker._sendMessage('cache', self.perspective, cacheID, | |
| 598 _name, args, kw) | |
| 599 | |
| 600 def remoteMethod(self, key): | |
| 601 """Get a L{pb.RemoteMethod} for this key. | |
| 602 """ | |
| 603 return RemoteCacheMethod(key, self.broker, self.cached, self.perspective
) | |
| OLD | NEW |