#!/usr/bin/env python3 """ Utility to push Hugging Face Space secrets from a local env file. Usage: export HF_TOKEN=hf_xxx # token with write access to the Space python backend/scripts/update_hf_space_secrets.py \ --space davidttran999/hue-portal-backendDocker \ --secrets-file ops/hf.secrets.env """ from __future__ import annotations import argparse import os import sys from pathlib import Path from typing import Dict import requests def parse_env_file(path: Path) -> Dict[str, str]: """ Load KEY=VALUE pairs from the provided file. Blank lines and comments starting with `#` are ignored. """ if not path.exists(): raise FileNotFoundError(f"Secrets file not found: {path}") secrets: Dict[str, str] = {} for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#"): continue if "=" not in line: raise ValueError(f"Invalid secret line (missing '='): {raw_line}") key, value = line.split("=", 1) secrets[key.strip()] = value.strip() if not secrets: raise ValueError(f"No secrets detected in {path}") return secrets def upsert_secret(space_id: str, token: str, key: str, value: str) -> None: """Create or update a secret for the given Hugging Face Space.""" url = f"https://huggingface.co/api/spaces/{space_id}/secrets" headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} response = requests.post(url, headers=headers, json={"key": key, "value": value}, timeout=30) if response.status_code != 200: raise RuntimeError( f"Failed to upsert secret '{key}'. " f"Status {response.status_code}: {response.text}" ) def build_parser() -> argparse.ArgumentParser: """Configure CLI options.""" parser = argparse.ArgumentParser(description="Sync secrets to a Hugging Face Space.") parser.add_argument( "--space", required=True, help="Space identifier in the form owner/space (e.g. davidttran999/hue-portal-backendDocker).", ) parser.add_argument( "--secrets-file", default="ops/hf.secrets.env", help="Path to file containing KEY=VALUE entries (default: %(default)s).", ) parser.add_argument( "--token-env", default="HF_TOKEN", help="Environment variable that stores the Hugging Face access token (default: %(default)s).", ) return parser def main() -> None: """CLI entry point.""" parser = build_parser() args = parser.parse_args() token = os.environ.get(args.token_env) if not token: parser.error(f"Environment variable {args.token_env} is not set.") secrets = parse_env_file(Path(args.secrets_file).expanduser()) for key, value in secrets.items(): upsert_secret(args.space, token, key, value) print(f"✅ Synced secret '{key}' to {args.space}") if __name__ == "__main__": try: main() except Exception as exc: # pylint: disable=broad-except print(f"❌ {exc}", file=sys.stderr) sys.exit(1)