Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 1 | # persist.py |
| 2 | # |
| 3 | # Implement limited persistence. |
| 4 | # |
| 5 | # Simple interface: |
| 6 | # persist.save() save __main__ module on file (overwrite) |
| 7 | # persist.load() load __main__ module from file (merge) |
| 8 | # |
| 9 | # These use the filename persist.defaultfile, initialized to 'wsrestore.py'. |
| 10 | # |
| 11 | # A raw interface also exists: |
| 12 | # persist.writedict(dict, fp) save dictionary to open file |
| 13 | # persist.readdict(dict, fp) read (merge) dictionary from open file |
| 14 | # |
| 15 | # Internally, the function dump() and a whole bunch of support of functions |
| 16 | # traverse a graph of objects and print them in a restorable form |
| 17 | # (which happens to be a Python module). |
| 18 | # |
| 19 | # XXX Limitations: |
| 20 | # - Volatile objects are dumped as strings: |
| 21 | # - open files, windows etc. |
| 22 | # - Other 'obscure' objects are dumped as strings: |
| 23 | # - classes, instances and methods |
| 24 | # - compiled regular expressions |
| 25 | # - anything else reasonably obscure (e.g., capabilities) |
| 26 | # - type objects for obscure objects |
| 27 | # - It's slow when there are many of lists or dictionaries |
| 28 | # (This could be fixed if there were a quick way to compute a hash |
| 29 | # function of any object, even if recursive) |
| 30 | |
| 31 | defaultfile = 'wsrestore.py' |
| 32 | |
| 33 | def save(): |
| 34 | import __main__ |
Guido van Rossum | 25d7caf | 1992-03-31 19:04:48 +0000 | [diff] [blame] | 35 | import os |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 36 | # XXX On SYSV, if len(defaultfile) >= 14, this is wrong! |
| 37 | backup = defaultfile + '~' |
| 38 | try: |
Guido van Rossum | 25d7caf | 1992-03-31 19:04:48 +0000 | [diff] [blame] | 39 | os.unlink(backup) |
| 40 | except os.error: |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 41 | pass |
| 42 | try: |
Guido van Rossum | 25d7caf | 1992-03-31 19:04:48 +0000 | [diff] [blame] | 43 | os.rename(defaultfile, backup) |
| 44 | except os.error: |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 45 | pass |
| 46 | fp = open(defaultfile, 'w') |
| 47 | writedict(__main__.__dict__, fp) |
| 48 | fp.close() |
| 49 | |
| 50 | def load(): |
| 51 | import __main__ |
| 52 | fp = open(defaultfile, 'r') |
| 53 | readdict(__main__.__dict__, fp) |
| 54 | |
| 55 | def writedict(dict, fp): |
| 56 | import sys |
| 57 | savestdout = sys.stdout |
| 58 | try: |
| 59 | sys.stdout = fp |
| 60 | dump(dict) # Writes to sys.stdout |
| 61 | finally: |
| 62 | sys.stdout = savestdout |
| 63 | |
| 64 | def readdict(dict, fp): |
Guido van Rossum | fa0e726 | 1991-04-21 19:33:30 +0000 | [diff] [blame] | 65 | contents = fp.read() |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 66 | globals = {} |
| 67 | exec(contents, globals) |
| 68 | top = globals['top'] |
| 69 | for key in top.keys(): |
| 70 | if dict.has_key(key): |
| 71 | print 'warning:', key, 'not overwritten' |
| 72 | else: |
| 73 | dict[key] = top[key] |
| 74 | |
| 75 | |
| 76 | # Function dump(x) prints (on sys.stdout!) a sequence of Python statements |
| 77 | # that, when executed in an empty environment, will reconstruct the |
| 78 | # contents of an arbitrary dictionary. |
| 79 | |
| 80 | import sys |
| 81 | |
| 82 | # Name used for objects dict on output. |
| 83 | # |
| 84 | FUNNYNAME = FN = 'A' |
| 85 | |
| 86 | # Top-level function. Call with the object you want to dump. |
| 87 | # |
| 88 | def dump(x): |
| 89 | types = {} |
| 90 | stack = [] # Used by test for recursive objects |
| 91 | print FN, '= {}' |
| 92 | topuid = dumpobject(x, types, stack) |
| 93 | print 'top =', FN, '[', `topuid`, ']' |
| 94 | |
| 95 | # Generic function to dump any object. |
| 96 | # |
| 97 | dumpswitch = {} |
| 98 | # |
| 99 | def dumpobject(x, types, stack): |
| 100 | typerepr = `type(x)` |
| 101 | if not types.has_key(typerepr): |
| 102 | types[typerepr] = {} |
| 103 | typedict = types[typerepr] |
| 104 | if dumpswitch.has_key(typerepr): |
| 105 | return dumpswitch[typerepr](x, typedict, types, stack) |
| 106 | else: |
| 107 | return dumpbadvalue(x, typedict, types, stack) |
| 108 | |
| 109 | # Generic function to dump unknown values. |
| 110 | # This assumes that the Python interpreter prints such values as |
| 111 | # <foo object at xxxxxxxx>. |
| 112 | # The object will be read back as a string: '<foo object at xxxxxxxx>'. |
| 113 | # In some cases it may be possible to fix the dump manually; |
| 114 | # to ease the editing, these cases are labeled with an XXX comment. |
| 115 | # |
| 116 | def dumpbadvalue(x, typedict, types, stack): |
| 117 | xrepr = `x` |
| 118 | if typedict.has_key(xrepr): |
| 119 | return typedict[xrepr] |
| 120 | uid = genuid() |
| 121 | typedict[xrepr] = uid |
| 122 | print FN, '[', `uid`, '] =', `xrepr`, '# XXX' |
| 123 | return uid |
| 124 | |
| 125 | # Generic function to dump pure, simple values, except strings |
| 126 | # |
| 127 | def dumpvalue(x, typedict, types, stack): |
| 128 | xrepr = `x` |
| 129 | if typedict.has_key(xrepr): |
| 130 | return typedict[xrepr] |
| 131 | uid = genuid() |
| 132 | typedict[xrepr] = uid |
| 133 | print FN, '[', `uid`, '] =', `x` |
| 134 | return uid |
| 135 | |
| 136 | # Functions to dump string objects |
| 137 | # |
| 138 | def dumpstring(x, typedict, types, stack): |
| 139 | # XXX This can break if strings have embedded '\0' bytes |
| 140 | # XXX because of a bug in the dictionary module |
| 141 | if typedict.has_key(x): |
| 142 | return typedict[x] |
| 143 | uid = genuid() |
| 144 | typedict[x] = uid |
| 145 | print FN, '[', `uid`, '] =', `x` |
| 146 | return uid |
| 147 | |
| 148 | # Function to dump type objects |
| 149 | # |
| 150 | typeswitch = {} |
Guido van Rossum | 1820010 | 1991-12-26 13:05:52 +0000 | [diff] [blame] | 151 | class some_class: |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 152 | def method(self): pass |
| 153 | some_instance = some_class() |
| 154 | # |
| 155 | def dumptype(x, typedict, types, stack): |
| 156 | xrepr = `x` |
| 157 | if typedict.has_key(xrepr): |
| 158 | return typedict[xrepr] |
| 159 | uid = genuid() |
| 160 | typedict[xrepr] = uid |
| 161 | if typeswitch.has_key(xrepr): |
| 162 | print FN, '[', `uid`, '] =', typeswitch[xrepr] |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 163 | elif x == type(sys): |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 164 | print 'import sys' |
| 165 | print FN, '[', `uid`, '] = type(sys)' |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 166 | elif x == type(sys.stderr): |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 167 | print 'import sys' |
| 168 | print FN, '[', `uid`, '] = type(sys.stderr)' |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 169 | elif x == type(dumptype): |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 170 | print 'def some_function(): pass' |
| 171 | print FN, '[', `uid`, '] = type(some_function)' |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 172 | elif x == type(some_class): |
Guido van Rossum | d316607 | 1993-05-24 14:16:22 +0000 | [diff] [blame] | 173 | print 'class some_class: pass' |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 174 | print FN, '[', `uid`, '] = type(some_class)' |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 175 | elif x == type(some_instance): |
Guido van Rossum | d316607 | 1993-05-24 14:16:22 +0000 | [diff] [blame] | 176 | print 'class another_class: pass' |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 177 | print 'some_instance = another_class()' |
| 178 | print FN, '[', `uid`, '] = type(some_instance)' |
Guido van Rossum | bdfcfcc | 1992-01-01 19:35:13 +0000 | [diff] [blame] | 179 | elif x == type(some_instance.method): |
Guido van Rossum | d316607 | 1993-05-24 14:16:22 +0000 | [diff] [blame] | 180 | print 'class yet_another_class:' |
Guido van Rossum | 2d844d1 | 1991-04-07 13:41:50 +0000 | [diff] [blame] | 181 | print ' def method(): pass' |
| 182 | print 'another_instance = yet_another_class()' |
| 183 | print FN, '[', `uid`, '] = type(another_instance.method)' |
| 184 | else: |
| 185 | # Unknown type |
| 186 | print FN, '[', `uid`, '] =', `xrepr`, '# XXX' |
| 187 | return uid |
| 188 | |
| 189 | # Initialize the typeswitch |
| 190 | # |
| 191 | for x in None, 0, 0.0, '', (), [], {}: |
| 192 | typeswitch[`type(x)`] = 'type(' + `x` + ')' |
| 193 | for s in 'type(0)', 'abs', '[].append': |
| 194 | typeswitch[`type(eval(s))`] = 'type(' + s + ')' |
| 195 | |
| 196 | # Dump a tuple object |
| 197 | # |
| 198 | def dumptuple(x, typedict, types, stack): |
| 199 | item_uids = [] |
| 200 | xrepr = '' |
| 201 | for item in x: |
| 202 | item_uid = dumpobject(item, types, stack) |
| 203 | item_uids.append(item_uid) |
| 204 | xrepr = xrepr + ' ' + item_uid |
| 205 | del stack[-1:] |
| 206 | if typedict.has_key(xrepr): |
| 207 | return typedict[xrepr] |
| 208 | uid = genuid() |
| 209 | typedict[xrepr] = uid |
| 210 | print FN, '[', `uid`, '] = (', |
| 211 | for item_uid in item_uids: |
| 212 | print FN, '[', `item_uid`, '],', |
| 213 | print ')' |
| 214 | return uid |
| 215 | |
| 216 | # Dump a list object |
| 217 | # |
| 218 | def dumplist(x, typedict, types, stack): |
| 219 | # Check for recursion |
| 220 | for x1, uid1 in stack: |
| 221 | if x is x1: return uid1 |
| 222 | # Check for occurrence elsewhere in the typedict |
| 223 | for uid1 in typedict.keys(): |
| 224 | if x is typedict[uid1]: return uid1 |
| 225 | # This uses typedict differently! |
| 226 | uid = genuid() |
| 227 | typedict[uid] = x |
| 228 | print FN, '[', `uid`, '] = []' |
| 229 | stack.append(x, uid) |
| 230 | item_uids = [] |
| 231 | for item in x: |
| 232 | item_uid = dumpobject(item, types, stack) |
| 233 | item_uids.append(item_uid) |
| 234 | del stack[-1:] |
| 235 | for item_uid in item_uids: |
| 236 | print FN, '[', `uid`, '].append(', FN, '[', `item_uid`, '])' |
| 237 | return uid |
| 238 | |
| 239 | # Dump a dictionary object |
| 240 | # |
| 241 | def dumpdict(x, typedict, types, stack): |
| 242 | # Check for recursion |
| 243 | for x1, uid1 in stack: |
| 244 | if x is x1: return uid1 |
| 245 | # Check for occurrence elsewhere in the typedict |
| 246 | for uid1 in typedict.keys(): |
| 247 | if x is typedict[uid1]: return uid1 |
| 248 | # This uses typedict differently! |
| 249 | uid = genuid() |
| 250 | typedict[uid] = x |
| 251 | print FN, '[', `uid`, '] = {}' |
| 252 | stack.append(x, uid) |
| 253 | item_uids = [] |
| 254 | for key in x.keys(): |
| 255 | val_uid = dumpobject(x[key], types, stack) |
| 256 | item_uids.append(key, val_uid) |
| 257 | del stack[-1:] |
| 258 | for key, val_uid in item_uids: |
| 259 | print FN, '[', `uid`, '][', `key`, '] =', |
| 260 | print FN, '[', `val_uid`, ']' |
| 261 | return uid |
| 262 | |
| 263 | # Dump a module object |
| 264 | # |
| 265 | def dumpmodule(x, typedict, types, stack): |
| 266 | xrepr = `x` |
| 267 | if typedict.has_key(xrepr): |
| 268 | return typedict[xrepr] |
| 269 | from string import split |
| 270 | # `x` has the form <module 'foo'> |
| 271 | name = xrepr[9:-2] |
| 272 | uid = genuid() |
| 273 | typedict[xrepr] = uid |
| 274 | print 'import', name |
| 275 | print FN, '[', `uid`, '] =', name |
| 276 | return uid |
| 277 | |
| 278 | |
| 279 | # Initialize dumpswitch, a table of functions to dump various objects, |
| 280 | # indexed by `type(x)`. |
| 281 | # |
| 282 | for x in None, 0, 0.0: |
| 283 | dumpswitch[`type(x)`] = dumpvalue |
| 284 | for x, f in ('', dumpstring), (type(0), dumptype), ((), dumptuple), \ |
| 285 | ([], dumplist), ({}, dumpdict), (sys, dumpmodule): |
| 286 | dumpswitch[`type(x)`] = f |
| 287 | |
| 288 | |
| 289 | # Generate the next unique id; a string consisting of digits. |
| 290 | # The seed is stored as seed[0]. |
| 291 | # |
| 292 | seed = [0] |
| 293 | # |
| 294 | def genuid(): |
| 295 | x = seed[0] |
| 296 | seed[0] = seed[0] + 1 |
| 297 | return `x` |