"""Simple transactional file writing. Providedes a file-like class that doesn't write any changes directly to the original file, but to a temporary copy instead. Only after a requested commit action, the original file is replaced with the new contents. A rollback simply leaves the original file untouched. Note that this class is NOT threadsafe! (c) Irmen de Jong - irmen@razorvine.net Software License: MIT (http://www.opensource.org/licenses/mit-license.php) I.e. use it freely, but include the above copyright notice and this license text. Supplied as-is. No warranties. """ __version__="1.0" import os class TransactionalFile(file): """This class attempts to be a file object with transactional properties. Writing initially goes to a temporary file instead of the target file directly. When you're done, you don't close() this file, but you commit() or rollback() the file. A commit() replaces the old file with the new file (using renaming, so should be quite safe and fast). When something happens that causes the commit not to be executed, such as an error, the file is automatically rollback-ed, and the original contents remain untouched. NOTE: this is NOT thread-safe!! Don't write the same file from within multiple threads, that will not work!""" def __init__(self, name, mode='wb', buffering=1): if mode not in ('w','wb'): raise ValueError("only accepting write mode") self.__name=name self.__tempname=name+"~" file.__init__(self, self.__tempname, mode, buffering) def __del__(self): self.rollback() def getName(self): return self.__name name=property(getName,None,None) def close(self): raise AttributeError("TransactionalFile must be closed using rollback() or commit()") # file.close(self) def rollback(self): if not self.closed: file.close(self) try: os.remove(self.__tempname) except EnvironmentError: pass def commit(self): if not self.closed: file.close(self) backup=self.__name+"@" if os.path.isfile(self.__name): try: os.rename(self.__name, backup) except EnvironmentError,x: os.remove(self.__tempname) raise else: backup=None try: os.rename(self.__tempname, self.__name) except EnvironmentError,x: os.rename(backup, self.__name) raise else: if backup: os.remove(backup) def test(): filename="/tmp/somefile.txt" content="The quick brown fox jumps over the lazy dog." print "Writing to file",filename r=TransactionalFile(filename,"wb") r.write(content+'\n') print "committing file." r.commit() print "done." print "Writing to file",filename r=TransactionalFile(filename,"wb") r.write("Scratch that!!!!\n") print "rollback file." r.rollback() print "The file should still contain the original content," print "'%s'." % content if __name__=="__main__": test()