Minifigures commited on
Commit
032864e
·
verified ·
1 Parent(s): 594f2b3

fix: magic-byte asset validation and hub repair

Browse files
Files changed (1) hide show
  1. scripts/bootstrap_assets.py +67 -29
scripts/bootstrap_assets.py CHANGED
@@ -1,10 +1,11 @@
1
- """Materialize git-LFS pointer files at container start (Hugging Face Spaces).
2
 
3
- The Space build context delivers some binary files as LFS pointers rather than
4
- real bytes (storage-class dependent and not under our control). Anything the
5
- app needs at runtime seed assets, model weights is scanned here; pointer
6
- files are replaced with the real content via the hub API, which resolves them
7
- reliably. Local and compose runs have real files on disk, so this is a no-op.
 
8
 
9
  Runs before the seeder in the image CMD chain.
10
  """
@@ -13,36 +14,67 @@ from __future__ import annotations
13
 
14
  import os
15
  from pathlib import Path
 
16
 
17
- POINTER_MAGIC = b"version https://git-lfs"
18
- SCAN_DIRS = ("seed-assets", "weights")
19
 
 
 
20
 
21
- def _pointer_files() -> list[Path]:
22
- found: list[Path] = []
23
- for scan in SCAN_DIRS:
24
- root = Path(scan)
25
- if not root.exists():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  continue
27
- for path in root.rglob("*"):
28
- if path.is_file():
29
- with path.open("rb") as fh:
30
- if fh.read(len(POINTER_MAGIC)) == POINTER_MAGIC:
31
- found.append(path)
32
- return found
 
 
 
 
33
 
34
 
35
  def main() -> None:
36
- pointers = _pointer_files()
37
- if not pointers:
38
- print("[bootstrap] all binary assets are materialized; nothing to do", flush=True)
39
  return
40
 
41
  repo_id = os.environ.get("SPACE_ID") # injected by Spaces, e.g. "Minifigures/claimflow-api"
42
  if repo_id is None:
43
  raise SystemExit(
44
- f"[bootstrap] {len(pointers)} LFS pointer files found but SPACE_ID is unset; "
45
- f"cannot resolve: {[str(p) for p in pointers]}"
46
  )
47
 
48
  # The image pins offline mode for serving; this one bootstrap step needs the hub.
@@ -50,11 +82,17 @@ def main() -> None:
50
  os.environ.pop("TRANSFORMERS_OFFLINE", None)
51
  from huggingface_hub import hf_hub_download
52
 
53
- for path in pointers:
54
- print(f"[bootstrap] resolving LFS pointer: {path}", flush=True)
55
- resolved = hf_hub_download(repo_id=repo_id, repo_type="space", filename=str(path))
56
- path.write_bytes(Path(resolved).read_bytes())
57
- print(f"[bootstrap] materialized {len(pointers)} files", flush=True)
 
 
 
 
 
 
58
 
59
 
60
  if __name__ == "__main__":
 
1
+ """Verify runtime binary assets at container start; re-fetch any that are invalid.
2
 
3
+ Hugging Face Space builds can deliver repo binaries in surprising states
4
+ (git-LFS pointers, xet pointers, partial materialization) depending on storage
5
+ classoutside our control and version-dependent. Instead of guessing storage
6
+ formats, every asset the app needs is validated by its content magic; anything
7
+ invalid is downloaded through the hub API, which resolves storage correctly.
8
+ Local and compose runs have real files on disk, so this is a no-op.
9
 
10
  Runs before the seeder in the image CMD chain.
11
  """
 
14
 
15
  import os
16
  from pathlib import Path
17
+ from typing import Callable
18
 
 
 
19
 
20
+ def _png(data: bytes) -> bool:
21
+ return data.startswith(b"\x89PNG\r\n\x1a\n")
22
 
23
+
24
+ def _dicom(data: bytes) -> bool:
25
+ return len(data) >= 132 and data[128:132] == b"DICM"
26
+
27
+
28
+ def _torch_zip(data: bytes) -> bool:
29
+ return data.startswith(b"PK")
30
+
31
+
32
+ def _safetensors(data: bytes) -> bool:
33
+ # 8-byte little-endian header length followed by a JSON header.
34
+ return len(data) > 8 and data[8:9] == b"{"
35
+
36
+
37
+ REQUIRED: dict[str, Callable[[bytes], bool]] = {
38
+ "seed-assets/clean_ct.png": _png,
39
+ "seed-assets/clean_mri.png": _png,
40
+ "seed-assets/clean_xray.png": _png,
41
+ "seed-assets/tampered_xray.dcm": _dicom,
42
+ "weights/modality_efficientnet_b0.pt": _torch_zip,
43
+ "weights/authenticity_efficientnet_b0.pt": _torch_zip,
44
+ "weights/all-MiniLM-L6-v2/model.safetensors": _safetensors,
45
+ }
46
+
47
+
48
+ def _invalid_assets() -> list[str]:
49
+ bad: list[str] = []
50
+ for rel, check in REQUIRED.items():
51
+ path = Path(rel)
52
+ if not path.is_file():
53
+ print(f"[bootstrap] MISSING: {rel}", flush=True)
54
+ bad.append(rel)
55
  continue
56
+ with path.open("rb") as fh:
57
+ head = fh.read(160)
58
+ if not check(head):
59
+ print(
60
+ f"[bootstrap] INVALID: {rel} size={path.stat().st_size} "
61
+ f"head={head[:24].hex()} text={head[:24]!r}",
62
+ flush=True,
63
+ )
64
+ bad.append(rel)
65
+ return bad
66
 
67
 
68
  def main() -> None:
69
+ bad = _invalid_assets()
70
+ if not bad:
71
+ print("[bootstrap] all required assets valid; nothing to do", flush=True)
72
  return
73
 
74
  repo_id = os.environ.get("SPACE_ID") # injected by Spaces, e.g. "Minifigures/claimflow-api"
75
  if repo_id is None:
76
  raise SystemExit(
77
+ f"[bootstrap] {len(bad)} invalid assets and SPACE_ID unset; cannot resolve: {bad}"
 
78
  )
79
 
80
  # The image pins offline mode for serving; this one bootstrap step needs the hub.
 
82
  os.environ.pop("TRANSFORMERS_OFFLINE", None)
83
  from huggingface_hub import hf_hub_download
84
 
85
+ for rel in bad:
86
+ print(f"[bootstrap] fetching from hub: {rel}", flush=True)
87
+ resolved = hf_hub_download(
88
+ repo_id=repo_id, repo_type="space", filename=rel, force_download=True
89
+ )
90
+ Path(rel).write_bytes(Path(resolved).read_bytes())
91
+
92
+ still_bad = _invalid_assets()
93
+ if still_bad:
94
+ raise SystemExit(f"[bootstrap] assets still invalid after hub fetch: {still_bad}")
95
+ print(f"[bootstrap] repaired {len(bad)} assets from the hub", flush=True)
96
 
97
 
98
  if __name__ == "__main__":