1
<?xml version="1.0"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title>Twisted Documentation: Upgrading Applications</title><link href="../howto/stylesheet.css" type="text/css" rel="stylesheet" /></head><body bgcolor="white"><h1 class="title">Upgrading Applications</h1><div class="toc"><ol><li><a href="#auto0">Basic Persistence: Application and .tap files</a></li><li><a href="#auto1">Versioned: New Code Meets Old Data</a></li><li><a href="#auto2">Rebuild: Loading New Code Without Restarting</a></li></ol></div><div class="content"><span></span><p>Applications must frequently deal with data that lives longer than the
3
programs that create it. Sometimes the structure of that data changes over
4
time, but new versions of a program must be able to accomodate data created
5
by an older version. These versions may change very quickly, especially
6
during development of new code. Sometimes different versions of the same
7
program are running at the same time, sharing data across a network
8
connection. These situations all result in a need for a way to upgrade data
9
structures. </p><h2>Basic Persistence: Application and .tap files<a name="auto0"></a></h2><p>Simple object persistence (using <code>pickle</code> or
10
<code>jelly</code>) provides the fundamental <q>save the object to disk</q>
11
functionality at application shutdown. If you use the <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code> object, every object
12
referenced by your Application will be saved into the
13
<code>-shutdown.tap</code> file when the program terminates. When you use
14
<code>twistd</code> to launch that new .tap file, the Application object
15
will be restored along with all of its referenced data.</p><p>This provides a simple way to have data outlive any particular invocation
16
of your program: simply store it as an attribute of the Application. Note
17
that all Services are referenced by the Application, so their attributes
18
will be stored as well. Ports that have been bound with listenTCP (and the
19
like) are also remembered, and the sockets are created at startup time (when
20
<code>Application.run</code> is called).</p><p>To influence the way that the <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.application.service.Application.html" title="twisted.application.service.Application">Application</a></code> is persisted, you can adapt
21
it to <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.sob.IPersistable.html" title="twisted.persisted.sob.IPersistable">twisted.persisted.sob.IPersistable</a></code> and use
22
the <code class="python">setStyle(style)</code> method with
23
a string like <q>pickle</q>, <q>xml</q>, or <q>source</q>. These use different serializers (and different
24
extensions: <q>.tap</q>, <q>.tax</q>, and <q>.tas</q> respectively) for the
25
saved Application.</p><p>You can manually cause the application to be saved by calling its
26
<code>.save</code> method (on the <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.sob.IPersistable.html" title="twisted.persisted.sob.IPersistable">twisted.persisted.sob.IPersistable</a></code>
27
adapted object).</p><h2>Versioned: New Code Meets Old Data<a name="auto1"></a></h2><p>So suppose you're running version 1 of some application, and you want to
28
upgrade to version 2. You shut down the program, giving you a .tap file that
29
you could restore with twistd to get back to the same state that you had
30
before. The upgrade process is to then install the new version of the
31
application, and then use twistd to launch the saved .tap file. The old data
32
will be loaded into classes created with the new code, and now you'll have a
33
program running with the new behavior but the old data.</p><p>But what about the data structures that have changed? Since these
34
structures are really just pickled class instances, the real question is
35
what about the class definitions that have changed? Changes to class methods
36
are easy: nothing about them is saved in the .tap file. The issue is when
37
the data attributes of a instance are added, removed, or their format is
38
changed.</p><p>Twisted provides a mechanism called <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">Versioned</a></code> to ease these upgrades.
39
Each version of the data structure (i.e. each version of the class) gets a
40
version number. This number must change every time you add or remove a data
41
attribute to the class. It must also change every time you modify one of
42
those data attributes: for example, if you use a string in one version and
43
an integer in another, those versions must have different version numbers.
44
</p><p>The version number is defined in a class attribute named
45
<code>persistenceVersion</code>. This is an integer which will be stored in
46
the .tap file along with the rest of the instance state. When the object is
47
unserialized, the saved persistenceVersion is compared against the current
48
class's value, and if they differ, special upgrade methods are called. These
49
methods are named <code>upgradeToVersionNN</code>, and there must be one for
50
each intermediate version. These methods are expected to manipulate the
51
instance's state from the previous version's format into that of the new
52
version.</p><p>To use this, simply have your class inherit from <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">Versioned</a></code>. You don't have to do this
53
from the very beginning of time: all objects have an implicit version number
54
of <q>0</q> when they don't inherit from Versioned. So when you first make
55
an incompatible data-format change to your class, add Versioned to the
56
inheritance list, and add an <code>upgradeToVersion1</code> method.</p><p>For example, suppose the first version of our class saves an integer
57
which measures the size of a line. We release this as version 1.0 of our
58
neat application:</p><pre class="python">
59
<span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span><span class="py-src-op">:</span><span class="py-src-newline">
60
</span><span class="py-src-indent"> </span><span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">length</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
61
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-variable">length</span><span class="py-src-newline">
62
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Then we fix some bugs elsewhere, and release versions 1.1 and 1.2 of the
63
application. Later, we decide that we should add some units to the length,
64
so that people can refer to it in inches or meters. Version 1.3 is shipped
65
with the following code:</p><pre class="python">
66
<span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span><span class="py-src-op">(</span><span class="py-src-parameter">Versioned</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
67
</span><span class="py-src-indent"> </span><span class="py-src-variable">persistenceVersion</span> <span class="py-src-op">=</span> <span class="py-src-number">1</span><span class="py-src-newline">
68
</span> <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">length</span><span class="py-src-op">,</span> <span class="py-src-parameter">units</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
69
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-string">"%d %s"</span> <span class="py-src-op">%</span> <span class="py-src-op">(</span><span class="py-src-variable">length</span><span class="py-src-op">,</span> <span class="py-src-variable">units</span><span class="py-src-op">)</span><span class="py-src-newline">
70
</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion1</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
71
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-string">"%d inches"</span> <span class="py-src-op">%</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-newline">
72
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Note that we must make an assumption about what the previous value meant:
73
in this case, we assume the number was in inches.</p><p>1.4 and 1.5 are shipped with other changes. Then in version 1.6 we decide
74
that saving the two values as a string was foolish and that it would be
75
better to save the number and the string separately, using a tuple. We ship
76
1.6 with the following:</p><pre class="python">
77
<span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span><span class="py-src-op">(</span><span class="py-src-parameter">Versioned</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
78
</span><span class="py-src-indent"> </span><span class="py-src-variable">persistenceVersion</span> <span class="py-src-op">=</span> <span class="py-src-number">2</span><span class="py-src-newline">
79
</span> <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">length</span><span class="py-src-op">,</span> <span class="py-src-parameter">units</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
80
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-op">(</span><span class="py-src-variable">length</span><span class="py-src-op">,</span> <span class="py-src-variable">units</span><span class="py-src-op">)</span><span class="py-src-newline">
81
</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion1</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
82
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-string">"%d inches"</span> <span class="py-src-op">%</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-newline">
83
</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion2</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
84
</span><span class="py-src-indent"> </span><span class="py-src-op">(</span><span class="py-src-variable">length</span><span class="py-src-op">,</span> <span class="py-src-variable">units</span><span class="py-src-op">)</span> <span class="py-src-op">=</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-op">.</span><span class="py-src-variable">split</span><span class="py-src-op">(</span><span class="py-src-op">)</span><span class="py-src-newline">
85
</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-op">(</span><span class="py-src-variable">length</span><span class="py-src-op">,</span> <span class="py-src-variable">units</span><span class="py-src-op">)</span><span class="py-src-newline">
86
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Note that we must provide both <code>upgradeToVersion1</code><em>and</em><code>upgradeToVersion2</code>. We have to assume that the
87
saved .tap files which will be provided to this class come from a random
88
assortment of old versions: we must be prepared to accept anything ever
89
saved by a released version of our application.</p><p>Finally, version 2.0 adds multiple dimensions. Instead of merely
90
recording the length of a line, it records the size of an N-dimensional
91
rectangular solid. For backwards compatiblity, all 1.X version of the
92
program are assumed to be dealing with a 1-dimensional line. We change the
93
name of the attribute from <code>.length</code> to <code>.size</code> to
94
reflect the new meaning.</p><pre class="python">
95
<span class="py-src-keyword">class</span> <span class="py-src-identifier">Thing</span><span class="py-src-op">(</span><span class="py-src-parameter">Versioned</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
96
</span><span class="py-src-indent"> </span><span class="py-src-variable">persistenceVersion</span> <span class="py-src-op">=</span> <span class="py-src-number">3</span><span class="py-src-newline">
97
</span> <span class="py-src-keyword">def</span> <span class="py-src-identifier">__init__</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">dimensions</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
98
</span> <span class="py-src-comment"># dimensions is a list of tuples, each is (length, units)
99
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">dimensions</span><span class="py-src-newline">
100
</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">name</span> <span class="py-src-op">=</span> <span class="py-src-op">[</span><span class="py-src-string">"line"</span><span class="py-src-op">,</span> <span class="py-src-string">"square"</span><span class="py-src-op">,</span> <span class="py-src-string">"cube"</span><span class="py-src-op">,</span> <span class="py-src-string">"hypercube"</span><span class="py-src-op">]</span><span class="py-src-op">[</span><span class="py-src-variable">len</span><span class="py-src-op">(</span><span class="py-src-variable">dimensions</span><span class="py-src-op">)</span><span class="py-src-op">]</span><span class="py-src-newline">
101
</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion1</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
102
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-string">"%d inches"</span> <span class="py-src-op">%</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-newline">
103
</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion2</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
104
</span><span class="py-src-indent"> </span><span class="py-src-op">(</span><span class="py-src-variable">length</span><span class="py-src-op">,</span> <span class="py-src-variable">units</span><span class="py-src-op">)</span> <span class="py-src-op">=</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-op">.</span><span class="py-src-variable">split</span><span class="py-src-op">(</span><span class="py-src-op">)</span><span class="py-src-newline">
105
</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span> <span class="py-src-op">=</span> <span class="py-src-op">(</span><span class="py-src-variable">length</span><span class="py-src-op">,</span> <span class="py-src-variable">units</span><span class="py-src-op">)</span><span class="py-src-newline">
106
</span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">upgradeToVersion3</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline">
107
</span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-op">[</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-op">]</span><span class="py-src-newline">
108
</span> <span class="py-src-keyword">del</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">length</span><span class="py-src-newline">
109
</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">name</span> <span class="py-src-op">=</span> <span class="py-src-string">"line"</span><span class="py-src-newline">
110
</span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>If a .tap file from the earliest version of our program were to be loaded
111
by the latest code, the following sequence would occur for each Thing
112
instance contained inside:</p><ol><li>An instance of Thing would be created, with a __dict__ that contained
113
a single attribute <code>.size</code>, which was an integer, like
114
<q>5</q>.</li><li><code class="python">self.upgradeToVersion1()</code> would be called,
115
changing <code>self.size</code> into a string, like <q>5 inches</q>.</li><li><code class="python">self.upgradeToVersion2()</code> would be called,
116
changing <code>self.size</code> into a tuple, like (5,
117
<q>inches</q>).</li><li>Finally, <code class="python">self.upgradeToVersion3()</code> would be
118
called, creating <code>self.size</code> as a list holding a single
119
dimension, like [(5, <q>inches</q>)]. The old <code>.length</code>
120
attribute is deleted, and a new <code>.name</code> is created with the
121
type of shape this instance represents (<q>line</q>).</li></ol><p>Some hints for the <code>upgradeVersion</code> methods:</p><ul><li>They must do everything the <code>__init__</code> method would have
122
done, as well as any methods that might have been called during the
123
lifetime of the object.</li><li>If the class has (or used to have) methods which can add attributes
124
that weren't created in <code>__init__</code>, then the saved object may
125
have a haphazard subset of those attributes, depending upon which methods
126
were called. The upgradeVersion methods must be prepared to deal with
127
this. <code>hasattr</code> and <code>.get</code> may be useful.</li><li>Once you have released a class with a given
128
<code>upgradeVersion</code> method, you should never change that method.
129
(assuming you care about infinite backwards compatibility).</li><li>You must add a new <code>upgradeVersion</code> method (and bump the
130
persistenceVersion value) for each and every release that has a different
131
set of data attributes than the previous release.</li><li><code>Versioned</code> works by providing <code>__setstate__</code>
132
and <code>__getstate__</code> methods. You probably don't want to override
133
these methods without being very careful to call the Versioned versions at
134
exactly the right time. It also requires a <code>doUpgrade</code> function
135
to be called after all the objects are loaded. This is done automatically
136
by <code>Application.run</code>.</li><li>Depending upon how they are serialized, <code>Versioned</code> objects
137
can probably be sent across a network connection, and the upgrade process
138
can be made to occur upon receipt. (You'll want to look at the <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.styles.requireUpgrade.html" title="twisted.persisted.styles.requireUpgrade">requireUpgrade</a></code>
139
function). This might be useful in providing compability with an older
140
peer. Note, however, that <code>Versioned</code> does not let you go
141
backwards in time; there is no <code>downgradeVersionNN</code> method.
142
This means it is probably only useful for compatibility in one direction:
143
the newer-to-older direction must still be explicitly handled by the
144
application.</li><li>In general, backwards compatibility is handled by pretending that the
145
old code was restricting itself to a narrow subset of the capabilities of
146
the new code. The job of the upgrade routines is then to translate the old
147
representation into a new one.</li></ul><p>For more information, look at the doc strings for <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">styles.Versioned</a></code>, as well as the <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.internet.app.Application.html" title="twisted.internet.app.Application">app.Application</a></code> class and the <a href="application.html">Application HOWTO</a>.</p><h2>Rebuild: Loading New Code Without Restarting<a name="auto2"></a></h2><p><code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/Versioned.html" title="Versioned">Versioned</a></code> is good for handling changes between
148
released versions of your program, where the application state is saved on
149
disk during the upgrade. But while you are developing that code, you often
150
want to change the behavior of the running program, <em>without</em> the
151
slowdown of saving everything out to disk, shutting down, and restarting.
152
Sometimes it will be difficult or time-consuming to get back to the previous
153
state: the running program could include ephemeral objects (like open
154
sockets) which cannot be persisted.</p><p><code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.rebuild.html" title="twisted.python.rebuild">twisted.python.rebuild</a></code> provides a function
155
called <code>rebuild</code> which helps smooth this cycle. It allows objects
156
in a running program to be upgraded to a new version of the code without
157
shutting down.</p><p>To use it, simply call <code class="python">rebuild</code> on the module
158
that holds the classes you want to be upgraded. Through deep <code class="python">gc</code> magic, all instances of classes in that module will
159
be located and upgraded.</p><p>Typically, this is done in response to a privileged command sent over a
160
network connection. The usual development cycle is to start the server, get
161
it into an interesting state, see a problem, edit the class definition, then
162
push the <q>rebuild yourself</q> button. That <q>button</q> could be a magic
163
web page which, when requested, runs <code class="python">rebuild(mymodule)</code>, or a special IRC command, or
164
perhaps just a socket that listens for connections and accepts a password to
165
trigger the rebuild. (You want this to be a privileged operation to prevent
166
someone from making your server do a rebuild while you're in the middle of
167
editing the code).</p><p>A few useful notes about the rebuild process:</p><ul><li>If the module has a top-level attribute named
168
<code>ALLOW_TWISTED_REBUILD</code>, this attribute must evaluate to True.
169
Should it be false, the rebuild attempt will raise an exception.</li><li>Adapters (from <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.components.html" title="twisted.python.components">twisted.python.components</a></code>) use
170
top-level registration function calls. These are handled correctly during
171
rebuilds, and the usual duplicate registration errors are not raised.</li><li>Rebuilds may be slow: every single object known to the interpreter
172
must be examined to see if it is one of the classes being changed.</li></ul><p>Finally, note that <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.python.rebuild.rebuild.html" title="twisted.python.rebuild.rebuild">rebuild</a></code><em>cannot</em> currently be
173
mixed with <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.persisted.styles.Versioned.html" title="twisted.persisted.styles.Versioned">Versioned</a></code>. <code>rebuild</code> does
174
not run any of the classes' methods, whereas <code>Versioned</code> works by
175
running <code>__setstate__</code> during the load process and
176
<code>doUpgrade</code> afterwards. This means <code>rebuild</code> can only
177
be used to process upgrades that do not change the data attributes of any of
178
the involved classes. Any time attributes are added or removed, the program
179
must be shut down, persisted, and restarted, with upgradeToVersionNN methods
180
used to handle the attributes. (this may change in the future, but for now
181
the implementation is easier and more reliable with this restriction).</p></div><p><a href="../howto/index.html">Index</a></p><span class="version">Version: 1.3.0</span></body></html>
b'\\ No newline at end of file'