Author neologix
Recipients barry, eric.araujo, eric.smith, exarkun, giampaolo.rodola, loewis, martin.panter, meatballhat, milko.krachounov, ncoghlan, neologix, olemis, pitrou, tarek, vstinner
Date 2013-05-16.06:44:31
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <CAH_1eM2Aec8A-RB13Tury-LmxjQ33ZfSnd_p1XGCgPRtjbS8Bw@mail.gmail.com>
In-reply-to <1368665698.01.0.829110519369.issue8604@psf.upfronthosting.co.za>
Content
> (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).
History
Date User Action Args
2013-05-16 06:44:31neologixsetrecipients: + neologix, loewis, barry, exarkun, ncoghlan, pitrou, vstinner, eric.smith, giampaolo.rodola, tarek, eric.araujo, olemis, meatballhat, milko.krachounov, martin.panter
2013-05-16 06:44:31neologixlinkissue8604 messages
2013-05-16 06:44:31neologixcreate