feat(826-2): M01 기계 = 한 대 한 파일 · M01 저장 길 줄이기
- 기계.json 한 파일 → 폴더 기계/(EQ 키마다 파일 + _머리.json · 차례 = 키 차례) · 옮긴 빌더 build_기계폴더_826_2 (655 대 머리 · 줄 · 차례 대조 뒤 옛 파일 지움) - 읽기 도우미 master_machine_files — cm.load · Store_Cache · Store.read 가 옛 「기계.json」 한 표 꼴로 지어 줌(API · 화면 · 색인 그대로) · 쓰기 = 바뀐 대 파일만 - 기계.json 직접 열던 곳 도우미로: M02_San_Basis_Index · M02_Logic_Standard 지문 · build_용도 · master_keys.adopt · _틀 · _갱신절차 - 저장 몸 M01_MasterData_Store_Save — 전 = 기억 · 후 = 고친 파일만 얕은 사본 · 틀은 고친 줄만 · 조합 · 일위대가 문서는 로직 줄이 빠질 때만 · 재료 찾기 기억 이어받음 · 저장 뒤 쓴 파일만 기억에 갈아 끼움 - 마스터 사본 시험 22 개 = 기계 폴더도 복사 · 821_2 · 815_2 글 대조 = 대 파일 하나 대조(옛 한 파일 글 대조 낡음) - 새 시험 test_826_2 — 폴더 = 한 표 · 한 대 한 파일 · 새 · 지움 · 409 · 옛 길과 같은 오류 스무 견본 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ import master_text as mtx # noqa: E402
|
||||
from M01_MasterData import M01_MasterData_Store_Cache as cache # noqa: E402
|
||||
from M01_MasterData.M01_MasterData_Store_Find import hit as _hit # noqa: E402
|
||||
from M01_MasterData.M01_MasterData_Store_Find import squash as _squash # noqa: E402
|
||||
from M01_MasterData.M01_MasterData_Store_Shape import dump, keep_shape # noqa: E402
|
||||
from M01_MasterData.M01_MasterData_Store_Shape import dump # noqa: E402,F401
|
||||
|
||||
_COPY = os.environ.get("AISLO_MASTER_DIR") # 화면 저장 시험 사본 폴더(정함 13) · 없으면 정본
|
||||
FOLDER: Path = Path(_COPY).resolve() if _COPY else cm.MASTER # 시험은 사본 폴더로 바꿈
|
||||
@@ -47,7 +47,8 @@ class StoreError(Exception):
|
||||
# ── 읽기 ──────────────────────────────────────────────────────────────
|
||||
def _path(file: str) -> Path:
|
||||
path = FOLDER / file
|
||||
if "/" in file or "\\" in file or file.startswith("_") or not path.is_file():
|
||||
there = path.is_file() or (file == cm.mmf.NAME and cm.mmf.here(FOLDER)) # 기계 = 폴더(826-2)
|
||||
if "/" in file or "\\" in file or file.startswith("_") or not there:
|
||||
raise StoreError(404, f"없는 파일 「{file}」")
|
||||
return path
|
||||
|
||||
@@ -57,10 +58,19 @@ def version_of(raw: bytes) -> str:
|
||||
|
||||
|
||||
def read(file: str) -> tuple[dict, str]:
|
||||
raw = _path(file).read_bytes()
|
||||
_path(file)
|
||||
if file == cm.mmf.NAME:
|
||||
return cm.mmf.table(FOLDER)
|
||||
raw = (FOLDER / file).read_bytes()
|
||||
return json.loads(raw, parse_float=Decimal, parse_int=Decimal), version_of(raw)
|
||||
|
||||
|
||||
def version(file: str) -> str:
|
||||
"""판만(읽어 풀지 않음 — 저장 409 확인)."""
|
||||
_path(file)
|
||||
return version_of(cm.mmf.raw(FOLDER, file))
|
||||
|
||||
|
||||
# ── 한 번 읽어 두기(`_Store_Cache`) ────────────────────────────────────
|
||||
def loaded() -> tuple[dict[str, dict], object]:
|
||||
"""(파일 묶음, 색인) — **읽기 전용**. 고칠 것이면 `cm.load` 로 새로 읽을 것."""
|
||||
@@ -109,13 +119,15 @@ def files_of(group: str) -> list[dict]:
|
||||
if group not in cm.mf.GROUPS:
|
||||
raise StoreError(404, f"없는 그룹 「{group}」")
|
||||
out = []
|
||||
for path in [*FOLDER.glob(f"{group}.json"), *FOLDER.glob(f"{group}_*.json")]:
|
||||
data, version = read(path.name)
|
||||
for name in cm.mmf.names(FOLDER):
|
||||
if name != f"{group}.json" and not name.startswith(f"{group}_"):
|
||||
continue
|
||||
data, version = read(name)
|
||||
out.append(
|
||||
{
|
||||
"file": path.name,
|
||||
"file": name,
|
||||
"book": _book(data),
|
||||
"chapter": chapter_of(path.name, data),
|
||||
"chapter": chapter_of(name, data),
|
||||
"edition": data.get("판"),
|
||||
"rows": len(data.get(items_key(data)) or []),
|
||||
"version": version,
|
||||
@@ -659,37 +671,7 @@ def _apply(data: dict, changes: list[dict], file: str, book: dict) -> None:
|
||||
|
||||
|
||||
def save(batch: list[dict]) -> list[dict]:
|
||||
with _LOCK:
|
||||
stale = [p.get("file", "") for p in batch if read(p.get("file", ""))[1] != p.get("version")]
|
||||
if stale:
|
||||
raise StoreError(409, {"stale": stale})
|
||||
before, after = cm.load(folder=FOLDER), cm.load(folder=FOLDER) # after = 고칠 사본
|
||||
book_path = FOLDER / mk.BOOK.name
|
||||
book = mk.load_book(book_path)
|
||||
issued = dict(book["다음"])
|
||||
names = [part["file"] for part in batch]
|
||||
for part in batch:
|
||||
_apply(after[part["file"]], part.get("changes") or [], part["file"], book)
|
||||
mk.finish(book)
|
||||
# 저장이 흔드는 줄만 검사(고친 줄 · 그 줄을 부르던 줄 · 그 요소를 보던 줄) —
|
||||
# 마스터 전체 검사는 `check_master` 몫
|
||||
narrowed = cache.narrow(FOLDER, batch, before, after)
|
||||
old = set(cache.check_saved(before, names, mk.load_book(book_path), narrowed))
|
||||
new = [x for x in cache.check_saved(after, names, book, narrowed) if x not in old]
|
||||
if new:
|
||||
raise StoreError(422, {"errors": new})
|
||||
if book["다음"] != issued:
|
||||
mk.save_book(book, book_path)
|
||||
out = []
|
||||
for name in dict.fromkeys(names):
|
||||
old_text = (FOLDER / name).read_bytes().decode("utf-8")
|
||||
text = keep_shape(old_text, before[name], after[name], items_key(after[name])) or dump(
|
||||
after[name]
|
||||
)
|
||||
tmp = FOLDER / f".{name}.tmp"
|
||||
tmp.write_text(text, encoding="utf-8", newline="\n")
|
||||
os.replace(tmp, FOLDER / name)
|
||||
out.append({"file": name, "version": version_of(text.encode("utf-8"))})
|
||||
# 저장 뒤에는 그 파일을 바로 다시 읽게 · 흔들린 자리를 알려 줘 그 줄만 다시 세게
|
||||
cache.stale(dict.fromkeys(names), narrowed[2])
|
||||
return out
|
||||
"""몸은 `M01_MasterData_Store_Save`(826-2 — 기억에서 전 · 후 · 바뀐 것만 검사)."""
|
||||
from M01_MasterData import M01_MasterData_Store_Save as saver
|
||||
|
||||
return saver.save(batch)
|
||||
|
||||
Reference in New Issue
Block a user