@@ -310,12 +310,13 @@ def nullcontext(enter_result: T = None) -> Iterator[T]:
310
310
yield enter_result
311
311
312
312
313
+ def _tmp_file_prefix () -> str :
314
+ return f'{ constants .INCOMPLETE_PREFIX } { uuid .uuid4 ().hex } '
315
+
316
+
313
317
def _tmp_file_name (path : epath .PathLike ) -> epath .Path :
314
318
path = epath .Path (path )
315
- return (
316
- path .parent
317
- / f'{ constants .INCOMPLETE_PREFIX } { uuid .uuid4 ().hex } .{ path .name } '
318
- )
319
+ return path .parent / f'{ _tmp_file_prefix ()} .{ path .name } '
319
320
320
321
321
322
@contextlib .contextmanager
@@ -332,6 +333,25 @@ def incomplete_file(
332
333
tmp_path .unlink (missing_ok = True )
333
334
334
335
336
+ @contextlib .contextmanager
337
+ def incomplete_files (
338
+ path : epath .Path ,
339
+ ) -> Iterator [epath .Path ]:
340
+ """Writes to path atomically, by writing to temp file and renaming it."""
341
+ tmp_file_prefix = _tmp_file_prefix ()
342
+ tmp_path = path .parent / f'{ tmp_file_prefix } .{ path .name } '
343
+ try :
344
+ yield tmp_path
345
+ # Rename all tmp files to their final name.
346
+ for tmp_file in path .parent .glob (f'{ tmp_file_prefix } .*' ):
347
+ file_name = tmp_file .name .removeprefix (tmp_file_prefix + '.' )
348
+ tmp_file .replace (path .parent / file_name )
349
+ finally :
350
+ # Eventually delete the tmp_path if exception was raised
351
+ for tmp_file in path .parent .glob (f'{ tmp_file_prefix } .*' ):
352
+ tmp_file .unlink (missing_ok = True )
353
+
354
+
335
355
def is_incomplete_file (path : epath .Path ) -> bool :
336
356
"""Returns whether the given filename suggests that it's incomplete."""
337
357
return bool (
0 commit comments