42 lines
1.2 KiB
Python
42 lines
1.2 KiB
Python
|
|
"""Shared utility functions for hermes-agent."""
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import tempfile
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any, Union
|
||
|
|
|
||
|
|
|
||
|
|
def atomic_json_write(path: Union[str, Path], data: Any, *, indent: int = 2) -> None:
|
||
|
|
"""Write JSON data to a file atomically.
|
||
|
|
|
||
|
|
Uses temp file + fsync + os.replace to ensure the target file is never
|
||
|
|
left in a partially-written state. If the process crashes mid-write,
|
||
|
|
the previous version of the file remains intact.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
path: Target file path (will be created or overwritten).
|
||
|
|
data: JSON-serializable data to write.
|
||
|
|
indent: JSON indentation (default 2).
|
||
|
|
"""
|
||
|
|
path = Path(path)
|
||
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
fd, tmp_path = tempfile.mkstemp(
|
||
|
|
dir=str(path.parent),
|
||
|
|
prefix=f".{path.stem}_",
|
||
|
|
suffix=".tmp",
|
||
|
|
)
|
||
|
|
try:
|
||
|
|
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||
|
|
json.dump(data, f, indent=indent, ensure_ascii=False)
|
||
|
|
f.flush()
|
||
|
|
os.fsync(f.fileno())
|
||
|
|
os.replace(tmp_path, path)
|
||
|
|
except BaseException:
|
||
|
|
try:
|
||
|
|
os.unlink(tmp_path)
|
||
|
|
except OSError:
|
||
|
|
pass
|
||
|
|
raise
|