1 # -*- test-case-name: twisted.test.test_persisted -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
8 Different styles of persisted objects.
19 import cStringIO as StringIO
24 from twisted.python import log
25 from twisted.python import reflect
29 ## First, let's register support for some stuff that really ought to
32 def pickleMethod(method):
33 'support function for copy_reg to pickle method refs'
34 return unpickleMethod, (method.im_func.__name__,
38 def unpickleMethod(im_name,
41 'support function for copy_reg to unpickle method refs'
43 unbound = getattr(im_class,im_name)
46 bound = types.MethodType(unbound.im_func, im_self, im_class)
48 except AttributeError:
49 log.msg("Method",im_name,"not on class",im_class)
50 assert im_self is not None,"No recourse: no instance to guess from."
51 # Attempt a common fix before bailing -- if classes have
52 # changed around since we pickled this method, we may still be
53 # able to get it by looking on the instance's current class.
54 unbound = getattr(im_self.__class__,im_name)
55 log.msg("Attempting fixup with",unbound)
58 bound = types.MethodType(unbound.im_func, im_self, im_self.__class__)
61 copy_reg.pickle(types.MethodType,
65 def pickleModule(module):
66 'support function for copy_reg to pickle module refs'
67 return unpickleModule, (module.__name__,)
69 def unpickleModule(name):
70 'support function for copy_reg to unpickle module refs'
71 if oldModules.has_key(name):
72 log.msg("Module has moved: %s" % name)
73 name = oldModules[name]
75 return __import__(name,{},{},'x')
78 copy_reg.pickle(types.ModuleType,
82 def pickleStringO(stringo):
83 'support function for copy_reg to pickle StringIO.OutputTypes'
84 return unpickleStringO, (stringo.getvalue(), stringo.tell())
86 def unpickleStringO(val, sek):
87 x = StringIO.StringIO()
92 if hasattr(StringIO, 'OutputType'):
93 copy_reg.pickle(StringIO.OutputType,
97 def pickleStringI(stringi):
98 return unpickleStringI, (stringi.getvalue(), stringi.tell())
100 def unpickleStringI(val, sek):
101 x = StringIO.StringIO(val)
106 if hasattr(StringIO, 'InputType'):
107 copy_reg.pickle(StringIO.InputType,
113 This type of object is never persisted; if possible, even references to it
117 def __getstate__(self):
118 log.msg( "WARNING: serializing ephemeral %s" % self )
120 if '__pypy__' not in sys.builtin_module_names:
121 if getattr(gc, 'get_referrers', None):
122 for r in gc.get_referrers(self):
123 log.msg( " referred to by %s" % (r,))
126 def __setstate__(self, state):
127 log.msg( "WARNING: unserializing ephemeral %s" % self.__class__ )
128 self.__class__ = Ephemeral
131 versionedsToUpgrade = {}
135 global versionedsToUpgrade, upgraded
136 for versioned in versionedsToUpgrade.values():
137 requireUpgrade(versioned)
138 versionedsToUpgrade = {}
141 def requireUpgrade(obj):
142 """Require that a Versioned instance be upgraded completely first.
145 if objID in versionedsToUpgrade and objID not in upgraded:
152 Get all of the parent classes of C{c}, not including C{c} itself, which are
153 strict subclasses of L{Versioned}.
155 The name comes from "all your base are belong to us", from the deprecated
156 L{twisted.python.reflect.allYourBase} function.
159 @returns: list of classes
161 # begin with two classes that should *not* be included in the
164 for b in inspect.getmro(c):
165 if b not in l and issubclass(b, Versioned):
167 # return all except the unwanted classes
172 This type of object is persisted with versioning information.
174 I have a single class attribute, the int persistenceVersion. After I am
175 unserialized (and styles.doUpgrade() is called), self.upgradeToVersionX()
176 will be called for each version upgrade I must undergo.
178 For example, if I serialize an instance of a Foo(Versioned) at version 4
179 and then unserialize it when the code is at version 9, the calls::
181 self.upgradeToVersion5()
182 self.upgradeToVersion6()
183 self.upgradeToVersion7()
184 self.upgradeToVersion8()
185 self.upgradeToVersion9()
187 will be made. If any of these methods are undefined, a warning message
190 persistenceVersion = 0
191 persistenceForgets = ()
193 def __setstate__(self, state):
194 versionedsToUpgrade[id(self)] = self
195 self.__dict__ = state
197 def __getstate__(self, dict=None):
198 """Get state, adding a version number to it on its way out.
200 dct = copy.copy(dict or self.__dict__)
201 bases = _aybabtu(self.__class__)
203 bases.append(self.__class__) # don't forget me!!
205 if base.__dict__.has_key('persistenceForgets'):
206 for slot in base.persistenceForgets:
207 if dct.has_key(slot):
209 if base.__dict__.has_key('persistenceVersion'):
210 dct['%s.persistenceVersion' % reflect.qual(base)] = base.persistenceVersion
213 def versionUpgrade(self):
214 """(internal) Do a version upgrade.
216 bases = _aybabtu(self.__class__)
217 # put the bases in order so superclasses' persistenceVersion methods
218 # will be called first.
220 bases.append(self.__class__) # don't forget me!!
221 # first let's look for old-skool versioned's
222 if self.__dict__.has_key("persistenceVersion"):
224 # Hacky heuristic: if more than one class subclasses Versioned,
225 # we'll assume that the higher version number wins for the older
226 # class, so we'll consider the attribute the version of the older
227 # class. There are obviously possibly times when this will
228 # eventually be an incorrect assumption, but hopefully old-school
229 # persistenceVersion stuff won't make it that far into multiple
230 # classes inheriting from Versioned.
232 pver = self.__dict__['persistenceVersion']
233 del self.__dict__['persistenceVersion']
237 if not base.__dict__.has_key('persistenceVersion'):
239 if base.persistenceVersion > highestVersion:
241 highestVersion = base.persistenceVersion
243 self.__dict__['%s.persistenceVersion' % reflect.qual(highestBase)] = pver
245 # ugly hack, but it's what the user expects, really
246 if (Versioned not in base.__bases__ and
247 not base.__dict__.has_key('persistenceVersion')):
249 currentVers = base.persistenceVersion
250 pverName = '%s.persistenceVersion' % reflect.qual(base)
251 persistVers = (self.__dict__.get(pverName) or 0)
253 del self.__dict__[pverName]
254 assert persistVers <= currentVers, "Sorry, can't go backwards in time."
255 while persistVers < currentVers:
256 persistVers = persistVers + 1
257 method = base.__dict__.get('upgradeToVersion%s' % persistVers, None)
259 log.msg( "Upgrading %s (of %s @ %s) to version %s" % (reflect.qual(base), reflect.qual(self.__class__), id(self), persistVers) )
262 log.msg( 'Warning: cannot upgrade %s to version %s' % (base, persistVers) )