import os, string def replaceFile(oldname, newname): """ Rename file 'oldname' to 'newname'. """ if os.name == 'nt' and os.path.exists(oldname): # POSIX rename does an atomic replace, WIN32 rename does not. :-( try: os.remove(newname) except OSError, exc: import errno if exc.errno != errno.ENOENT: raise exc # rename it os.rename(oldname, newname) class FileMorpher: """ A class that enables a client to securely update an existing file, including the ability to make an automated backup version. """ def __init__(self, filename, **kw): """ The constructor takes the filename and some options. backup -- boolean indicating whether you want a backup file (default is yes) """ self.filename = filename self.do_backup = kw.get('backup', 1) self.stream = None self.basename, ext = os.path.splitext(self.filename) def __del__(self): if self.stream: # Remove open temp file self.__close() os.remove(self.__tempfile()) def __tempfile(self): return self.basename + ".tmp" def __close(self): """ Close temp stream, if open. """ if self.stream: self.stream.close() self.stream = None def load(self): """ Load the content of the original file into a string and return it. All I/O exceptions are passed through. """ file = open(self.filename, "rt") try: content = file.read() finally: file.close() return content def save(self, content): """ Save new content, using a temporary file. """ file = self.opentemp() file.write(content) self.commit() def opentemp(self): """ Open a temporary file for writing and return an open stream. """ assert not self.stream, "Write stream already open" self.stream = open(self.__tempfile(), "wt") return self.stream def commit(self): """ Close the open temp stream and replace the original file, optionally making a backup copy. """ assert self.stream, "Write stream not open" # close temp file self.__close() # do optional backup and rename temp file to the correct name if self.do_backup: replaceFile(self.filename, self.basename + ".bak") replaceFile(self.__tempfile(), self.filename) if __name__ == "__main__": # prepare test file test = open('foo.txt', 'wt') test.write('\txxx\nline 2\n\t\t2 tabs\n') test.close() # load the file's content file = FileMorpher('foo.txt') text = file.load() # detab it, this could also be done shorter by file.save() stream = file.opentemp() stream.write(string.expandtabs(text)) file.commit()