CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
57 lines
2.3 KiB
Python
57 lines
2.3 KiB
Python
"""write_json - crash-safe JSON writes (R06-T02).
|
|
|
|
``core/projects.py::save_project`` and ``core/history.py``'s
|
|
``save_conversation``/``rename_conversation``/``set_pinned`` all do a plain
|
|
``path.write_text(json.dumps(...))`` today. That is two syscalls with a gap in
|
|
between: a crash, a killed process, or a full disk between the truncate and
|
|
the write leaves a half-written, unparseable JSON file - the NEXT read of
|
|
that project/conversation then fails outright (``load_project`` /
|
|
``load_conversation`` already treat a parse error as "missing", so this isn't
|
|
even a loud failure - a project can silently vanish).
|
|
|
|
``write_json`` fixes this the standard way: write the full content to a
|
|
temporary file in the SAME directory (so the following replace is on one
|
|
filesystem, not crossing a mount point), then atomically rename it over the
|
|
target. Either the old file is still there, or the new one is fully there -
|
|
never a partial one.
|
|
|
|
Transitional note: EPIC R02 (Team Nam, ``docs/refactor/Refactoring_Checklist.md``
|
|
R02-T01) plans a shared ``infrastructure/persistence/json/atomic_json_file.py``
|
|
for the SAME purpose across the whole app (config, secrets, ...). This module
|
|
is deliberately named differently and scoped to R06's two repositories only,
|
|
so the two EPICs don't edit the same file in parallel; once R02-T01 lands,
|
|
``WorkspaceRepository``/``ConversationRepository`` should switch to it and
|
|
this module can go away.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def write_json(path: Path, data: Any) -> None:
|
|
"""Serialize ``data`` as indented UTF-8 JSON and write it to ``path``
|
|
atomically. Creates parent directories if needed."""
|
|
path = Path(path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
text = json.dumps(data, ensure_ascii=False, indent=2)
|
|
fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), prefix=f".{path.name}.", suffix=".tmp")
|
|
try:
|
|
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
handle.write(text)
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
os.replace(tmp_name, path)
|
|
except BaseException:
|
|
try:
|
|
os.unlink(tmp_name)
|
|
except OSError:
|
|
pass
|
|
raise
|
|
|
|
|
|
__all__ = ["write_json"]
|