"""Atomic writes for JSON settings shared by GUI and pipeline processes.""" from __future__ import annotations import json import os from contextlib import suppress from pathlib import Path import tempfile def atomic_write_json(path: Path, payload: object, *, durable: bool = False) -> Path: """Serialize strict JSON before opening any files, then atomically commit it.""" return atomic_write_text(path, json.dumps(payload, indent=2, allow_nan=False), durable=durable) def atomic_write_text(path: Path, text: str, *, durable: bool = False) -> Path: """Replace a text file only after a complete write, cleaning up failed writes. Each call owns its temporary file, so concurrent writers cannot truncate or rename one another's work. Durable writes also sync the file and directory; frequent live settings can omit those syncs to avoid unnecessary disk I/O. """ path.parent.mkdir(parents=True, exist_ok=True) temporary = None try: with tempfile.NamedTemporaryFile( mode="w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", suffix=".tmp", delete=False, ) as handle: temporary = Path(handle.name) handle.write(text) if durable: handle.flush() os.fsync(handle.fileno()) os.replace(temporary, path) if durable: directory_fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) try: os.fsync(directory_fd) finally: os.close(directory_fd) finally: if temporary is not None: with suppress(OSError): temporary.unlink(missing_ok=True) return path