feat: establish Quant OS production-80 architecture
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a deterministic, content-hashed inventory of ignored local assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_INCLUDES = ("data", "artifacts", "mlruns")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def build_inventory(root: Path, includes: tuple[str, ...]) -> dict:
|
||||
root = root.resolve()
|
||||
entries: list[dict] = []
|
||||
summaries: dict[str, dict] = {}
|
||||
for include in includes:
|
||||
relative = Path(include)
|
||||
if (
|
||||
relative.is_absolute()
|
||||
or ".." in relative.parts
|
||||
or len(relative.parts) != 1
|
||||
):
|
||||
raise ValueError(f"unsafe include: {include}")
|
||||
directory = root / relative
|
||||
file_count = 0
|
||||
total_bytes = 0
|
||||
if directory.exists() and not directory.is_dir():
|
||||
raise ValueError(f"include is not a directory: {include}")
|
||||
if directory.is_dir():
|
||||
for path in sorted(directory.rglob("*")):
|
||||
if path.is_symlink():
|
||||
raise ValueError(
|
||||
f"symlink is not allowed in inventory: "
|
||||
f"{path.relative_to(root)}"
|
||||
)
|
||||
if not path.is_file():
|
||||
continue
|
||||
size = path.stat().st_size
|
||||
entries.append(
|
||||
{
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"size": size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
)
|
||||
file_count += 1
|
||||
total_bytes += size
|
||||
summaries[include] = {
|
||||
"available": directory.is_dir(),
|
||||
"files": file_count,
|
||||
"bytes": total_bytes,
|
||||
}
|
||||
body = {
|
||||
"schema_version": "quant-os-local-assets-inventory/1.0",
|
||||
"includes": list(includes),
|
||||
"summaries": summaries,
|
||||
"entries": entries,
|
||||
}
|
||||
body["inventory_sha256"] = hashlib.sha256(
|
||||
json.dumps(
|
||||
body,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
return body
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument(
|
||||
"--include",
|
||||
action="append",
|
||||
dest="includes",
|
||||
help="one top-level ignored directory; repeat as needed",
|
||||
)
|
||||
parser.add_argument("--output", type=Path)
|
||||
parser.add_argument("--compare", type=Path)
|
||||
args = parser.parse_args()
|
||||
includes = tuple(args.includes or DEFAULT_INCLUDES)
|
||||
try:
|
||||
inventory = build_inventory(args.root, includes)
|
||||
except (OSError, ValueError) as exc:
|
||||
print(f"inventory failed: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
encoded = (
|
||||
json.dumps(
|
||||
inventory,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
if args.compare is not None:
|
||||
try:
|
||||
expected = json.loads(args.compare.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
print(f"cannot read comparison inventory: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if inventory != expected:
|
||||
print("local asset inventory differs", file=sys.stderr)
|
||||
return 1
|
||||
if args.output is None:
|
||||
print(encoded, end="")
|
||||
else:
|
||||
args.output.write_text(encoded, encoding="utf-8")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"files": len(inventory["entries"]),
|
||||
"inventory_sha256": inventory["inventory_sha256"],
|
||||
"output": args.output.name,
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user