Files
cowork-local/infrastructure/persistence/json/atomic_write.py
T
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## 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>
2026-08-31 05:15:13 +00:00

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"]