Atomic file save -- code review and comments requested

Oscar Benjamin oscar.j.benjamin at gmail.com
Wed Sep 2 15:12:45 EDT 2015


On Wed, 2 Sep 2015 19:06 Steven D'Aprano <steve at pearwood.info> wrote:

Howdy,

I have a utility function for performing atomic file saves, and I'd like to
ask for a code review and comments.

I have published this on ActiveState:

https://code.activestate.com/recipes/579097-safely-and-atomically-write-to-a-file/

under an MIT licence. You should read the version there, I discuss the
use-case for the function and include an extensive doc string. Feel free to
comment either here or on the ActiveState site.

Here is the function, minus the docstring (for brevity):

import contextlib
import os
import stat
import tempfile

@contextlib.contextmanager
def atomic_write(filename, text=True, keep=True,
                 owner=None, group=None, perms=None,
                 suffix='.bak', prefix='tmp'):
    t = (uid, gid, mod) = (owner, group, perms)
    if any(x is None for x in t):
        info = os.stat(filename)
        if uid is None:
            uid = info.st_uid
        if gid is None:
            gid = info.st_gid
        if mod is None:
            mod = stat.S_IMODE(info.st_mode)
    path = os.path.dirname(filename)
    fd, tmp = tempfile.mkstemp(
                  suffix=suffix, prefix=prefix, dir=path, text=text)
    try:
        with os.fdopen(fd, 'w' if text else 'wb') as f:
            yield f
        os.rename(tmp, filename)
        tmp = None
        os.chown(filename, uid, gid)
        os.chmod(filename, mod)
    finally:
        if (tmp is not None) and (not keep):
            # Silently delete the temporary file. Ignore any errors.
            try:
                os.unlink(tmp)
            except:
                pass


Usage is:

with atomic_write("mydata.txt") as f:
    f.write("some data")
    # if an error occurs in here, mydata.txt is preserved

# if no error occurs and the with-block exits cleanly,
# mydata.txt is atomically overwritten with the new contents.

The function is written for Python 2.6, but should work on 2.7 as well.

I'm looking for a review of the code, and any general comments. In
particular, have I missed any ways that the function may fail and lose
data?

One question comes to mind -- should I perform a flush and/or sync of the
file before the rename?



Your with statement will close the file so that shouldn't be necessary.

Not an expert on these things but maybe it makes sense to call chown/chmod
before the rename so that a failure can't result in the replaced file's
permissions being changed.

--
Oscar
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-list/attachments/20150902/3ce163f8/attachment.html>


More information about the Python-list mailing list