Spaces:
Paused
Paused
| """ | |
| Fingerprint the artifacts and source files the process actually loaded. | |
| A deployed container can diverge from the repository in two independent ways: | |
| the code can be stale (an old commit, or a cached layer), or the artifacts can | |
| be stale (a previous build's download that `ensure_artifacts` then skips, | |
| because the files already exist on disk). Reading the app's output cannot tell | |
| these apart β both produce "wrong answers" β and neither can comparing the Hub | |
| against a local checkout, since that says nothing about what the container has. | |
| Hashing from inside the process resolves it. Run this locally and read the same | |
| lines from the container's startup log: if the source hashes differ, the code | |
| path is wrong; if the artifact hashes differ, the artifacts are stale; if | |
| everything matches and behaviour still differs, the cause is neither. | |
| AI Attribution: Fingerprint design assisted by Claude (Anthropic, https://claude.ai). | |
| """ | |
| import hashlib | |
| from pathlib import Path | |
| # Ordered so the source files β the usual suspect after a push β come first. | |
| TRACKED_PATHS = [ | |
| "scripts/recommender.py", | |
| "scripts/artifacts.py", | |
| "main.py", | |
| "data/processed/finetuned.index", | |
| "data/processed/base.index", | |
| "data/processed/finetuned_embeddings.npy", | |
| "data/processed/base_embeddings.npy", | |
| "data/processed/paper_metadata.pkl", | |
| "models/fine_tuned/model.safetensors", | |
| "models/base/model.safetensors", | |
| ] | |
| CHUNK_BYTES = 1 << 20 # 1 MiB | |
| def sha256_file(path, digest_chars=16): | |
| """Hash a file with SHA-256, streaming so large files stay off the heap. | |
| Args: | |
| path: Path to the file. | |
| digest_chars: How many leading hex characters to return. Sixteen is | |
| far more than needed to distinguish two builds and stays readable | |
| in a log line. | |
| Returns: | |
| Truncated hex digest, or a marker string if the file is absent. | |
| """ | |
| p = Path(path) | |
| if not p.exists(): | |
| return "MISSING" | |
| h = hashlib.sha256() | |
| with open(p, "rb") as f: | |
| for chunk in iter(lambda: f.read(CHUNK_BYTES), b""): | |
| h.update(chunk) | |
| return h.hexdigest()[:digest_chars] | |
| def fingerprint(paths=None): | |
| """Hash every tracked path. | |
| Args: | |
| paths: Optional list of paths to hash. Defaults to TRACKED_PATHS. | |
| Returns: | |
| Dict mapping path to truncated digest, size in bytes, and existence. | |
| """ | |
| result = {} | |
| for path in paths or TRACKED_PATHS: | |
| p = Path(path) | |
| result[path] = { | |
| "sha256": sha256_file(p), | |
| "bytes": p.stat().st_size if p.exists() else 0, | |
| } | |
| return result | |
| def print_fingerprint(paths=None, header="FINGERPRINT"): | |
| """Print the fingerprint table. | |
| Called at app startup so the lines land in the container log, and runnable | |
| locally so the two can be diffed line by line. | |
| Args: | |
| paths: Optional list of paths to hash. | |
| header: Label for the table. | |
| """ | |
| fp = fingerprint(paths) | |
| width = max(len(p) for p in fp) | |
| print("\n" + "=" * (width + 34)) | |
| print(f" {header}") | |
| print("=" * (width + 34)) | |
| for path, info in fp.items(): | |
| size = f"{info['bytes'] / 1e6:.1f}MB" if info["bytes"] else "-" | |
| print(f" {path:<{width}} {info['sha256']} {size:>9}") | |
| print("=" * (width + 34) + "\n") | |
| return fp | |
| def main(): | |
| """Print the fingerprint of the current working tree.""" | |
| print_fingerprint(header="LOCAL FINGERPRINT") | |
| if __name__ == "__main__": | |
| main() | |