<p dir="ltr"><br>
</p>
<p dir="ltr"></p>
<p dir="ltr">On Wed, 2 Sep 2015 19:06 Steven D'Aprano <<a href="mailto:steve@pearwood.info">steve@pearwood.info</a>> wrote:</p>
<blockquote><p dir="ltr">Howdy,</p>
<p dir="ltr">I have a utility function for performing atomic file saves, and I'd like to<br>
ask for a code review and comments.</p>
<p dir="ltr">I have published this on ActiveState:</p>
<p dir="ltr"><a href="https://code.activestate.com/recipes/579097-safely-and-atomically-write-to-a-file/">https://code.activestate.com/recipes/579097-safely-and-atomically-write-to-a-file/</a></p>
<p dir="ltr">under an MIT licence. You should read the version there, I discuss the<br>
use-case for the function and include an extensive doc string. Feel free to<br>
comment either here or on the ActiveState site.</p>
<p dir="ltr">Here is the function, minus the docstring (for brevity):<br></p>
<p dir="ltr">import contextlib<br>
import os<br>
import stat<br>
import tempfile</p>
<p dir="ltr">@contextlib.contextmanager<br>
def atomic_write(filename, text=True, keep=True,<br>
                 owner=None, group=None, perms=None,<br>
                 suffix='.bak', prefix='tmp'):<br>
    t = (uid, gid, mod) = (owner, group, perms)<br>
    if any(x is None for x in t):<br>
        info = os.stat(filename)<br>
        if uid is None:<br>
            uid = info.st_uid<br>
        if gid is None:<br>
            gid = info.st_gid<br>
        if mod is None:<br>
            mod = stat.S_IMODE(info.st_mode)<br>
    path = os.path.dirname(filename)<br>
    fd, tmp = tempfile.mkstemp(<br>
                  suffix=suffix, prefix=prefix, dir=path, text=text)<br>
    try:<br>
        with os.fdopen(fd, 'w' if text else 'wb') as f:<br>
            yield f<br>
        os.rename(tmp, filename)<br>
        tmp = None<br>
        os.chown(filename, uid, gid)<br>
        os.chmod(filename, mod)<br>
    finally:<br>
        if (tmp is not None) and (not keep):<br>
            # Silently delete the temporary file. Ignore any errors.<br>
            try:<br>
                os.unlink(tmp)<br>
            except:<br>
                pass<br><br><br></p>
<p dir="ltr">Usage is:</p>
<p dir="ltr">with atomic_write("mydata.txt") as f:<br>
    f.write("some data")<br>
    # if an error occurs in here, mydata.txt is preserved</p>
<p dir="ltr"># if no error occurs and the with-block exits cleanly,<br>
# mydata.txt is atomically overwritten with the new contents.<br><br></p>
<p dir="ltr">The function is written for Python 2.6, but should work on 2.7 as well.</p>
<p dir="ltr">I'm looking for a review of the code, and any general comments. In<br>
particular, have I missed any ways that the function may fail and lose<br>
data?</p>
<p dir="ltr">One question comes to mind -- should I perform a flush and/or sync of the<br>
file before the rename?</p>
</blockquote>
<blockquote><p dir="ltr"><br>
</p>
</blockquote>
<p dir="ltr"><br>
Your with statement will close the file so that shouldn't be necessary. </p>
<p dir="ltr">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.</p>
<p dir="ltr">--<br>
Oscar </p>
<p dir="ltr"><br>
</p>