Persistent items (that is, Files) are read-only. Writing is handled by a specialization of the Writer class, which mediates modifications of the in-memory structure and the saving of changes to disk in a robust way. Saving is initially done to a temporary file. If any errors occur during saving, the temporary file is discarded and the item is reloaded from the original file. If saving completes successfully, the original file becomes a back-up and the temporary file becomes the new version.
The class BackingSave allows one to write a file in a way that is robust to errors. It directs all writes to a temporary file. If the temporary file is written and closed without error, then the target file is renamed with the suffix .bak (replacing any existing .bak version), and the temporary file is renamed to the target. However, if an error occurs during writing, the original file is left unchanged.
An example of use:
with BackingSave('myfile') as f: f.write('Test 1 2 3\n')
A Writer is used as a host for side-effecting operations. The object wrapped by a writer is accessible as the member file. The file should have a writer() method that creates an instance of the appropriate specialization of Writer. The writer should always be created within a with clause. For example:
with mydict.writer() as w: w['foo'] = 42 w['bar'] = 'hi'
Here mydict is assumed to be of class Dict, and its writer() method returns a DictWriter, which provides the implementation of __setitem__(). Multiple calls may be placed to side-effecting methods within the body of the with. At the end, a single save is done.
The base Writer class provides the __enter__() and __exit__() methods. The initializer of Writer sets the writer's file member to the associated file object. The __enter__() method initializes modified to False and sets the file's _writer member. But it first checks to make sure that the the file is not already being written by a different writer instance, signalling an error if so.
On __exit__(), the file is written to disk, provided that modified is True. The actual writing is done by the writer's close() method, which should be provided by specializations of Writer. If __exit__() is called because of an exception, close() is not called; rather, the file's contents are reloaded, discarding all changes.
The only difference between a FileWriter and a Writer is that a FileWriter checks whether the user has write permission. The check is done during __enter__().
Writer assumes that its file provides the following. (The assumptions will be satisfied if the file is an instance of File.)
def writer (self): return DictWriter(self)
Specializations of Writer are responsible for actual changes to the file. Here is the definition of DictWriter:
class DictWriter (FileWriter): def __setitem__ (self, key, value): self.modified = True d = self.file.contents() d[key] = value def close (self): file = self.file with BackingSave(file.filename()) as f: for (k,v) in file.contents().items(): f.write(k) f.write('\t') f.write(v) f.write('\n')
The following are particular points to note:
In some cases, to edit an item x we will need to simultaneously edit another item y. There are currently two examples: to edit a TokenFile, one needs to have a lexicon writer (for interning tokens), and to edit a Transcription, one needs to edit the clips file and token file that it contains. The secondary writers are called slaves. Entering the primary writer causes an __enter__() call to be sent to the slaves, and exiting causes an __exit__() call to be sent to the slaves. (The master is exited first; this allows the master to set up conditions for the slaves.)
The method slave() attaches one writer as slave to another. For example, here is the __init__() method of the transcription writer:
def __init__ (self, file): StructureWriter.__init__(self, file) self.clips_writer = file.clips.writer() self.slave(self.clips_writer) self.transcript_writer = file.transcript.writer() self.slave(self.transcript_writer)
One should not explicitly close the slaves; their close() methods will be called on exit.