44'''Defines various tools related to temporary files.'''
55
66import pathlib
7+ import uuid
78import os
89import re
910
@@ -101,8 +102,8 @@ def create_file_renaming_if_taken(path, mode='x',
101102 N_MAX_ATTEMPTS ):
102103 try :
103104 return path .open (mode , buffering = buffering ,
104- encoding = encoding , errors = errors ,
105- newline = newline )
105+ encoding = encoding , errors = errors ,
106+ newline = newline )
106107 except FileExistsError :
107108 pass
108109 else :
@@ -126,4 +127,59 @@ def write_to_file_renaming_if_taken(path, data, mode='x',
126127 newline = newline ) as file :
127128
128129 return file .write (data )
129-
130+
131+
132+ def atomic_create_and_write (path , data = None , binary = False ):
133+ '''
134+ Write data to file, but use a temporary file as a buffer.
135+
136+ The data you write to this file is actuall written to a temporary file in
137+ the same folder, and only after you close it, without having an exception
138+ raised, it renames the temporary file to your original file name. If an
139+ exception was raised during writing it deletes the temporary file.
140+
141+ This way you're sure you're not getting a half-baked file.
142+ '''
143+ with atomic_create (path , binary = binary ) as file :
144+ return file .write (data )
145+
146+
147+ @context_management .ContextManagerType
148+ def atomic_create (path , binary = False ):
149+ '''
150+ Create a file for writing, but use a temporary file as a buffer.
151+
152+ Use as a context manager:
153+
154+ with atomic_create(path) as my_file:
155+ my_file.write('Whatever')
156+
157+ When you write to this file it actually writes to a temporary file in the
158+ same folder, and only after you close it, without having an exception
159+ raised, it renames the temporary file to your original file name. If an
160+ exception was raised during writing it deletes the temporary file.
161+
162+ This way you're sure you're not getting a half-baked file.
163+ '''
164+ path = pathlib .Path (path )
165+ if path .exists ():
166+ raise Exception ("There's already a file called %s" % path )
167+ desired_temp_file_path = path .parent / ('._%s.tmp' % path .stem )
168+ try :
169+ with create_file_renaming_if_taken (desired_temp_file_path ,
170+ 'xb' if binary else 'x' ) as temp_file :
171+ actual_temp_file_path = pathlib .Path (temp_file .name )
172+ yield temp_file
173+
174+ # This part runs only if there was no exception when writing to the
175+ # file:
176+ if path .exists ():
177+ raise Exception ("There's already a file called %s" % path )
178+ actual_temp_file_path .rename (path )
179+ assert path .exists ()
180+
181+ finally :
182+ if actual_temp_file_path .exists ():
183+ actual_temp_file_path .unlink ()
184+
185+
0 commit comments