# persist.py # # Implement limited persistence. # # Simple interface: # persist.save() save __main__ module on file (overwrite) # persist.load() load __main__ module from file (merge) # # These use the filename persist.defaultfile, initialized to 'wsrestore.py'. # # A raw interface also exists: # persist.writedict(dict, fp) save dictionary to open file # persist.readdict(dict, fp) read (merge) dictionary from open file # # Internally, the function dump() and a whole bunch of support of functions # traverse a graph of objects and print them in a restorable form # (which happens to be a Python module). # # XXX Limitations: # - Volatile objects are dumped as strings: # - open files, windows etc. # - Other 'obscure' objects are dumped as strings: # - classes, instances and methods # - compiled regular expressions # - anything else reasonably obscure (e.g., capabilities) # - type objects for obscure objects # - It's slow when there are many of lists or dictionaries # (This could be fixed if there were a quick way to compute a hash # function of any object, even if recursive) defaultfile = 'wsrestore.py' def save(): import __main__ import os # XXX On SYSV, if len(defaultfile) >= 14, this is wrong! backup = defaultfile + '~' try: os.unlink(backup) except os.error: pass try: os.rename(defaultfile, backup) except os.error: pass fp = open(defaultfile, 'w') writedict(__main__.__dict__, fp) fp.close() def load(): import __main__ fp = open(defaultfile, 'r') readdict(__main__.__dict__, fp) def writedict(dict, fp): import sys savestdout = sys.stdout try: sys.stdout = fp dump(dict) # Writes to sys.stdout finally: sys.stdout = savestdout def readdict(dict, fp): contents = fp.read() globals = {} exec(contents, globals) top = globals['top'] for key in top.keys(): if dict.has_key(key): print 'warning:', key, 'not overwritten' else: dict[key] = top[key] # Function dump(x) prints (on sys.stdout!) a sequence of Python statements # that, when executed in an empty environment, will reconstruct the # contents of an arbitrary dictionary. import sys # Name used for objects dict on output. # FUNNYNAME = FN = 'A' # Top-level function. Call with the object you want to dump. # def dump(x): types = {} stack = [] # Used by test for recursive objects print FN, '= {}' topuid = dumpobject(x, types, stack) print 'top =', FN, '[', `topuid`, ']' # Generic function to dump any object. # dumpswitch = {} # def dumpobject(x, types, stack): typerepr = `type(x)` if not types.has_key(typerepr): types[typerepr] = {} typedict = types[typerepr] if dumpswitch.has_key(typerepr): return dumpswitch[typerepr](x, typedict, types, stack) else: return dumpbadvalue(x, typedict, types, stack) # Generic function to dump unknown values. # This assumes that the Python interpreter prints such values as # . # The object will be read back as a string: ''. # In some cases it may be possible to fix the dump manually; # to ease the editing, these cases are labeled with an XXX comment. # def dumpbadvalue(x, typedict, types, stack): xrepr = `x` if typedict.has_key(xrepr): return typedict[xrepr] uid = genuid() typedict[xrepr] = uid print FN, '[', `uid`, '] =', `xrepr`, '# XXX' return uid # Generic function to dump pure, simple values, except strings # def dumpvalue(x, typedict, types, stack): xrepr = `x` if typedict.has_key(xrepr): return typedict[xrepr] uid = genuid() typedict[xrepr] = uid print FN, '[', `uid`, '] =', `x` return uid # Functions to dump string objects # def dumpstring(x, typedict, types, stack): # XXX This can break if strings have embedded '\0' bytes # XXX because of a bug in the dictionary module if typedict.has_key(x): return typedict[x] uid = genuid() typedict[x] = uid print FN, '[', `uid`, '] =', `x` return uid # Function to dump type objects # typeswitch = {} class some_class: def method(self): pass some_instance = some_class() # def dumptype(x, typedict, types, stack): xrepr = `x` if typedict.has_key(xrepr): return typedict[xrepr] uid = genuid() typedict[xrepr] = uid if typeswitch.has_key(xrepr): print FN, '[', `uid`, '] =', typeswitch[xrepr] elif x == type(sys): print 'import sys' print FN, '[', `uid`, '] = type(sys)' elif x == type(sys.stderr): print 'import sys' print FN, '[', `uid`, '] = type(sys.stderr)' elif x == type(dumptype): print 'def some_function(): pass' print FN, '[', `uid`, '] = type(some_function)' elif x == type(some_class): print 'class some_class: pass' print FN, '[', `uid`, '] = type(some_class)' elif x == type(some_instance): print 'class another_class: pass' print 'some_instance = another_class()' print FN, '[', `uid`, '] = type(some_instance)' elif x == type(some_instance.method): print 'class yet_another_class:' print ' def method(): pass' print 'another_instance = yet_another_class()' print FN, '[', `uid`, '] = type(another_instance.method)' else: # Unknown type print FN, '[', `uid`, '] =', `xrepr`, '# XXX' return uid # Initialize the typeswitch # for x in None, 0, 0.0, '', (), [], {}: typeswitch[`type(x)`] = 'type(' + `x` + ')' for s in 'type(0)', 'abs', '[].append': typeswitch[`type(eval(s))`] = 'type(' + s + ')' # Dump a tuple object # def dumptuple(x, typedict, types, stack): item_uids = [] xrepr = '' for item in x: item_uid = dumpobject(item, types, stack) item_uids.append(item_uid) xrepr = xrepr + ' ' + item_uid del stack[-1:] if typedict.has_key(xrepr): return typedict[xrepr] uid = genuid() typedict[xrepr] = uid print FN, '[', `uid`, '] = (', for item_uid in item_uids: print FN, '[', `item_uid`, '],', print ')' return uid # Dump a list object # def dumplist(x, typedict, types, stack): # Check for recursion for x1, uid1 in stack: if x is x1: return uid1 # Check for occurrence elsewhere in the typedict for uid1 in typedict.keys(): if x is typedict[uid1]: return uid1 # This uses typedict differently! uid = genuid() typedict[uid] = x print FN, '[', `uid`, '] = []' stack.append(x, uid) item_uids = [] for item in x: item_uid = dumpobject(item, types, stack) item_uids.append(item_uid) del stack[-1:] for item_uid in item_uids: print FN, '[', `uid`, '].append(', FN, '[', `item_uid`, '])' return uid # Dump a dictionary object # def dumpdict(x, typedict, types, stack): # Check for recursion for x1, uid1 in stack: if x is x1: return uid1 # Check for occurrence elsewhere in the typedict for uid1 in typedict.keys(): if x is typedict[uid1]: return uid1 # This uses typedict differently! uid = genuid() typedict[uid] = x print FN, '[', `uid`, '] = {}' stack.append(x, uid) item_uids = [] for key in x.keys(): val_uid = dumpobject(x[key], types, stack) item_uids.append(key, val_uid) del stack[-1:] for key, val_uid in item_uids: print FN, '[', `uid`, '][', `key`, '] =', print FN, '[', `val_uid`, ']' return uid # Dump a module object # def dumpmodule(x, typedict, types, stack): xrepr = `x` if typedict.has_key(xrepr): return typedict[xrepr] from string import split # `x` has the form name = xrepr[9:-2] uid = genuid() typedict[xrepr] = uid print 'import', name print FN, '[', `uid`, '] =', name return uid # Initialize dumpswitch, a table of functions to dump various objects, # indexed by `type(x)`. # for x in None, 0, 0.0: dumpswitch[`type(x)`] = dumpvalue for x, f in ('', dumpstring), (type(0), dumptype), ((), dumptuple), \ ([], dumplist), ({}, dumpdict), (sys, dumpmodule): dumpswitch[`type(x)`] = f # Generate the next unique id; a string consisting of digits. # The seed is stored as seed[0]. # seed = [0] # def genuid(): x = seed[0] seed[0] = seed[0] + 1 return `x`