~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

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