Welcome, guest | Sign In | My Account | Store | Cart

One of the problems with persistent objects is that one often needs a mechanism to keep them in sync with the codebase. This provides such a mechanism for pickled objects under the control of CVS via the magic CVS $Revision$ string, which CVS will automatically update to match a file's revision number.

Python, 59 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import re

def parse_revision_string(r):
    # convert "$Revision: x.y.z $" => "x.y.z"
    try:
        return re.search('Revision: (.*?)\s*\$',r).group(1)
    except:
        return "0"

# PickleRevisionControl class.  Inherit me.
class PickleRevisionControl(object):
    # Important!  This line must be put into all subclasses
    __revision__ = parse_revision_string("$Revision$")

    def __init__(self):
        self.__revision__ = self.__revision__

    def is_obsolete(self):
        return self.__revision__ < self.__class__.__revision__

    def __setstate__(self, state):
        self.__dict__.update(state)
        if self.is_obsolete():
            self.upgrade()
            self.__revision__ = self.__class__.__revision__

    def upgrade(self):
        # do your upgrading here.
        pass


#-----------------------------------------------------------------
# Example usage scenario
#-----------------------------------------------------------------
# Suppose you have a class "Widget" in Widget.py.  One day, after a
# little misunderstanding with NASA, you get a memo declaring that 
# all widget measurements will henceforth be in metric units.
#
# Changes to the widget source code go quickly, and you commit Widget.py
# version 1.5 to CVS.  Unfortunately, all of your pickled version 1.4 
# widgets still use imperial units for everything.  But you've been 
# using the PickleRevisionControl class all along, so you can fix 
# things by adding few lines to Widget.upgrade()
#
# 
# class Widget(PickleRevisionControl):
#    __revision__ = parse_revision_string("$Revision: 1.5 $")
#    def __init__(self):
#        PickleRevisionControl.__init__(self)
#        ...
#
#    def upgrade(self):
#        if self.__revision__ < '1.5':
#            self.radius *= 0.0254  # inches to meters
#            self.height *= 0.0254 
#
#    ...
#
# Now your old widgets will be magically updated when you unpickle them!

The CVS revision is stored both in the class __revision__ string and as an attribute of instances of the class. When an instance is unpickled, self.__revision__ is compared to self.__class__.__revision__, and self.upgrade() is called if the revisions are out of sync.

This does not consider the case where the codebase might have an older version than the pickle, but it would be easy to incorporate this.

Also, it would be perfectly easy to substitute a non-CVS revision string, e.g. if you wanted to update the revision manually.

1 comment

Alexander Semenov 17 years ago  # | flag

Twisted Versioned. Excellent recipe! Some improvment ideas may be taken from Twisted Versioned http://twistedmatrix.com/documents/current/howto/upgrading Personally, I prefer to have methods like UpgradeXXXtoYYY() to do chained updates.

Created by Lonnie Princehouse on Tue, 16 Nov 2004 (PSF)
Python recipes (4591)
Lonnie Princehouse's recipes (5)

Required Modules

Other Information and Tasks