Files
skills/orchestrator/kvb_orchestrator/diffs.py
T

67 lines
3.1 KiB
Python
Raw Normal View History

from __future__ import annotations
import json
import re
from pathlib import Path
from .errors import Missing, ToolError
from .records import read_json, write_atomic
from .signed import limits
DIFF_LIMIT = limits.DIFF_LIMIT
DIFF_BODY_LIMIT = limits.DIFF_BODY_LIMIT
FILE_CAP = limits.FILE_CAP
PATH_LIMIT = limits.PATH_LIMIT
STATUSES = ("A", "M", "D", "R", "B")
DIFF_KEYS = ("path", "status", "mode", "additions", "deletions", "patch", "note")
NOTES = ("", limits.WITHHELD)
MODE = re.compile(r"[0-7]{6}(?: => [0-7]{6})?")
DIFF_SUFFIX = {"review": ".review-diff.json", "ask": ".ask-diff.json"}
def counted(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool) and value >= 0
def diff_entry(entry: object) -> dict:
if not isinstance(entry, dict) or set(entry) != set(DIFF_KEYS):
raise ToolError(f"every diff entry must be an object with exactly {', '.join(DIFF_KEYS)}")
path, patch, mode = entry["path"], entry["patch"], entry["mode"]
if not isinstance(path, str) or not 0 < len(path) <= PATH_LIMIT or entry["status"] not in STATUSES:
raise ToolError(f"a diff path must be 1 to {PATH_LIMIT} characters and its status one of {', '.join(STATUSES)}")
if not isinstance(mode, str) or not MODE.fullmatch(mode):
raise ToolError("mode must be a git file mode, or old => new for a changed one")
if not counted(entry["additions"]) or not counted(entry["deletions"]) or not isinstance(patch, str) or (entry["status"] == "B" and patch):
raise ToolError("additions and deletions must be counts, patch text, empty for a binary file")
if entry["note"] not in NOTES or (entry["note"] and patch):
raise ToolError(f"note must be empty or {limits.WITHHELD!r}, the latter with an empty patch")
return {key: entry[key] for key in DIFF_KEYS}
def valid_diff(value: object) -> list[dict]:
"""Returns:
The review diff of requirement 003, checked: at most FILE_CAP
entries, and at most DIFF_LIMIT bytes of JSON-encoded patch text in
all, the bound the requesting script keeps.
"""
if not isinstance(value, list) or len(value) > FILE_CAP:
raise ToolError(f"diff must be a list of at most {FILE_CAP} files")
entries = [diff_entry(entry) for entry in value]
if sum(len(json.dumps(entry["patch"], ensure_ascii=False).encode("utf-8")) for entry in entries) > DIFF_LIMIT:
raise ToolError(f"the patches of a diff must stay within {DIFF_LIMIT} bytes")
return entries
def diff_bytes(diff: list[dict]) -> bytes:
return json.dumps({"diff": diff}, ensure_ascii=False).encode("utf-8")
def store_diff(folder: Path, kind_name: str, item_id: str, diff: list[dict]) -> None:
write_atomic(folder / f"{item_id}{DIFF_SUFFIX[kind_name]}", diff_bytes(diff))
def stored_diff(folder: Path, kind_name: str, item_id: str) -> dict:
data = read_json(folder / f"{item_id}{DIFF_SUFFIX[kind_name]}") if kind_name in DIFF_SUFFIX else None
if not isinstance(data, dict) or not isinstance(data.get("diff"), list):
raise Missing(item_id)
return {"kind": kind_name, "id": item_id, "diff": data["diff"]}