cascade-review / app.py
Siddharth63's picture
Push an empty reviews.json before the first verdict
4e35008 verified
Raw History Blame Contribute Delete
3.67 kB
#!/usr/bin/env python3
"""Hugging Face Space entry point for the cascade human review (private Space sociate/cascade-review).
On start: downloads the web copy of the batch (images, product.json, review.html) from the private dataset BATCH_REPO and
the saved verdicts (reviews.json) from VERDICT_REPO, then runs review_server on port 7860. The Space disk is wiped on
restart, so after the workbook is rebuilt, reviews.json + cascade_report.xlsx are pushed back to VERDICT_REPO (at most
one commit per PUSH_EVERY seconds, plus once on shutdown). Needs the HF_TOKEN secret (read on BATCH_REPO, write on VERDICT_REPO).
"""
from __future__ import annotations
import os
import signal
import sys
import threading
import time
from functools import partial
from http.server import ThreadingHTTPServer
from pathlib import Path
from huggingface_hub import CommitOperationAdd, HfApi, hf_hub_download, snapshot_download
from huggingface_hub.errors import EntryNotFoundError
import review_server
BATCH_REPO = os.environ.get("BATCH_REPO", "sociate/cascade-review-batch")
VERDICT_REPO = os.environ.get("VERDICT_REPO", "sociate/cascade-review-verdicts")
PUSH_EVERY = float(os.environ.get("PUSH_EVERY", "30"))
BATCH = Path(os.environ.get("BATCH_DIR", Path.home() / "data" / "batch"))
api = HfApi()
class Pusher:
"""Throttled push of reviews.json + cascade_report.xlsx to the verdict dataset."""
def __init__(self):
self.files, self.dirty, self.lock = None, threading.Event(), threading.Lock()
threading.Thread(target=self._loop, daemon=True).start()
def __call__(self, reviews: Path, xlsx: Path):
self.files = (reviews, xlsx)
self.dirty.set()
def push(self):
with self.lock:
if not self.files:
return
self.dirty.clear()
reviews, xlsx = self.files
try:
api.create_commit(VERDICT_REPO, repo_type="dataset", commit_message="Update human review verdicts",
operations=[CommitOperationAdd("reviews.json", reviews.read_bytes() if reviews.exists() else b"[]"),
CommitOperationAdd("cascade_report.xlsx", xlsx.read_bytes())])
print(f"pushed verdicts to {VERDICT_REPO}", flush=True)
except Exception as e: # retried on the next change / tick
self.dirty.set()
print("push failed:", e, flush=True)
def _loop(self):
while True:
self.dirty.wait()
self.push()
time.sleep(PUSH_EVERY)
def main() -> None:
print(f"downloading {BATCH_REPO} ...", flush=True)
snapshot_download(BATCH_REPO, repo_type="dataset", local_dir=BATCH, max_workers=16)
try:
hf_hub_download(VERDICT_REPO, "reviews.json", repo_type="dataset", local_dir=BATCH, force_download=True)
print("restored reviews.json", flush=True)
except EntryNotFoundError:
print("no saved verdicts yet", flush=True)
pusher = Pusher()
review_server.Handler.store = store = review_server.Store(BATCH, debounce=2.0, after_build=pusher)
store.dirty.set() # build the workbook once at start (with any restored verdicts)
def stop(*_):
if store.excel["pending"]:
time.sleep(3) # let a rebuild in flight finish
pusher.push()
sys.exit(0)
signal.signal(signal.SIGTERM, stop)
srv = ThreadingHTTPServer(("0.0.0.0", 7860), partial(review_server.Handler, directory=str(BATCH)))
print(f"serving {BATCH} ({len(store.reviews)} saved reviews) on :7860", flush=True)
srv.serve_forever()
if __name__ == "__main__":
main()