Source code for httk.core.atomic_write
"""Exception-safe local file replacement for filename-sensitive format writers."""
import os
import stat
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from tempfile import TemporaryDirectory
__all__ = ["atomic_destination"]
@contextmanager
[docs]
def atomic_destination(destination: str | os.PathLike[str]) -> Iterator[Path]:
"""Yield a same-filesystem staging path and publish it only on success.
Follow symlinks, preserve existing permission bits, and retain the requested
basename for filename-sensitive writers. Replacing a hard link leaves its
other links unchanged. This does not provide crash durability or serialize
concurrent writers. The caller must close all output streams before exit.
:param destination: Output filename whose resolved parent directory exists.
:yields: A staging path retaining the requested basename for the writer.
:raises ValueError: If the resolved destination exists and is not a regular file.
:raises OSError: If staging or publishing the output fails.
"""
requested = Path(destination)
target = requested.resolve()
try:
previous = target.stat()
except FileNotFoundError:
mode = None
else:
if not stat.S_ISREG(previous.st_mode):
raise ValueError(f"save destination must be a regular file: {destination}")
mode = stat.S_IMODE(previous.st_mode)
with TemporaryDirectory(prefix=".httk-save-", dir=target.parent) as directory:
staged = Path(directory) / requested.name
yield staged
if mode is not None:
staged.chmod(mode)
os.replace(staged, target)