A same-directory temporary file can be flushed and replaced without exposing a partially written target through its pathname.
Python file replacement: write a sibling file before changing the name
Operation contract
A receipt index is written to a sibling temporary file. The writer flushes Python buffers, asks the OS to flush the file, closes it, then uses os.replace on the target path. A reader opening the target before or after replacement sees a whole old or new file on filesystems that honor the operation's atomic rename semantics; an already open reader may keep the old inode.
Failure and ownership boundary
Atomic replacement is a visibility contract, not a universal crash-durability promise. Durable directory-entry persistence may require a directory fsync on supported POSIX filesystems; Windows and network filesystems differ. Validate the destination and permissions, clean a failed temporary file, and coordinate concurrent writers if the last writer must not silently win.
Working program
import os
import pathlib
import tempfile
with tempfile.TemporaryDirectory() as directory:
target = pathlib.Path(directory) / "receipt-index.txt"
target.write_text("revision 46\n")
with tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", dir=directory,
prefix=".receipt-index-", delete=False,
) as draft:
draft.write("revision 47\n")
draft.flush()
os.fsync(draft.fileno())
draft_name = draft.name
try:
os.replace(draft_name, target)
finally:
if os.path.exists(draft_name):
os.unlink(draft_name)
print(target.read_text().strip())
print("target_exists", target.exists())Output
revision 47
target_exists TrueCosts and limits
The write is O(bytes) and uses a second file until replacement. File fsync can add storage latency; the example does not time it or prove power-loss survival.
Common Mistakes
- Writing directly to the target can expose a partial file.
- File fsync and atomic rename are distinct from directory durability.
- Last-writer-wins replacement is not a concurrency control policy.
