| # 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 |
| # <foo object at xxxxxxxx>. |
| # The object will be read back as a string: '<foo object at xxxxxxxx>'. |
| # 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 <module 'foo'> |
| 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` |