[issue8604] Adding an atomic FS write API

Charles-François Natali report at bugs.python.org
Thu May 16 08:44:31 CEST 2013


Charles-François Natali added the comment:

> (Note that the Beaker version would need to be enhanced with the extra API parameters from Victor's version, as well as updated to use the exclusive open and close-on-exec flags)

I think the API would be nicer if it was just a wrapper around the
underlying temporary file object, delegating all methods except
close() (sort of like the _TemporaryFileWrapper in tempfile).

Something like (untested, not even foolproof-read) :

class AtomicFile:

    def __init__(self, path):
        self.dest_path = path
        dirname, basename = os.path.split(self.dest_path)
        fd, temp_path = tempfile.mkstemp(prefix='.' + basename, dir=dirname)
        try:
            self.f = os.fdopen(fd, 'w')
        except:
            os.unlink(temp_path)
            raise
        self.temp_path = temp_path

    def close(self):
        self.f.close()
        os.rename(self.temp_path, self.dest_path)

    # copied from tempfile
    def __getattr__(self, name):
        # Attribute lookups are delegated to the underlying file
        # and cached for non-numeric results
        # (i.e. methods are cached, closed and friends are not)
        file = self.__dict__['file']
        a = getattr(file, name)
        if not issubclass(type(a), type(0)):
        setattr(self, name, a)
        return a

    def __enter__self():
        self.file.__enter__()
        return f

    def __exit__(self, exc, value, tb):
       if exc is None:
           self.close()
       else:
           self.file.close()
           os.remove(self.temp_path)

This way, one just has to do:
f = AtomicFile(path)
and then use it as a normal file; it will automatically be committed
(or rollback) when the file is closed, either because you're leaving
the context manager, or because an explicit() close is called.
It also makes it easier to use with legacy code/libraries accepting an
open file (since no modification is needed), is less error-prone
(since you don't have to remember to call special methods like
destroy_temp/replace_dest), and leads to a more compact API (exactly
the same as regular files).

Otherwise, I think the calls to fsync() should be optional (maybe an
option passed to the constructor): most of the time, you want
atomicity but not durability (i.e. you don't really care if data is
committed to disk), and you don't want to pay for the performance hit
incurred by fsync().

Also, fsync() should also be done on the containing directory (which
is not the case in Victor's version).

----------

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue8604>
_______________________________________


More information about the Python-bugs-list mailing list