AQIT 0.1.0
Loading...
Searching...
No Matches
store.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2# This file is part of the Aquin Engine. Unauthorized copying, modification,
3# distribution, or use of this file, via any medium, is strictly prohibited.
4# Proprietary and confidential. See LICENSE for terms.
5
6"""On-disk layout under ./aquin_run/ — revisions, runs, checkpoints, gates."""
7
8from __future__ import annotations
9
10import hashlib
11import json
12import shutil
13from pathlib import Path
14from typing import Any
15
16from aquin.recipe.schema import DataRevision, RecipeError, RunRecord
17
18RUN_ROOT_NAME = "aquin_run"
19
20
21def run_root(cwd: Path | None = None) -> Path:
22 return (cwd or Path.cwd()) / RUN_ROOT_NAME
23
24
25def revision_dir(rev_id: str, *, cwd: Path | None = None) -> Path:
26 return run_root(cwd) / "revisions" / rev_id
27
28
29def run_dir(run_id: str, *, cwd: Path | None = None) -> Path:
30 return run_root(cwd) / "runs" / run_id
31
32
33def hash_file(path: Path) -> str:
34 digest = hashlib.sha256()
35 with path.open("rb") as fh:
36 while True:
37 chunk = fh.read(1024 * 1024)
38 if not chunk:
39 break
40 digest.update(chunk)
41 return digest.hexdigest()
42
43
44def _count_rows(path: Path, fmt: str) -> int | None:
45 try:
46 if fmt == "csv":
47 with path.open(encoding="utf-8", errors="replace") as fh:
48 n = sum(1 for _ in fh)
49 return max(0, n - 1)
50 if fmt == "jsonl":
51 with path.open(encoding="utf-8", errors="replace") as fh:
52 return sum(1 for line in fh if line.strip())
53 except OSError:
54 return None
55 return None
56
57
58def _csv_columns(path: Path) -> list[str] | None:
59 try:
60 with path.open(encoding="utf-8", errors="replace") as fh:
61 header = fh.readline()
62 return [c.strip() for c in header.strip().split(",") if c.strip()]
63 except OSError:
64 return None
65
66
68 path: str | Path,
69 *,
70 snapshot: bool = False,
71 cwd: Path | None = None,
72) -> DataRevision:
73 src = Path(path).expanduser().resolve()
74 if not src.exists():
75 raise RecipeError(f"Data path not found: {src}")
76 if src.is_dir():
77 raise RecipeError(f"Data path must be a file (csv/jsonl), got directory: {src}")
78
79 suffix = src.suffix.lower()
80 fmt = {".csv": "csv", ".jsonl": "jsonl", ".json": "json"}.get(suffix, suffix.lstrip(".") or "file")
81 sha = hash_file(src)
82 rev_id = sha[:12]
83 dest = revision_dir(rev_id, cwd=cwd)
84 dest.mkdir(parents=True, exist_ok=True)
85
86 snapshot_dir = None
87 data_path = src
88 if snapshot:
89 copied = dest / src.name
90 shutil.copy2(src, copied)
91 snapshot_dir = str(copied)
92 data_path = copied
93
94 rev = DataRevision(
95 id=rev_id,
96 path=str(data_path),
97 sha256=sha,
98 format=fmt,
99 n_rows=_count_rows(src, fmt),
100 columns=_csv_columns(src) if fmt == "csv" else None,
101 snapshot_dir=snapshot_dir,
102 )
103 (dest / "manifest.json").write_text(json.dumps(rev.to_dict(), indent=2), encoding="utf-8")
104 return rev
105
106
107def write_json(path: Path, payload: Any) -> None:
108 path.parent.mkdir(parents=True, exist_ok=True)
109 path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
110
112def save_run(record: RunRecord) -> Path:
113 d = Path(record.run_dir)
114 d.mkdir(parents=True, exist_ok=True)
115 write_json(d / "run.json", record.to_dict())
116 if record.checkpoint:
117 write_json(d / "checkpoint.json", record.checkpoint)
118 if record.gate:
119 write_json(d / "gate.json", record.gate)
120 if record.metrics:
121 write_json(d / "metrics.json", record.metrics)
122 if record.inspect:
123 write_json(d / "inspect.json", record.inspect)
124 return d / "run.json"
125
126
127def load_run(run_id: str, *, cwd: Path | None = None) -> RunRecord:
128 path = run_dir(run_id, cwd=cwd) / "run.json"
129 if not path.is_file():
130 raise RecipeError(f"No run {run_id!r} under {run_root(cwd) / 'runs'}")
131 data = json.loads(path.read_text(encoding="utf-8"))
132 return RunRecord(
133 run_id=data["run_id"],
134 name=data.get("name") or "",
135 family=data.get("family") or "",
136 status=data.get("status") or "unknown",
137 run_dir=data.get("run_dir") or str(path.parent),
138 recipe=dict(data.get("recipe") or {}),
139 revision=dict(data.get("revision") or {}),
140 checkpoint=data.get("checkpoint"),
141 gate=data.get("gate"),
142 metrics=dict(data.get("metrics") or {}),
143 inspect=dict(data.get("inspect") or {}),
144 error=data.get("error"),
145 )
Path revision_dir(str rev_id, *, Path|None cwd=None)
Definition store.py:29
None write_json(Path path, Any payload)
Definition store.py:111
RunRecord load_run(str run_id, *, Path|None cwd=None)
Definition store.py:131
Path run_dir(str run_id, *, Path|None cwd=None)
Definition store.py:33
Path run_root(Path|None cwd=None)
Definition store.py:25
Path save_run(RunRecord record)
Definition store.py:116
list[str]|None _csv_columns(Path path)
Definition store.py:62
int|None _count_rows(Path path, str fmt)
Definition store.py:48
str hash_file(Path path)
Definition store.py:37
DataRevision capture_revision(str|Path path, *, bool snapshot=False, Path|None cwd=None)
Definition store.py:76