On Wed, 2 Sep 2015 19:06 Steven D'Aprano <st...@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
-- 
https://mail.python.org/mailman/listinfo/python-list

Reply via email to