Github-Transfer / repo_to_space_auto.py
openfree's picture
Update repo_to_space_auto.py
b99342b verified
raw
history blame
7.01 kB
#!/usr/bin/env python3
"""
repo_to_space_auto.py
---------------------
๊ณต๊ฐœ Git ๋ ˆํฌ๋ฅผ Hugging Face Gradio Space๋กœ ์ž๋™ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค.
ํ•„์š” ํ™˜๊ฒฝ๋ณ€์ˆ˜
-------------
BAPI_TOKEN : Brave Search API Key
FRIENDLI_TOKEN : Friendli Dedicated Endpoint Token
CLI ์ธ์ž
--------
--repo_url <URL> : ์›๋ณธ Git ๋ ˆํฌ URL (ํ•„์ˆ˜*ยน)
--hf_token <TOK> : ์“ฐ๊ธฐ ๊ถŒํ•œ Hugging Face Access Token (ํ•„์ˆ˜*ยน)
--private : ๋น„๊ณต๊ฐœ Space๋กœ ์ƒ์„ฑ
--hardware <tier> : ์˜ˆ) 't4-medium' GPU ์ธ์Šคํ„ด์Šค ์ง€์ •
*ยน ์ธ์ž๋ฅผ ์ƒ๋žตํ•˜๋ฉด ๋™๋ช… ํ™˜๊ฒฝ๋ณ€์ˆ˜(REPO_URL, HF_TOKEN)๊ฐ€ ๋Œ€์‹  ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
"""
import os, sys, json, argparse, subprocess, tempfile, textwrap, requests, shutil
from pathlib import Path
import git # GitPython
from huggingface_hub import HfApi, login # HF Hub SDK
# ---------- Brave Search ํ—ฌํผ ---------- #
def brave_search_repo(repo_url: str, count: int = 5) -> list[dict]:
api_key = os.getenv("BAPI_TOKEN")
if not api_key:
raise RuntimeError("ํ™˜๊ฒฝ๋ณ€์ˆ˜ BAPI_TOKEN์ด ์„ค์ •๋ผ ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
headers = {"X-Subscription-Token": api_key, "Accept": "application/json"}
params = {"q": f'site:github.com "{repo_url}"', "count": count, "search_lang": "en"}
resp = requests.get("https://api.search.brave.com/res/v1/web/search",
headers=headers, params=params, timeout=10)
resp.raise_for_status()
return resp.json().get("web", {}).get("results", [])
# ---------- Friendli LLM ํ—ฌํผ ---------- #
def friendli_generate_scaffold(context: str) -> dict:
token = os.getenv("FRIENDLI_TOKEN")
if not token:
raise RuntimeError("ํ™˜๊ฒฝ๋ณ€์ˆ˜ FRIENDLI_TOKEN์ด ์„ค์ •๋ผ ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
payload = {
"model": "dep89a2fld32mcm",
"messages": [
{"role": "system",
"content": ("You are an expert Hugging Face Space architect. "
"Given repository context, output JSON with keys "
"`app_py`, `requirements_txt`, `need_docker` (bool), "
"`dockerfile` (if needed) and `summary`.")},
{"role": "user", "content": context}
],
"max_tokens": 16384,
"top_p": 0.8,
"stream": False
}
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
r = requests.post("https://api.friendli.ai/dedicated/v1/chat/completions",
json=payload, headers=headers, timeout=120)
r.raise_for_status()
return json.loads(r.json()["choices"][0]["message"]["content"])
# ---------- ๋ฉ”์ธ ๋ฐฐํฌ ๋กœ์ง ---------- #
def deploy(repo_url: str, hf_token: str, private=False, hardware=None) -> str:
"""๋ ˆํฌ๋ฅผ Gradio Space๋กœ ๋ฐฐํฌํ•˜๊ณ  Space URL ๋ฐ˜ํ™˜."""
login(hf_token)
api = HfApi(token=hf_token)
user = api.whoami()["name"]
space = f"{user}/{Path(repo_url).stem.lower().replace('.', '-')}"
# create_repo ํ˜ธ์ถœ (hardware ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐ)
api.create_repo(
repo_id=space,
repo_type="space",
space_sdk="gradio",
private=private,
exist_ok=True
)
# hardware ์„ค์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ณ„๋„๋กœ update_repo_settings ํ˜ธ์ถœ
if hardware:
try:
# Space ์„ค์ • ์—…๋ฐ์ดํŠธ (hardware)
api.request_space_hardware(repo_id=space, hardware=hardware)
except Exception as e:
print(f"โš ๏ธ Hardware ์„ค์ • ์‹คํŒจ (Space๋Š” ์ƒ์„ฑ๋จ): {e}")
with tempfile.TemporaryDirectory() as work:
# 1) Brave ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
brave_meta = brave_search_repo(repo_url)
# 2) ์›๋ณธ ๋ ˆํฌ ํด๋ก 
src = Path(work) / "src"
git.Repo.clone_from(repo_url, src)
readme = ""
if (src / "README.md").exists():
readme = (src / "README.md").read_text(encoding="utf-8", errors="ignore")[:4000]
tree_out = subprocess.run(["bash", "-lc", f"tree -L 2 {src} 2>/dev/null || find {src} -maxdepth 2 -type f | head -n 40"],
text=True, capture_output=True).stdout
context = textwrap.dedent(f"""
## Brave meta
{json.dumps(brave_meta, ensure_ascii=False, indent=2)}
## Repository tree (depth 2)
{tree_out}
## README (first 4 kB)
{readme}
""")
# 3) Friendli ์Šค์บํด๋“œ ์ƒ์„ฑ
scaffold = friendli_generate_scaffold(context)
# 4) Space ๋ ˆํฌ ํด๋ก  & ํŒŒ์ผ ์“ฐ๊ธฐ
dst = Path(work) / "space"
space_repo = git.Repo.clone_from(
f"https://huggingface.co/spaces/{space}",
dst,
env={"HF_TOKEN": hf_token}
)
(dst / "app.py").write_text(scaffold["app_py"], encoding="utf-8")
(dst / "requirements.txt").write_text(scaffold["requirements_txt"], encoding="utf-8")
if scaffold.get("need_docker"):
(dst / "Dockerfile").write_text(scaffold["dockerfile"], encoding="utf-8")
# README.md์— ์›๋ณธ ๋ ˆํฌ ์ •๋ณด ์ถ”๊ฐ€
readme_content = f"""---
title: {Path(repo_url).stem}
emoji: ๐Ÿš€
colorFrom: blue
colorTo: green
sdk: gradio
sdk_version: 4.44.1
app_file: app.py
pinned: false
---
# {Path(repo_url).stem}
Automatically deployed from: {repo_url}
## Summary
{scaffold["summary"]}
---
*Created by HF Space Auto-Deployer*
"""
(dst / "README.md").write_text(readme_content, encoding="utf-8")
# Git ์ปค๋ฐ‹ ๋ฐ ํ‘ธ์‹œ
space_repo.index.add(["app.py", "requirements.txt", "README.md"])
if scaffold.get("need_docker"):
space_repo.index.add(["Dockerfile"])
space_repo.index.commit("Initial auto-deploy")
# Push with authentication
origin = space_repo.remote("origin")
origin.push(env={"HF_TOKEN": hf_token})
return f"https://huggingface.co/spaces/{space}"
# ---------- CLI ์—”ํŠธ๋ฆฌ ---------- #
def main():
parser = argparse.ArgumentParser(description="Git ๋ ˆํฌ๋ฅผ Hugging Face Gradio Space๋กœ ์ž๋™ ๋ฐฐํฌ")
parser.add_argument("--repo_url", default=os.getenv("REPO_URL"),
help="์›๋ณธ Git ๋ ˆํฌ์ง€ํ† ๋ฆฌ URL (or env REPO_URL)")
parser.add_argument("--hf_token", default=os.getenv("HF_TOKEN"),
help="์“ฐ๊ธฐ ๊ถŒํ•œ HF ํ† ํฐ (or env HF_TOKEN)")
parser.add_argument("--private", action="store_true", help="๋น„๊ณต๊ฐœ Space ์ƒ์„ฑ")
parser.add_argument("--hardware", default=None, help="์˜ˆ: 't4-medium'")
args = parser.parse_args()
if not args.repo_url or not args.hf_token:
parser.error("--repo_url ๋ฐ --hf_token (๋˜๋Š” ๋™๋ช… ํ™˜๊ฒฝ๋ณ€์ˆ˜) ๋‘˜ ๋‹ค ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
try:
url = deploy(args.repo_url, args.hf_token, args.private, args.hardware)
print(f"โœ… ๋ฐฐํฌ ์„ฑ๊ณต: {url}")
except Exception as e:
print(f"โŒ ๋ฐฐํฌ ์‹คํŒจ: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()