Spaces:
Sleeping
Sleeping
| """Provide locked JSON/JSONL persistence and atomic file replacement. | |
| Mutations use a sibling lock file so append and full rewrites cannot overlap. | |
| """ | |
| import json | |
| import os | |
| from contextlib import contextmanager | |
| from pathlib import Path | |
| from typing import Any, Iterator | |
| def file_lock(path: Path) -> Iterator[None]: | |
| """Hold an exclusive process lock for a data path.""" | |
| lock_path = path.with_name(f"{path.name}.lock") | |
| lock_path.parent.mkdir(parents=True, exist_ok=True) | |
| with lock_path.open("a+b") as handle: | |
| if os.name == "nt": | |
| import msvcrt | |
| if handle.tell() == 0: | |
| handle.write(b"\0") | |
| handle.flush() | |
| handle.seek(0) | |
| msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1) | |
| else: | |
| import fcntl | |
| fcntl.flock(handle.fileno(), fcntl.LOCK_EX) | |
| try: | |
| yield | |
| finally: | |
| if os.name == "nt": | |
| handle.seek(0) | |
| msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1) | |
| else: | |
| fcntl.flock(handle.fileno(), fcntl.LOCK_UN) | |
| def atomic_write_text(path: Path, text: str) -> None: | |
| """Replace a file atomically after flushing its contents to disk.""" | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temporary = path.with_name(f"{path.name}.tmp") | |
| with temporary.open("w", encoding="utf-8", newline="\n") as handle: | |
| handle.write(text) | |
| handle.flush() | |
| os.fsync(handle.fileno()) | |
| os.replace(temporary, path) | |
| def read_json(path: Path) -> dict[str, Any] | None: | |
| """Read a JSON object, returning None when the file is absent.""" | |
| if not path.exists(): | |
| return None | |
| with path.open("r", encoding="utf-8") as handle: | |
| value = json.load(handle) | |
| if not isinstance(value, dict): | |
| raise ValueError(f"{path.name} must contain a JSON object") | |
| return value | |
| def write_json(path: Path, value: dict[str, Any]) -> None: | |
| """Write a JSON object under a lock using atomic replacement.""" | |
| payload = json.dumps(value, ensure_ascii=False, indent=2) + "\n" | |
| with file_lock(path): | |
| atomic_write_text(path, payload) | |
| def read_jsonl(path: Path) -> list[dict[str, Any]]: | |
| """Read non-empty JSONL records in file order.""" | |
| if not path.exists(): | |
| return [] | |
| records: list[dict[str, Any]] = [] | |
| with path.open("r", encoding="utf-8") as handle: | |
| for line in handle: | |
| if line.strip(): | |
| value = json.loads(line) | |
| if not isinstance(value, dict): | |
| raise ValueError(f"{path.name} contains a non-object record") | |
| records.append(value) | |
| return records | |
| def append_jsonl(path: Path, value: dict[str, Any]) -> None: | |
| """Append one durable JSONL record while holding the file lock.""" | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| line = json.dumps(value, ensure_ascii=False, separators=(",", ":")) + "\n" | |
| with file_lock(path): | |
| with path.open("a", encoding="utf-8", newline="\n") as handle: | |
| handle.write(line) | |
| handle.flush() | |
| os.fsync(handle.fileno()) | |
| def rewrite_jsonl(path: Path, values: list[dict[str, Any]]) -> None: | |
| """Atomically replace a JSONL file while holding its lock.""" | |
| text = "".join( | |
| json.dumps(value, ensure_ascii=False, separators=(",", ":")) + "\n" | |
| for value in values | |
| ) | |
| with file_lock(path): | |
| atomic_write_text(path, text) | |