Skip to content

Commit 4485bd9

Browse files
fix: make _safe_write_json actually atomic with mkstemp + os.replace
Despite its name, _safe_write_json used write_text() which truncates the file before writing. A crash or power loss mid-write leaves a partial JSON file. Now uses tempfile.mkstemp + os.replace for atomic writes, matching the pattern used in _utils.py, shared_infra.py, and other safe-write utilities in the codebase.
1 parent f8b3d60 commit 4485bd9

1 file changed

Lines changed: 16 additions & 2 deletions

File tree

src/specify_cli/events.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import sys
1818
import subprocess
1919
import platform
20+
import tempfile
2021
from pathlib import Path
2122
from typing import TYPE_CHECKING, Any
2223

@@ -2010,10 +2011,23 @@ def _load_user_json(path: Path) -> dict | None:
20102011

20112012

20122013
def _safe_write_json(dst: Path, data: dict) -> None:
2013-
"""Write *data* as JSON to *dst* after validating the destination (#12)."""
2014+
"""Write *data* as JSON to *dst* atomically after validating the destination (#12)."""
20142015
_ensure_safe_destination(dst)
20152016
dst.parent.mkdir(parents=True, exist_ok=True)
2016-
dst.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
2017+
fd, tmp = tempfile.mkstemp(
2018+
dir=str(dst.parent), prefix=f".{dst.name}.", suffix=".tmp"
2019+
)
2020+
try:
2021+
with os.fdopen(fd, "w", encoding="utf-8") as f:
2022+
json.dump(data, f, indent=2)
2023+
f.write("\n")
2024+
os.replace(tmp, dst)
2025+
except BaseException:
2026+
try:
2027+
os.unlink(tmp)
2028+
except OSError:
2029+
pass
2030+
raise
20172031

20182032

20192033
def _ensure_safe_destination(dst: Path) -> None:

0 commit comments

Comments
 (0)