UBCHelper / scripts /setup_space.py
Devdan Schretlen
Added FastAPI app for live HuggingFace demo
024efc3
Raw
History Blame Contribute Delete
5.11 kB
"""Create and configure the Hugging Face Space in one step.
Reads the keys from `.env` and sets them as Space secrets over the API, so live credentials
never pass through a shell command line (and therefore never land in PSReadLine history).
Idempotent: safe to re-run to update settings on an existing Space.
python scripts/setup_space.py --space DevSchretl/ubc-calendar-rag
Free Gradio Spaces must run on ZeroGPU hardware, which is the default here. This app requests
no GPU (there is no @spaces.GPU anywhere) so it consumes a ZeroGPU *hosting* slot but never any
GPU quota. Afterwards, push the code:
git remote add hf https://huggingface.co/spaces/<space>
git push hf main
"""
from __future__ import annotations
import argparse
import secrets
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from dotenv import dotenv_values # noqa: E402
# Copied onto the Space as secrets. HF_TOKEN should be a READ-scoped token: the Space only
# ever pulls the private index dataset, so a write token there adds risk and no capability.
SECRET_KEYS = ("OPENAI_API_KEY", "COHERE_API_KEY", "ANTHROPIC_API_KEY", "HF_TOKEN")
def main() -> None:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--space", required=True, help="e.g. DevSchretl/ubc-calendar-rag")
p.add_argument("--env", type=Path, default=Path(".env"))
p.add_argument("--hardware", default="zero-a10g",
help="ZeroGPU is what makes a free Gradio Space free (default: zero-a10g)")
p.add_argument("--private", action="store_true", help="create it private; flip later in Settings")
p.add_argument("--per-hour", type=int, default=30, help="DEMO_IP_PER_HOUR (default 30, good for testing)")
p.add_argument("--per-day", type=int, default=60)
p.add_argument("--daily-calls", type=int, default=400, help="global DEMO_DAILY_LLM_CALLS ceiling")
p.add_argument("--salt", default="", help="reuse a specific DEMO_IP_SALT instead of generating one")
args = p.parse_args()
env = dotenv_values(args.env)
missing = [k for k in SECRET_KEYS if not env.get(k)]
if missing:
sys.exit(f"{args.env} is missing: {', '.join(missing)}")
index_repo = env.get("DEMO_INDEX_REPO")
index_rev = env.get("DEMO_INDEX_REVISION")
if not index_repo or not index_rev:
sys.exit("Set DEMO_INDEX_REPO and DEMO_INDEX_REVISION in .env first "
"(run scripts/publish_index.py — it prints the sha to pin).")
from huggingface_hub import HfApi
api = HfApi() # the CLI's logged-in WRITE token, for creating and configuring
# Sanity-check the read token before baking it in, so a bad one fails here rather than as
# a 401 buried in the Space's startup log.
try:
who = HfApi(token=env["HF_TOKEN"]).whoami()
role = ((who.get("auth") or {}).get("accessToken") or {}).get("role")
print(f"HF_TOKEN belongs to {who.get('name')} (role: {role})")
if role not in ("read", "fineGrained"):
print(f" note: a '{role}' token is broader than this Space needs; a read token is enough.")
except Exception as exc:
sys.exit(f"The HF_TOKEN in {args.env} is not valid: {type(exc).__name__}: {exc}")
# And that it can actually see the private index — the single most common startup failure.
try:
info = HfApi(token=env["HF_TOKEN"]).dataset_info(index_repo)
if info.sha != index_rev:
print(f" warning: DEMO_INDEX_REVISION ({index_rev[:12]}…) is not the dataset HEAD "
f"({info.sha[:12]}…). That is fine if you pinned it deliberately.")
except Exception as exc:
sys.exit(f"HF_TOKEN cannot read {index_repo}: {type(exc).__name__}: {exc}")
url = api.create_repo(
args.space, repo_type="space", space_sdk="gradio",
space_hardware=args.hardware, private=args.private, exist_ok=True,
)
print(f"Space ready: {url}")
variables = {
"DEMO_INDEX_REPO": index_repo,
"DEMO_INDEX_REVISION": index_rev,
"DEMO_DAILY_LLM_CALLS": str(args.daily_calls),
"DEMO_IP_PER_HOUR": str(args.per_hour),
"DEMO_IP_PER_DAY": str(args.per_day),
}
for key, value in variables.items():
api.add_space_variable(args.space, key, value)
print(f" variable {key} = {value}")
salt = args.salt or env.get("DEMO_IP_SALT") or secrets.token_urlsafe(32)
for key in SECRET_KEYS:
api.add_space_secret(args.space, key, env[key])
print(f" secret {key} = {env[key][:6]}… ({len(env[key])} chars)")
api.add_space_secret(args.space, "DEMO_IP_SALT", salt)
print(f" secret DEMO_IP_SALT = {salt[:6]}… (generated)" if not (args.salt or env.get("DEMO_IP_SALT"))
else " secret DEMO_IP_SALT = (reused)")
print(f"\nNow push the code:\n"
f" git remote add hf https://huggingface.co/spaces/{args.space}\n"
f" git push hf main\n"
f"\nThen watch it start:\n"
f" hf spaces logs {args.space}")
if __name__ == "__main__":
main()