1 <?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
3 <title>Twisted Documentation: PB Copyable: Passing Complex Types</title>
4 <link href="stylesheet.css" rel="stylesheet" type="text/css"/>
8 <h1 class="title">PB Copyable: Passing Complex Types</h1>
9 <div class="toc"><ol><li><a href="#auto0">Overview</a></li><li><a href="#auto1">Motivation</a></li><li><a href="#auto2">Passing Objects</a></li><ul><li><a href="#auto3">Security Options</a></li><li><a href="#auto4">What class to use?</a></li></ul><li><a href="#auto5">pb.Copyable</a></li><ul><li><a href="#auto6">Controlling the Copied State</a></li><li><a href="#auto7">Things To Watch Out For</a></li><li><a href="#auto8">More Information</a></li></ul><li><a href="#auto9">pb.Cacheable</a></li><ul><li><a href="#auto10">Example</a></li><li><a href="#auto11">More Information</a></li></ul></ol></div>
13 <h2>Overview<a name="auto0"/></h2>
15 <p>This chapter focuses on how to use PB to pass complex types (specifically
16 class instances) to and from a remote process. The first section is on
17 simply copying the contents of an object to a remote process (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>). The second covers how
18 to copy those contents once, then update them later when they change (<code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">Cacheable</a></code>).</p>
20 <h2>Motivation<a name="auto1"/></h2>
22 <p>From the <a href="pb-usage.html" shape="rect">previous chapter</a>, you've seen how to
23 pass basic types to a remote process, by using them in the arguments or
24 return values of a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteReference.callRemote.html" title="twisted.spread.pb.RemoteReference.callRemote">callRemote</a></code> function. However,
25 if you've experimented with it, you may have discovered problems when trying
26 to pass anything more complicated than a primitive int/list/dict/string
27 type, or another <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Referenceable.html" title="twisted.spread.pb.Referenceable">pb.Referenceable</a></code> object. At some point you want
28 to pass entire objects between processes, instead of having to reduce them
29 down to dictionaries on one end and then re-instantiating them on the
32 <h2>Passing Objects<a name="auto2"/></h2>
34 <p>The most obvious and straightforward way to send an object to a remote
35 process is with something like the following code. It also happens that this
36 code doesn't work, as will be explained below.</p>
38 <pre class="python"><p class="py-linenumber">1
44 </p><span class="py-src-keyword">class</span> <span class="py-src-identifier">LilyPond</span>:
45 <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">frogs</span>):
46 <span class="py-src-variable">self</span>.<span class="py-src-variable">frogs</span> = <span class="py-src-variable">frogs</span>
48 <span class="py-src-variable">pond</span> = <span class="py-src-variable">LilyPond</span>(<span class="py-src-number">12</span>)
49 <span class="py-src-variable">ref</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"sendPond"</span>, <span class="py-src-variable">pond</span>)
52 <p>If you try to run this, you might hope that a suitable remote end which
53 implements the <code>remote_sendPond</code> method would see that method get
54 invoked with an instance from the <code>LilyPond</code> class. But instead,
55 you'll encounter the dreaded <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code> exception. This is
56 Twisted's way of telling you that you've violated a security restriction,
57 and that the receiving end refuses to accept your object.</p>
59 <h3>Security Options<a name="auto3"/></h3>
61 <p>What's the big deal? What's wrong with just copying a class into another
62 process' namespace?</p>
64 <p>Reversing the question might make it easier to see the issue: what is the
65 problem with accepting a stranger's request to create an arbitrary object in
66 your local namespace? The real question is how much power you are granting
67 them: what actions can they convince you to take on the basis of the bytes
68 they are sending you over that remote connection.</p>
70 <p>Objects generally represent more power than basic types like strings and
71 dictionaries because they also contain (or reference) code, which can modify
72 other data structures when executed. Once previously-trusted data is
73 subverted, the rest of the program is compromised.</p>
75 <p>The built-in Python <q>batteries included</q> classes are relatively
76 tame, but you still wouldn't want to let a foreign program use them to
77 create arbitrary objects in your namespace or on your computer. Imagine a
78 protocol that involved sending a file-like object with a <code>read()</code>
79 method that was supposed to used later to retrieve a document. Then imagine
80 what if that object were created with
81 <code>os.fdopen("~/.gnupg/secring.gpg")</code>. Or an instance of
82 <code>telnetlib.Telnet("localhost", "chargen")</code>. </p>
84 <p>Classes you've written for your own program are likely to have far more
85 power. They may run code during <code>__init__</code>, or even have special
86 meaning simply because of their existence. A program might have
87 <code>User</code> objects to represent user accounts, and have a rule that
88 says all <code>User</code> objects in the system are referenced when
89 authorizing a login session. (In this system, <code>User.__init__</code>
90 would probably add the object to a global list of known users). The simple
91 act of creating an object would give access to somebody. If you could be
92 tricked into creating a bad object, an unauthorized user would get
95 <p>So object creation needs to be part of a system's security design. The
96 dotted line between <q>trusted inside</q> and <q>untrusted outside</q> needs
97 to describe what may be done in response to outside events. One of those
98 events is the receipt of an object through a PB remote procedure call, which
99 is a request to create an object in your <q>inside</q> namespace. The
100 question is what to do in response to it. For this reason, you must
101 explicitly specify what remote classes will be accepted, and how their
102 local representatives are to be created.</p>
104 <h3>What class to use?<a name="auto4"/></h3>
106 <p>Another basic question to answer before we can do anything useful with an
107 incoming serialized object is: what class should we create? The simplistic
108 answer is to create the <q>same kind</q> that was serialized on the sender's
109 end of the wire, but this is not as easy or as straightforward as you might
110 think. Remember that the request is coming from a different program, using a
111 potentially different set of class libraries. In fact, since PB has also
112 been implemented in Java, Emacs-Lisp, and other languages, there's no
113 guarantee that the sender is even running Python! All we know on the
114 receiving end is a list of two things which describe the instance they are
115 trying to send us: the name of the class, and a representation of the
116 contents of the object.</p>
119 <p>PB lets you specify the mapping from remote class names to local classes
120 with the <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.setUnjellyableForClass.html" title="twisted.spread.jelly.setUnjellyableForClass">setUnjellyableForClass</a></code> function
121 <a href="#footnote-1" title="Note that, in this context, unjelly is a verb with the opposite meaning of jelly. The verb to jelly means to serialize an object or data structure into a sequence of bytes (or other primitive transmittable/storable representation), while to unjelly means to unserialize the bytestream into a live object in the receiver's memory space. Unjellyable is a noun, (not an adjective), referring to the the class that serves as a destination or recipient of the unjellying process. A is unjellyable into B means that a serialized representation A (of some remote object) can be unserialized into a local object of type B. It is these objects B that are the Unjellyable second argument of the setUnjellyableForClass function. In particular, unjellyable does not mean cannot be jellied. Unpersistable means not persistable, but unjelly, unserialize, and unpickle mean to reverse the operations of jellying, serializing, and pickling."><super>1</super></a>.
124 This function takes a remote/sender class reference (either the
125 fully-qualified name as used by the sending end, or a class object from
126 which the name can be extracted), and a local/recipient class (used to
127 create the local representation for incoming serialized objects). Whenever
128 the remote end sends an object, the class name that they transmit is looked
129 up in the table controlled by this function. If a matching class is found,
130 it is used to create the local object. If not, you get the
131 <code>InsecureJelly</code> exception.</p>
133 <p>In general you expect both ends to share the same codebase: either you
134 control the program that is running on both ends of the wire, or both
135 programs share some kind of common language that is implemented in code
136 which exists on both ends. You wouldn't expect them to send you an object of
137 the MyFooziWhatZit class unless you also had a definition for that class. So
138 it is reasonable for the Jelly layer to reject all incoming classes except
139 the ones that you have explicitly marked with
140 <code>setUnjellyableForClass</code>. But keep in mind that the sender's idea
141 of a <code>User</code> object might differ from the recipient's, either
142 through namespace collisions between unrelated packages, version skew
143 between nodes that haven't been updated at the same rate, or a malicious
144 intruder trying to cause your code to fail in some interesting or
145 potentially vulnerable way.</p>
148 <h2>pb.Copyable<a name="auto5"/></h2>
150 <p>Ok, enough of this theory. How do you send a fully-fledged object from
151 one side to the other?</p>
153 <div class="py-listing"><pre><p class="py-linenumber"> 1
210 </p><span class="py-src-comment">#!/usr/bin/env python</span>
212 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
213 <span class="py-src-comment"># See LICENSE for details.</span>
215 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
216 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
217 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
219 <span class="py-src-keyword">class</span> <span class="py-src-identifier">LilyPond</span>:
220 <span class="py-src-keyword">def</span> <span class="py-src-identifier">setStuff</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">color</span>, <span class="py-src-parameter">numFrogs</span>):
221 <span class="py-src-variable">self</span>.<span class="py-src-variable">color</span> = <span class="py-src-variable">color</span>
222 <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> = <span class="py-src-variable">numFrogs</span>
223 <span class="py-src-keyword">def</span> <span class="py-src-identifier">countFrogs</span>(<span class="py-src-parameter">self</span>):
224 <span class="py-src-keyword">print</span> <span class="py-src-string">"%d frogs"</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span>
226 <span class="py-src-keyword">class</span> <span class="py-src-identifier">CopyPond</span>(<span class="py-src-parameter">LilyPond</span>, <span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Copyable</span>):
227 <span class="py-src-keyword">pass</span>
229 <span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
230 <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
231 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
233 <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">remote</span>):
234 <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">remote</span>
235 <span class="py-src-variable">d</span> = <span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"takePond"</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
236 <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ok</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">notOk</span>)
238 <span class="py-src-keyword">def</span> <span class="py-src-identifier">ok</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
239 <span class="py-src-keyword">print</span> <span class="py-src-string">"pond arrived"</span>, <span class="py-src-variable">response</span>
240 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
241 <span class="py-src-keyword">def</span> <span class="py-src-identifier">notOk</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
242 <span class="py-src-keyword">print</span> <span class="py-src-string">"error during takePond:"</span>
243 <span class="py-src-keyword">if</span> <span class="py-src-variable">failure</span>.<span class="py-src-variable">type</span> == <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
244 <span class="py-src-keyword">print</span> <span class="py-src-string">" InsecureJelly"</span>
245 <span class="py-src-keyword">else</span>:
246 <span class="py-src-keyword">print</span> <span class="py-src-variable">failure</span>
247 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
248 <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
250 <span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
251 <span class="py-src-keyword">from</span> <span class="py-src-variable">copy_sender</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">CopyPond</span> <span class="py-src-comment"># so it's not __main__.CopyPond</span>
252 <span class="py-src-variable">pond</span> = <span class="py-src-variable">CopyPond</span>()
253 <span class="py-src-variable">pond</span>.<span class="py-src-variable">setStuff</span>(<span class="py-src-string">"green"</span>, <span class="py-src-number">7</span>)
254 <span class="py-src-variable">pond</span>.<span class="py-src-variable">countFrogs</span>()
255 <span class="py-src-comment"># class name:</span>
256 <span class="py-src-keyword">print</span> <span class="py-src-string">"."</span>.<span class="py-src-variable">join</span>([<span class="py-src-variable">pond</span>.<span class="py-src-variable">__class__</span>.<span class="py-src-variable">__module__</span>, <span class="py-src-variable">pond</span>.<span class="py-src-variable">__class__</span>.<span class="py-src-variable">__name__</span>])
258 <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">pond</span>)
259 <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
260 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">"localhost"</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
261 <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
262 <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">got_obj</span>)
263 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
265 <span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
266 <span class="py-src-variable">main</span>()
267 </pre><div class="caption">Source listing - <a href="listings/pb/copy_sender.py"><span class="filename">listings/pb/copy_sender.py</span></a></div></div>
268 <div class="py-listing"><pre><p class="py-linenumber"> 1
309 </p><span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
310 <span class="py-src-comment"># See LICENSE for details.</span>
312 <span class="py-src-string">"""
313 PB copy receiver example.
315 This is a Twisted Application Configuration (tac) file. Run with e.g.
316 twistd -ny copy_receiver.tac
318 See the twistd(1) man page or
319 http://twistedmatrix.com/documents/current/howto/application for details.
320 """</span>
322 <span class="py-src-keyword">import</span> <span class="py-src-variable">sys</span>
323 <span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
324 <span class="py-src-keyword">print</span> <span class="py-src-variable">__doc__</span>
325 <span class="py-src-variable">sys</span>.<span class="py-src-variable">exit</span>(<span class="py-src-number">1</span>)
327 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
328 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
329 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
330 <span class="py-src-keyword">from</span> <span class="py-src-variable">copy_sender</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">LilyPond</span>, <span class="py-src-variable">CopyPond</span>
332 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
333 <span class="py-src-comment">#log.startLogging(sys.stdout)</span>
335 <span class="py-src-keyword">class</span> <span class="py-src-identifier">ReceiverPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCopy</span>, <span class="py-src-parameter">LilyPond</span>):
336 <span class="py-src-keyword">pass</span>
337 <span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">CopyPond</span>, <span class="py-src-variable">ReceiverPond</span>)
339 <span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
340 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
341 <span class="py-src-keyword">print</span> <span class="py-src-string">" got pond:"</span>, <span class="py-src-variable">pond</span>
342 <span class="py-src-variable">pond</span>.<span class="py-src-variable">countFrogs</span>()
343 <span class="py-src-keyword">return</span> <span class="py-src-string">"safe and sound"</span> <span class="py-src-comment"># positive acknowledgement</span>
344 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
345 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
347 <span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">"copy_receiver"</span>)
348 <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
349 <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
350 </pre><div class="caption">Source listing - <a href="listings/pb/copy_receiver.tac"><span class="filename">listings/pb/copy_receiver.tac</span></a></div></div>
352 <p>The sending side has a class called <code>LilyPond</code>. To make this
353 eligble for transport through <code>callRemote</code> (either as an
354 argument, a return value, or something referenced by either of those [like a
355 dictionary value]), it must inherit from one of the four <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Serializable.html" title="twisted.spread.pb.Serializable">Serializable</a></code> classes. In this section,
356 we focus on <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">Copyable</a></code>.
357 The copyable subclass of <code>LilyPond</code> is called
358 <code>CopyPond</code>. We create an instance of it and send it through
359 <code>callRemote</code> as an argument to the receiver's
360 <code>remote_takePond</code> method. The Jelly layer will serialize
361 (<q>jelly</q>) that object as an instance with a class name of
362 <q>copy_sender.CopyPond</q> and some chunk of data that represents the
363 object's state. <code>pond.__class__.__module__</code> and
364 <code>pond.__class__.__name__</code> are used to derive the class name
365 string. The object's <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Copyable.getStateToCopy.html" title="twisted.spread.flavors.Copyable.getStateToCopy">getStateToCopy</a></code> method is
366 used to get the state: this is provided by <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, and the default just retrieves
367 <code>self.__dict__</code>. This works just like the optional
368 <code>__getstate__</code> method used by <code>pickle</code>. The pair of
369 name and state are sent over the wire to the receiver.</p>
371 <p>The receiving end defines a local class named <code>ReceiverPond</code>
372 to represent incoming <code>LilyPond</code> instances. This class derives
373 from the sender's <code>LilyPond</code> class (with a fully-qualified name
374 of <code>copy_sender.LilyPond</code>), which specifies how we expect it to
375 behave. We trust that this is the same <code>LilyPond</code> class as the
376 sender used. (At the very least, we hope ours will be able to accept a state
377 created by theirs). It also inherits from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code>, which is a requirement for all
378 classes that act in this local-representative role (those which are given to
379 the second argument of <code>setUnjellyableForClass</code>).
380 <code>RemoteCopy</code> provides the methods that tell the Jelly layer how
381 to create the local object from the incoming serialized state.</p>
383 <p>Then <code>setUnjellyableForClass</code> is used to register the two
384 classes. This has two effects: instances of the remote class (the first
385 argument) will be allowed in through the security layer, and instances of
386 the local class (the second argument) will be used to contain the state that
387 is transmitted when the sender serializes the remote object.</p>
389 <p>When the receiver unserializes (<q>unjellies</q>) the object, it will
390 create an instance of the local <code>ReceiverPond</code> class, and hand
391 the transmitted state (usually in the form of a dictionary) to that object's
392 <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.RemoteCopy.setCopyableState.html" title="twisted.spread.flavors.RemoteCopy.setCopyableState">setCopyableState</a></code> method.
393 This acts just like the <code>__setstate__</code> method that
394 <code>pickle</code> uses when unserializing an object.
395 <code>getStateToCopy</code>/<code>setCopyableState</code> are distinct from
396 <code>__getstate__</code>/<code>__setstate__</code> to allow objects to be
397 persisted (across time) differently than they are transmitted (across
400 <p>When this is run, it produces the following output:</p>
402 <pre class="shell" xml:space="preserve">
403 [-] twisted.spread.pb.PBServerFactory starting on 8800
404 [-] Starting factory <twisted.spread.pb.PBServerFactory instance at
406 [Broker,0,127.0.0.1] got pond: <__builtin__.ReceiverPond instance at
408 [Broker,0,127.0.0.1] 7 frogs
411 <pre class="shell" xml:space="preserve">
415 pond arrived safe and sound
416 Main loop terminated.
422 <h3>Controlling the Copied State<a name="auto6"/></h3>
424 <p>By overriding <code>getStateToCopy</code> and
425 <code>setCopyableState</code>, you can control how the object is transmitted
426 over the wire. For example, you might want perform some data-reduction:
427 pre-compute some results instead of sending all the raw data over the wire.
428 Or you could replace references to a local object on the sender's side with
429 markers before sending, then upon receipt replace those markers with
430 references to a receiver-side proxy that could perform the same operations
431 against a local cache of data.</p>
433 <p>Another good use for <code>getStateToCopy</code> is to implement
434 <q>local-only</q> attributes: data that is only accessible by the local
435 process, not to any remote users. For example, a <code>.password</code>
436 attribute could be removed from the object state before sending to a remote
437 system. Combined with the fact that <code>Copyable</code> objects return
438 unchanged from a round trip, this could be used to build a
439 challenge-response system (in fact PB does this with
440 <code>pb.Referenceable</code> objects to implement authorization as
441 described <a href="pb-cred.html" shape="rect">here</a>).</p>
443 <p>Whatever <code>getStateToCopy</code> returns from the sending object will
444 be serialized and sent over the wire; <code>setCopyableState</code> gets
445 whatever comes over the wire and is responsible for setting up the state of
446 the object it lives in.</p>
449 <div class="py-listing"><pre><p class="py-linenumber"> 1
478 </p><span class="py-src-comment">#!/usr/bin/env python</span>
480 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
481 <span class="py-src-comment"># See LICENSE for details.</span>
483 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
485 <span class="py-src-keyword">class</span> <span class="py-src-identifier">FrogPond</span>:
486 <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">numFrogs</span>, <span class="py-src-parameter">numToads</span>):
487 <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> = <span class="py-src-variable">numFrogs</span>
488 <span class="py-src-variable">self</span>.<span class="py-src-variable">numToads</span> = <span class="py-src-variable">numToads</span>
489 <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
490 <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">numFrogs</span> + <span class="py-src-variable">self</span>.<span class="py-src-variable">numToads</span>
492 <span class="py-src-keyword">class</span> <span class="py-src-identifier">SenderPond</span>(<span class="py-src-parameter">FrogPond</span>, <span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Copyable</span>):
493 <span class="py-src-keyword">def</span> <span class="py-src-identifier">getStateToCopy</span>(<span class="py-src-parameter">self</span>):
494 <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">__dict__</span>.<span class="py-src-variable">copy</span>()
495 <span class="py-src-variable">d</span>[<span class="py-src-string">'frogsAndToads'</span>] = <span class="py-src-variable">d</span>[<span class="py-src-string">'numFrogs'</span>] + <span class="py-src-variable">d</span>[<span class="py-src-string">'numToads'</span>]
496 <span class="py-src-keyword">del</span> <span class="py-src-variable">d</span>[<span class="py-src-string">'numFrogs'</span>]
497 <span class="py-src-keyword">del</span> <span class="py-src-variable">d</span>[<span class="py-src-string">'numToads'</span>]
498 <span class="py-src-keyword">return</span> <span class="py-src-variable">d</span>
500 <span class="py-src-keyword">class</span> <span class="py-src-identifier">ReceiverPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCopy</span>):
501 <span class="py-src-keyword">def</span> <span class="py-src-identifier">setCopyableState</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">state</span>):
502 <span class="py-src-variable">self</span>.<span class="py-src-variable">__dict__</span> = <span class="py-src-variable">state</span>
503 <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
504 <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">frogsAndToads</span>
506 <span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">SenderPond</span>, <span class="py-src-variable">ReceiverPond</span>)
507 </pre><div class="caption">Source listing - <a href="listings/pb/copy2_classes.py"><span class="filename">listings/pb/copy2_classes.py</span></a></div></div>
508 <div class="py-listing"><pre><p class="py-linenumber"> 1
552 </p><span class="py-src-comment">#!/usr/bin/env python</span>
554 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
555 <span class="py-src-comment"># See LICENSE for details.</span>
557 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
558 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
559 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
560 <span class="py-src-keyword">from</span> <span class="py-src-variable">copy2_classes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">SenderPond</span>
562 <span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
563 <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
564 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
566 <span class="py-src-keyword">def</span> <span class="py-src-identifier">got_obj</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">obj</span>):
567 <span class="py-src-variable">d</span> = <span class="py-src-variable">obj</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"takePond"</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
568 <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ok</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">notOk</span>)
570 <span class="py-src-keyword">def</span> <span class="py-src-identifier">ok</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
571 <span class="py-src-keyword">print</span> <span class="py-src-string">"pond arrived"</span>, <span class="py-src-variable">response</span>
572 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
573 <span class="py-src-keyword">def</span> <span class="py-src-identifier">notOk</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">failure</span>):
574 <span class="py-src-keyword">print</span> <span class="py-src-string">"error during takePond:"</span>
575 <span class="py-src-keyword">if</span> <span class="py-src-variable">failure</span>.<span class="py-src-variable">type</span> == <span class="py-src-variable">jelly</span>.<span class="py-src-variable">InsecureJelly</span>:
576 <span class="py-src-keyword">print</span> <span class="py-src-string">" InsecureJelly"</span>
577 <span class="py-src-keyword">else</span>:
578 <span class="py-src-keyword">print</span> <span class="py-src-variable">failure</span>
579 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
580 <span class="py-src-keyword">return</span> <span class="py-src-variable">None</span>
582 <span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
583 <span class="py-src-variable">pond</span> = <span class="py-src-variable">SenderPond</span>(<span class="py-src-number">3</span>, <span class="py-src-number">4</span>)
584 <span class="py-src-keyword">print</span> <span class="py-src-string">"count %d"</span> % <span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
586 <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">pond</span>)
587 <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
588 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">"localhost"</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
589 <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
590 <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">got_obj</span>)
591 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
593 <span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
594 <span class="py-src-variable">main</span>()
595 </pre><div class="caption">Source listing - <a href="listings/pb/copy2_sender.py"><span class="filename">listings/pb/copy2_sender.py</span></a></div></div>
596 <div class="py-listing"><pre><p class="py-linenumber"> 1
617 </p><span class="py-src-comment">#!/usr/bin/env python</span>
619 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
620 <span class="py-src-comment"># See LICENSE for details.</span>
622 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
623 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
624 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
625 <span class="py-src-keyword">import</span> <span class="py-src-variable">copy2_classes</span> <span class="py-src-comment"># needed to get ReceiverPond registered with Jelly</span>
627 <span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
628 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
629 <span class="py-src-keyword">print</span> <span class="py-src-string">" got pond:"</span>, <span class="py-src-variable">pond</span>
630 <span class="py-src-keyword">print</span> <span class="py-src-string">" count %d"</span> % <span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
631 <span class="py-src-keyword">return</span> <span class="py-src-string">"safe and sound"</span> <span class="py-src-comment"># positive acknowledgement</span>
632 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
633 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
635 <span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">"copy_receiver"</span>)
636 <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
637 <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
638 </pre><div class="caption">Source listing - <a href="listings/pb/copy2_receiver.py"><span class="filename">listings/pb/copy2_receiver.py</span></a></div></div>
640 <p>In this example, the classes are defined in a separate source file, which
641 also sets up the binding between them. The <code>SenderPond</code> and
642 <code>ReceiverPond</code> are unrelated save for this binding: they happen
643 to implement the same methods, but use different internal instance variables
644 to accomplish them.</p>
646 <p>The recipient of the object doesn't even have to import the class
647 definition into their namespace. It is sufficient that they import the class
648 definition (and thus execute the <code>setUnjellyableForClass</code>
649 statement). The Jelly layer remembers the class definition until a matching
650 object is received. The sender of the object needs the definition, of
651 course, to create the object in the first place.</p>
653 <p>When run, the <code>copy2</code> example emits the following:</p>
655 <pre class="shell" xml:space="preserve">
656 $ twistd -n -y copy2_receiver.py
657 [-] twisted.spread.pb.PBServerFactory starting on 8800
658 [-] Starting factory <twisted.spread.pb.PBServerFactory instance at
660 [Broker,0,127.0.0.1] got pond: <copy2_classes.ReceiverPond instance at
662 [Broker,0,127.0.0.1] count 7
665 <pre class="shell" xml:space="preserve">
668 pond arrived safe and sound
669 Main loop terminated.
674 <h3>Things To Watch Out For<a name="auto7"/></h3>
678 <li>The first argument to <code>setUnjellyableForClass</code> must refer
679 to the class <em>as known by the sender</em>. The sender has no way of
680 knowing about how your local <code>import</code> statements are set up,
681 and Python's flexible namespace semantics allow you to access the same
682 class through a variety of different names. You must match whatever the
683 sender does. Having both ends import the class from a separate file, using
684 a canonical module name (no <q>sibiling imports</q>), is a good way to get
685 this right, especially when both the sending and the receiving classes are
686 defined together, with the <code>setUnjellyableForClass</code> immediately
689 <li>The class that is sent must inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>. The class that is registered to
690 receive it must inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code><a href="#footnote-2" title="pb.RemoteCopy is actually defined in twisted.spread.flavors, but pb.RemoteCopy is the preferred way to access it"><super>2</super></a>. </li>
692 <li>The same class can be used to send and receive. Just have it inherit
693 from both <code>pb.Copyable</code> and <code>pb.RemoteCopy</code>. This
694 will also make it possible to send the same class symmetrically back and
695 forth over the wire. But don't get confused about when it is coming (and
696 using <code>setCopyableState</code>) versus when it is going (using
697 <code>getStateToCopy</code>).</li>
699 <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.InsecureJelly.html" title="twisted.spread.jelly.InsecureJelly">InsecureJelly</a></code>
700 exceptions are raised by the receiving end. They will be delivered
701 asynchronously to an <code>errback</code> handler. If you do not add one
702 to the <code>Deferred</code> returned by <code>callRemote</code>, then you
703 will never receive notification of the problem. </li>
705 <li>The class that is derived from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code> will be created using a
706 constructor <code>__init__</code> method that takes no arguments. All
707 setup must be performed in the <code>setCopyableState</code> method. As
708 the docstring on <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">RemoteCopy</a></code> says, don't implement a
709 constructor that requires arguments in a subclass of
710 <code>RemoteCopy</code>.</li>
718 <h3>More Information<a name="auto8"/></h3>
722 <li> <code>pb.Copyable</code> is mostly implemented
723 in <code>twisted.spread.flavors</code>, and the docstrings there are
724 the best source of additional information.</li>
726 <li><code>Copyable</code> is also used in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.distrib.html" title="twisted.web.distrib">twisted.web.distrib</a></code> to deliver HTTP requests to other
727 programs for rendering, allowing subtrees of URL space to be delegated to
728 multiple programs (on multiple machines).</li>
730 <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.manhole.explorer.html" title="twisted.manhole.explorer">twisted.manhole.explorer</a></code> also uses
731 <code>Copyable</code> to distribute debugging information from the program
732 under test to the debugging tool.</li>
737 <h2>pb.Cacheable<a name="auto9"/></h2>
739 <p>Sometimes the object you want to send to the remote process is big and
740 slow. <q>big</q> means it takes a lot of data (storage, network bandwidth,
741 processing) to represent its state. <q>slow</q> means that state doesn't
742 change very frequently. It may be more efficient to send the full state only
743 once, the first time it is needed, then afterwards only send the differences
744 or changes in state whenever it is modified. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code> class provides a framework to
747 <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code> is derived
748 from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Copyable.html" title="twisted.spread.pb.Copyable">pb.Copyable</a></code>, so it is
749 based upon the idea of an object's state being captured on the sending side,
750 and then turned into a new object on the receiving side. This is extended to
751 have an object <q>publishing</q> on the sending side (derived from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Cacheable.html" title="twisted.spread.pb.Cacheable">pb.Cacheable</a></code>), matched with one
752 <q>observing</q> on the receiving side (derived from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">pb.RemoteCache</a></code>).</p>
754 <p>To effectively use <code>pb.Cacheable</code>, you need to isolate changes
755 to your object into accessor functions (specifically <q>setter</q>
756 functions). Your object needs to get control <em>every</em> single time some
757 attribute is changed<a href="#footnote-3" title="Of course you could be clever and add a hook to __setattr__, along with magical change-announcing subclasses of the usual builtin types, to detect changes that result from normal = set operations. The semi-magical property attributes that were introduced in Python 2.2 could be useful too. The result might be hard to maintain or extend, though."><super>3</super></a>.</p>
759 <p>You derive your sender-side class from <code>pb.Cacheable</code>, and you
760 add two methods: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Cacheable.getStateToCacheAndObserveFor.html" title="twisted.spread.flavors.Cacheable.getStateToCacheAndObserveFor">getStateToCacheAndObserveFor</a></code>
761 and <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Cacheable.stoppedObserving.html" title="twisted.spread.flavors.Cacheable.stoppedObserving">stoppedObserving</a></code>. The first
762 is called when a remote caching reference is first created, and retrieves
763 the data with which the cache is first filled. It also provides an
764 object called the <q>observer</q> <a href="#footnote-4" title="This is actually a RemoteCacheObserver, but it isn't very useful to subclass or modify, so simply treat it as a little demon that sits in your pb.Cacheable class and helps you distribute change notifications. The only useful thing to do with it is to run its callRemote method, which acts just like a normal pb.Referenceable's method of the same name."><super>4</super></a> that points at that receiver-side cache. Every time the state of the object
765 is changed, you give a message to the observer, informing them of the
766 change. The other method, <code>stoppedObserving</code>, is called when the
767 remote cache goes away, so that you can stop sending updates.</p>
769 <p>On the receiver end, you make your cache class inherit from <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCache.html" title="twisted.spread.pb.RemoteCache">pb.RemoteCache</a></code>, and implement the
770 <code>setCopyableState</code> as you would for a <code>pb.RemoteCopy</code>
771 object. In addition, you must implement methods to receive the updates sent
772 to the observer by the <code>pb.Cacheable</code>: these methods should have
773 names that start with <code>observe_</code>, and match the
774 <code>callRemote</code> invocations from the sender side just as the usual
775 <code>remote_*</code> and <code>perspective_*</code> methods match normal
776 <code>callRemote</code> calls. </p>
778 <p>The first time a reference to the <code>pb.Cacheable</code> object is
779 sent to any particular recipient, a sender-side Observer will be created for
780 it, and the <code>getStateToCacheAndObserveFor</code> method will be called
781 to get the current state and register the Observer. The state which that
782 returns is sent to the remote end and turned into a local representation
783 using <code>setCopyableState</code> just like <code>pb.RemoteCopy</code>,
784 described above (in fact it inherits from that class). </p>
786 <p>After that, your <q>setter</q> functions on the sender side should call
787 <code>callRemote</code> on the Observer, which causes <code>observe_*</code>
788 methods to run on the receiver, which are then supposed to update the
789 receiver-local (cached) state.</p>
791 <p>When the receiver stops following the cached object and the last
792 reference goes away, the <code>pb.RemoteCache</code> object can be freed.
793 Just before it dies, it tells the sender side it no longer cares about the
794 original object. When <em>that</em> reference count goes to zero, the
795 Observer goes away and the <code>pb.Cacheable</code> object can stop
796 announcing every change that takes place. The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.Cacheable.stoppedObserving.html" title="twisted.spread.flavors.Cacheable.stoppedObserving">stoppedObserving</a></code> method is
797 used to tell the <code>pb.Cacheable</code> that the Observer has gone
800 <p>With the <code>pb.Cacheable</code> and <code>pb.RemoteCache</code>
801 classes in place, bound together by a call to
802 <code>pb.setUnjellyableForClass</code>, all that remains is to pass a
803 reference to your <code>pb.Cacheable</code> over the wire to the remote end.
804 The corresponding <code>pb.RemoteCache</code> object will automatically be
805 created, and the matching methods will be used to keep the receiver-side
806 slave object in sync with the sender-side master object.</p>
808 <h3>Example<a name="auto10"/></h3>
810 <p>Here is a complete example, in which the <code>MasterDuckPond</code> is
811 controlled by the sending side, and the <code>SlaveDuckPond</code> is a
812 cache that tracks changes to the master:</p>
814 <div class="py-listing"><pre><p class="py-linenumber"> 1
857 </p><span class="py-src-comment">#!/usr/bin/env python</span>
859 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
860 <span class="py-src-comment"># See LICENSE for details.</span>
862 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
864 <span class="py-src-keyword">class</span> <span class="py-src-identifier">MasterDuckPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Cacheable</span>):
865 <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">ducks</span>):
866 <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span> = []
867 <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span> = <span class="py-src-variable">ducks</span>
868 <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
869 <span class="py-src-keyword">print</span> <span class="py-src-string">"I have [%d] ducks"</span> % <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>)
870 <span class="py-src-keyword">def</span> <span class="py-src-identifier">addDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">duck</span>):
871 <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">duck</span>)
872 <span class="py-src-keyword">for</span> <span class="py-src-variable">o</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>: <span class="py-src-variable">o</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'addDuck'</span>, <span class="py-src-variable">duck</span>)
873 <span class="py-src-keyword">def</span> <span class="py-src-identifier">removeDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">duck</span>):
874 <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">duck</span>)
875 <span class="py-src-keyword">for</span> <span class="py-src-variable">o</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>: <span class="py-src-variable">o</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'removeDuck'</span>, <span class="py-src-variable">duck</span>)
876 <span class="py-src-keyword">def</span> <span class="py-src-identifier">getStateToCacheAndObserveFor</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">observer</span>):
877 <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">observer</span>)
878 <span class="py-src-comment"># you should ignore pb.Cacheable-specific state, like self.observers</span>
879 <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">ducks</span> <span class="py-src-comment"># in this case, just a list of ducks</span>
880 <span class="py-src-keyword">def</span> <span class="py-src-identifier">stoppedObserving</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">perspective</span>, <span class="py-src-parameter">observer</span>):
881 <span class="py-src-variable">self</span>.<span class="py-src-variable">observers</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">observer</span>)
883 <span class="py-src-keyword">class</span> <span class="py-src-identifier">SlaveDuckPond</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">RemoteCache</span>):
884 <span class="py-src-comment"># This is a cache of a remote MasterDuckPond</span>
885 <span class="py-src-keyword">def</span> <span class="py-src-identifier">count</span>(<span class="py-src-parameter">self</span>):
886 <span class="py-src-keyword">return</span> <span class="py-src-variable">len</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>)
887 <span class="py-src-keyword">def</span> <span class="py-src-identifier">getDucks</span>(<span class="py-src-parameter">self</span>):
888 <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>
889 <span class="py-src-keyword">def</span> <span class="py-src-identifier">setCopyableState</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">state</span>):
890 <span class="py-src-keyword">print</span> <span class="py-src-string">" cache - sitting, er, setting ducks"</span>
891 <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span> = <span class="py-src-variable">state</span>
892 <span class="py-src-keyword">def</span> <span class="py-src-identifier">observe_addDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">newDuck</span>):
893 <span class="py-src-keyword">print</span> <span class="py-src-string">" cache - addDuck"</span>
894 <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>.<span class="py-src-variable">append</span>(<span class="py-src-variable">newDuck</span>)
895 <span class="py-src-keyword">def</span> <span class="py-src-identifier">observe_removeDuck</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">deadDuck</span>):
896 <span class="py-src-keyword">print</span> <span class="py-src-string">" cache - removeDuck"</span>
897 <span class="py-src-variable">self</span>.<span class="py-src-variable">cacheducks</span>.<span class="py-src-variable">remove</span>(<span class="py-src-variable">deadDuck</span>)
899 <span class="py-src-variable">pb</span>.<span class="py-src-variable">setUnjellyableForClass</span>(<span class="py-src-variable">MasterDuckPond</span>, <span class="py-src-variable">SlaveDuckPond</span>)
900 </pre><div class="caption">Source listing - <a href="listings/pb/cache_classes.py"><span class="filename">listings/pb/cache_classes.py</span></a></div></div>
901 <div class="py-listing"><pre><p class="py-linenumber"> 1
951 </p><span class="py-src-comment">#!/usr/bin/env python</span>
953 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
954 <span class="py-src-comment"># See LICENSE for details.</span>
956 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>, <span class="py-src-variable">jelly</span>
957 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">python</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">log</span>
958 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
959 <span class="py-src-keyword">from</span> <span class="py-src-variable">cache_classes</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">MasterDuckPond</span>
961 <span class="py-src-keyword">class</span> <span class="py-src-identifier">Sender</span>:
962 <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
963 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
965 <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase1</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">remote</span>):
966 <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span> = <span class="py-src-variable">remote</span>
967 <span class="py-src-variable">d</span> = <span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"takePond"</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>)
968 <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase2</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
969 <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase2</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">response</span>):
970 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">addDuck</span>(<span class="py-src-string">"ugly duckling"</span>)
971 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
972 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">callLater</span>(<span class="py-src-number">1</span>, <span class="py-src-variable">self</span>.<span class="py-src-variable">phase3</span>)
973 <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase3</span>(<span class="py-src-parameter">self</span>):
974 <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"checkDucks"</span>)
975 <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase4</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-variable">log</span>.<span class="py-src-variable">err</span>)
976 <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase4</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
977 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">removeDuck</span>(<span class="py-src-string">"one duck"</span>)
978 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>()
979 <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"checkDucks"</span>)
980 <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"ignorePond"</span>)
981 <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase5</span>)
982 <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase5</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
983 <span class="py-src-variable">d</span> = <span class="py-src-variable">self</span>.<span class="py-src-variable">remote</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">"shutdown"</span>)
984 <span class="py-src-variable">d</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">self</span>.<span class="py-src-variable">phase6</span>)
985 <span class="py-src-keyword">def</span> <span class="py-src-identifier">phase6</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">dummy</span>):
986 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
988 <span class="py-src-keyword">def</span> <span class="py-src-identifier">main</span>():
989 <span class="py-src-variable">master</span> = <span class="py-src-variable">MasterDuckPond</span>([<span class="py-src-string">"one duck"</span>, <span class="py-src-string">"two duck"</span>])
990 <span class="py-src-variable">master</span>.<span class="py-src-variable">count</span>()
992 <span class="py-src-variable">sender</span> = <span class="py-src-variable">Sender</span>(<span class="py-src-variable">master</span>)
993 <span class="py-src-variable">factory</span> = <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBClientFactory</span>()
994 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">connectTCP</span>(<span class="py-src-string">"localhost"</span>, <span class="py-src-number">8800</span>, <span class="py-src-variable">factory</span>)
995 <span class="py-src-variable">deferred</span> = <span class="py-src-variable">factory</span>.<span class="py-src-variable">getRootObject</span>()
996 <span class="py-src-variable">deferred</span>.<span class="py-src-variable">addCallback</span>(<span class="py-src-variable">sender</span>.<span class="py-src-variable">phase1</span>)
997 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">run</span>()
999 <span class="py-src-keyword">if</span> <span class="py-src-variable">__name__</span> == <span class="py-src-string">'__main__'</span>:
1000 <span class="py-src-variable">main</span>()
1001 </pre><div class="caption">Source listing - <a href="listings/pb/cache_sender.py"><span class="filename">listings/pb/cache_sender.py</span></a></div></div>
1002 <div class="py-listing"><pre><p class="py-linenumber"> 1
1030 </p><span class="py-src-comment">#!/usr/bin/env python</span>
1032 <span class="py-src-comment"># Copyright (c) Twisted Matrix Laboratories.</span>
1033 <span class="py-src-comment"># See LICENSE for details.</span>
1035 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span>, <span class="py-src-variable">internet</span>
1036 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">internet</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">reactor</span>
1037 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">spread</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">pb</span>
1038 <span class="py-src-keyword">import</span> <span class="py-src-variable">cache_classes</span>
1040 <span class="py-src-keyword">class</span> <span class="py-src-identifier">Receiver</span>(<span class="py-src-parameter">pb</span>.<span class="py-src-parameter">Root</span>):
1041 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_takePond</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">pond</span>):
1042 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">pond</span>
1043 <span class="py-src-keyword">print</span> <span class="py-src-string">"got pond:"</span>, <span class="py-src-variable">pond</span> <span class="py-src-comment"># a DuckPondCache</span>
1044 <span class="py-src-variable">self</span>.<span class="py-src-variable">remote_checkDucks</span>()
1045 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_checkDucks</span>(<span class="py-src-parameter">self</span>):
1046 <span class="py-src-keyword">print</span> <span class="py-src-string">"[%d] ducks: "</span> % <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">count</span>(), <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span>.<span class="py-src-variable">getDucks</span>()
1047 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_ignorePond</span>(<span class="py-src-parameter">self</span>):
1048 <span class="py-src-comment"># stop watching the pond</span>
1049 <span class="py-src-keyword">print</span> <span class="py-src-string">"dropping pond"</span>
1050 <span class="py-src-comment"># gc causes __del__ causes 'decache' msg causes stoppedObserving</span>
1051 <span class="py-src-variable">self</span>.<span class="py-src-variable">pond</span> = <span class="py-src-variable">None</span>
1052 <span class="py-src-keyword">def</span> <span class="py-src-identifier">remote_shutdown</span>(<span class="py-src-parameter">self</span>):
1053 <span class="py-src-variable">reactor</span>.<span class="py-src-variable">stop</span>()
1055 <span class="py-src-variable">application</span> = <span class="py-src-variable">service</span>.<span class="py-src-variable">Application</span>(<span class="py-src-string">"copy_receiver"</span>)
1056 <span class="py-src-variable">internet</span>.<span class="py-src-variable">TCPServer</span>(<span class="py-src-number">8800</span>, <span class="py-src-variable">pb</span>.<span class="py-src-variable">PBServerFactory</span>(<span class="py-src-variable">Receiver</span>())).<span class="py-src-variable">setServiceParent</span>(
1057 <span class="py-src-variable">service</span>.<span class="py-src-variable">IServiceCollection</span>(<span class="py-src-variable">application</span>))
1058 </pre><div class="caption">Source listing - <a href="listings/pb/cache_receiver.py"><span class="filename">listings/pb/cache_receiver.py</span></a></div></div>
1059 <p>When run, this example emits the following:</p>
1061 <pre class="shell" xml:space="preserve">
1062 $ twistd -n -y cache_receiver.py
1063 [-] twisted.spread.pb.PBServerFactory starting on 8800
1064 [-] Starting factory <twisted.spread.pb.PBServerFactory instance at
1066 [Broker,0,127.0.0.1] cache - sitting, er, setting ducks
1067 [Broker,0,127.0.0.1] got pond: <cache_classes.SlaveDuckPond instance at
1069 [Broker,0,127.0.0.1] [2] ducks: ['one duck', 'two duck']
1070 [Broker,0,127.0.0.1] cache - addDuck
1071 [Broker,0,127.0.0.1] [3] ducks: ['one duck', 'two duck', 'ugly duckling']
1072 [Broker,0,127.0.0.1] cache - removeDuck
1073 [Broker,0,127.0.0.1] [2] ducks: ['two duck', 'ugly duckling']
1074 [Broker,0,127.0.0.1] dropping pond
1077 <pre class="shell" xml:space="preserve">
1082 Main loop terminated.
1086 <p>Points to notice:</p>
1089 <li>There is one <code>Observer</code> for each remote program that holds
1090 an active reference. Multiple references inside the same program don't
1091 matter: the serialization layer notices the duplicates and does the
1092 appropriate reference counting<a href="#footnote-5" title="This applies to multiple references through the same Broker. If you've managed to make multiple TCP connections to the same program, you deserve whatever you get."><super>5</super></a>.
1095 <li>Multiple Observers need to be kept in a list, and all of them need to
1096 be updated when something changes. By sending the initial state at the
1097 same time as you add the observer to the list, in a single atomic action
1098 that cannot be interrupted by a state change, you insure that you can send
1099 the same status update to all the observers.</li>
1101 <li>The <code>observer.callRemote</code> calls can still fail. If the
1102 remote side has disconnected very recently and
1103 <code>stoppedObserving</code> has not yet been called, you may get a
1104 <code>DeadReferenceError</code>. It is a good idea to add an errback to
1105 those <code>callRemote</code>s to throw away such an error. This is a
1108 <pre class="python"><p class="py-linenumber">1
1109 </p><span class="py-src-variable">observer</span>.<span class="py-src-variable">callRemote</span>(<span class="py-src-string">'foo'</span>, <span class="py-src-variable">arg</span>).<span class="py-src-variable">addErrback</span>(<span class="py-src-keyword">lambda</span> <span class="py-src-variable">f</span>: <span class="py-src-variable">None</span>)
1114 <li><code>getStateToCacheAndObserverFor</code> must return some object
1115 that represents the current state of the object. This may simply be the
1116 object's <code>__dict__</code> attribute. It is a good idea to remove the
1117 <code>pb.Cacheable</code>-specific members of it before sending it to the
1118 remote end. The list of Observers, in particular, should be left out, to
1119 avoid dizzying recursive Cacheable references. The mind boggles as to the
1120 potential consequences of leaving in such an item.</li>
1122 <li>A <code>perspective</code> argument is available to
1123 <code>getStateToCacheAndObserveFor</code>, as well as
1124 <code>stoppedObserving</code>. I think the purpose of this is to allow
1125 viewer-specific changes to the way the cache is updated. If all remote
1126 viewers are supposed to see the same data, it can be ignored.</li>
1133 <h3>More Information<a name="auto11"/></h3>
1136 <li>The best source for information comes from the docstrings
1137 in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.html" title="twisted.spread.flavors">twisted.spread.flavors</a></code>,
1138 where <code>pb.Cacheable</code> is implemented.</li>
1140 <li><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.manhole.explorer.html" title="twisted.manhole.explorer">twisted.manhole.explorer</a></code> uses
1141 <code>Cacheable</code>, and does some fairly interesting things with it.</li>
1143 <li>The <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.publish.html" title="twisted.spread.publish">spread.publish</a></code> module also
1144 uses <code>Cacheable</code>, and might be a source of further
1150 <h2>Footnotes</h2><ol><li><a name="footnote-1"><span class="footnote">Note that, in this context, <q>unjelly</q> is
1151 a verb with the opposite meaning of <q>jelly</q>. The verb <q>to jelly</q>
1152 means to serialize an object or data structure into a sequence of bytes (or
1153 other primitive transmittable/storable representation), while <q>to
1154 unjelly</q> means to unserialize the bytestream into a live object in the
1155 receiver's memory space. <q>Unjellyable</q> is a noun, (<em>not</em> an
1156 adjective), referring to the the class that serves as a destination or
1157 recipient of the unjellying process. <q>A is unjellyable into B</q> means
1158 that a serialized representation A (of some remote object) can be
1159 unserialized into a local object of type B. It is these objects <q>B</q>
1160 that are the <q>Unjellyable</q> second argument of the
1161 <code>setUnjellyableForClass</code> function.
1162 In particular, <q>unjellyable</q> does <em>not</em> mean <q>cannot be
1163 jellied</q>. <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.jelly.Unpersistable.html" title="twisted.spread.jelly.Unpersistable">Unpersistable</a></code> means <q>not
1164 persistable</q>, but <q>unjelly</q>, <q>unserialize</q>, and <q>unpickle</q>
1165 mean to reverse the operations of <q>jellying</q>, <q>serializing</q>, and
1166 <q>pickling</q>.</span></a></li><li><a name="footnote-2"><span class="footnote"><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCopy.html" title="twisted.spread.pb.RemoteCopy">pb.RemoteCopy</a></code> is actually defined
1167 in <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.flavors.html" title="twisted.spread.flavors">twisted.spread.flavors</a></code>, but
1168 <code>pb.RemoteCopy</code> is the preferred way to access it</span></a></li><li><a name="footnote-3"><span class="footnote">Of course you could be clever and
1169 add a hook to <code>__setattr__</code>, along with magical change-announcing
1170 subclasses of the usual builtin types, to detect changes that result from
1171 normal <q>=</q> set operations. The semi-magical <q>property attributes</q>
1172 that were introduced in Python 2.2 could be useful too. The result might be
1173 hard to maintain or extend, though.</span></a></li><li><a name="footnote-4"><span class="footnote">This is actually a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.RemoteCacheObserver.html" title="twisted.spread.pb.RemoteCacheObserver">RemoteCacheObserver</a></code>, but it isn't very
1174 useful to subclass or modify, so simply treat it as a little demon that sits
1175 in your <code>pb.Cacheable</code> class and helps you distribute change
1176 notifications. The only useful thing to do with it is to run its
1177 <code>callRemote</code> method, which acts just like a normal
1178 <code>pb.Referenceable</code>'s method of the same name.</span></a></li><li><a name="footnote-5"><span class="footnote">This applies to
1179 multiple references through the same <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.spread.pb.Broker.html" title="twisted.spread.pb.Broker">Broker</a></code>. If you've managed to make multiple
1180 TCP connections to the same program, you deserve whatever you get.</span></a></li></ol></div>
1182 <p><a href="index.html">Index</a></p>
1183 <span class="version">Version: 12.1.0</span>