A class that enables a client to securely update an existing file, including the ability to make an automated backup version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | 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()
|
This code encapsulates the method to safely update files in-place, by first writing to a temporary file and finally renaming the new temporary file to the old filename. This also make it possible to create a backup copy of the original file.
The "FileMorpher" class encapsulates all the necessary steps, including the filename handling, which makes the calling code much cleaner as opposed to the naive approach where all the gory details are spread over that higher-level code.
os.mkstemp(). A well coded recipe. I find only one drawback: Using [filename].tmp as temporary filename might pose a security risk. Use os.mkstemp() instead to get rid of the risk.