trigger / app.py
alesamodio's picture
After public#2
dc055c2
from fastapi import FastAPI
import os, time, requests
app = FastAPI()
# === CONFIG ===
BASE = os.getenv("DB3_BASE_URL", "https://alesamodio-db3-update.hf.space") # receiver base URL
CRON_SECRET = os.getenv("CRON_SECRET", "") # receiver's /run secret (set in receiver Space)
HF_TOKEN = os.getenv("HF_TOKEN", "") # READ token if receiver is PRIVATE (set in this trigger Space)
# Timings (tune if needed; can also override via query params)
DEFAULT_WARMUP_WAIT_S = int(os.getenv("WARMUP_WAIT_S", "180")) # quiet wait after first wake
READY_INTERVAL_S = int(os.getenv("READY_INTERVAL_S", "20")) # poll cadence
READY_MAX_ATTEMPTS = int(os.getenv("READY_MAX_ATTEMPTS", "15")) # ~5 min total
def _headers():
return {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
@app.get("/")
def home():
return {"status": "running", "message": "HF trigger alive"}
@app.get("/run")
def run():
"""
Wake receiver once -> quiet wait -> poll /ready -> fire /run?secret=...
Optional query overrides:
warmup_wait (int seconds), max_attempts, interval
"""
log = []
warmup_wait = int(os.getenv("WARMUP_WAIT_S", str(DEFAULT_WARMUP_WAIT_S)))
# allow query overrides without importing FastAPI Request
try:
import starlette.requests
# no-op; just to avoid extra dependency notes
except Exception:
pass
# simple query parsing without typing Request
from fastapi import Request
def get_query():
# tiny helper because we didn't annotate Request in signature
import inspect
frame = inspect.currentframe().f_back
req = frame.f_locals.get('request') # not available in this simple handler
return {}
# actual overrides via os env only to keep it minimal
max_attempts = READY_MAX_ATTEMPTS
interval = READY_INTERVAL_S
# --- 1) Wake once (GET /) ---
try:
r = requests.get(f"{BASE}/", headers=_headers(), timeout=10)
log.append(f"wake: {r.status_code}")
except Exception as e:
log.append(f"wake_err: {e}")
# --- 2) Quiet wait to let models load ---
time.sleep(warmup_wait)
log.append(f"quiet_wait_done: {warmup_wait}s")
# --- 3) Poll /ready ---
ready = False
last_text = ""
for i in range(1, max_attempts + 1):
try:
rr = requests.get(f"{BASE}/ready", headers=_headers(), timeout=10)
last_text = rr.text
if rr.ok and '"ready"' in rr.text:
log.append(f"ready_yes_at_attempt:{i}")
ready = True
break
else:
log.append(f"ready_no_{i}: {rr.status_code} {rr.text[:80]}")
except Exception as e:
log.append(f"ready_err_{i}: {e}")
time.sleep(interval)
if not ready:
return {
"status": "not_ready",
"last_ready_response": last_text[:200],
"log": log
}
# --- 4) Fire /run with secret ---
try:
run_url = f"{BASE}/run?secret={CRON_SECRET}"
final = requests.get(run_url, headers=_headers(), timeout=600)
return {
"status": "ok",
"code": final.status_code,
"body": (final.text[:2000] if final.text else ""),
"log": log
}
except Exception as e:
return {"status": "run_error", "error": str(e), "log": log}