|
from __future__ import annotations |
|
import shutil, tempfile, textwrap, time |
|
from pathlib import Path |
|
from typing import Dict |
|
import git |
|
from huggingface_hub import HfApi |
|
from huggingface_hub.errors import HfHubHTTPError |
|
from .exceptions import ( |
|
DockerfileMissingError, |
|
RepoCloneError, |
|
SpaceBuildTimeoutError, |
|
SpaceCreationError, |
|
SpaceDeployError, |
|
) |
|
|
|
__all__ = [ |
|
"deploy_space", |
|
"SpaceDeployError", |
|
] |
|
|
|
from .utils import _chmod_and_retry |
|
|
|
|
|
def deploy_space(*, hf_token: str, git_repo_url: str, deploy_path: str, space_name: str, space_port: int, description: str, env_vars: Dict[str, str], private: bool) -> str: |
|
deploy_path = deploy_path.strip(".").strip("/") |
|
api = HfApi(token=hf_token) |
|
try: |
|
username = api.whoami().get("name", None) |
|
repo_id = f"{username}/{space_name}" |
|
api.create_repo( |
|
repo_id=repo_id, |
|
repo_type="space", |
|
space_sdk="docker", |
|
private=private, |
|
|
|
space_secrets=[{"key": k, "value": v} for k, v in env_vars.items()], |
|
exist_ok=False, |
|
) |
|
except HfHubHTTPError as exc: |
|
raise SpaceCreationError(exc) from exc |
|
|
|
tmp = tempfile.mkdtemp(prefix="hf_space_") |
|
try: |
|
try: |
|
if not deploy_path: |
|
git.Repo.clone_from( |
|
git_repo_url, |
|
tmp, |
|
depth=1, |
|
multi_options=["--single-branch"] |
|
) |
|
else: |
|
repo = git.Repo.clone_from( |
|
git_repo_url, tmp, |
|
depth=1, |
|
no_checkout=True, |
|
filter=["tree:0"], |
|
sparse=True, |
|
multi_options=["--single-branch"], |
|
) |
|
repo.git.sparse_checkout("init", "--no-cone") |
|
repo.git.sparse_checkout("set", "--no-cone", f"/{deploy_path}/**") |
|
repo.git.checkout() |
|
|
|
|
|
src = Path(tmp, deploy_path) |
|
for item in src.iterdir(): |
|
shutil.move(item, tmp) |
|
shutil.rmtree(src) |
|
except git.GitCommandError as exc: |
|
raise RepoCloneError(str(exc)) from exc |
|
|
|
git_dir = Path(tmp, ".git") |
|
if git_dir.exists() and git_dir.is_dir(): |
|
shutil.rmtree(git_dir, onerror=_chmod_and_retry) |
|
|
|
if not Path(tmp, "Dockerfile").exists(): |
|
raise DockerfileMissingError() |
|
|
|
readme = Path(tmp, "README.md") |
|
header = ( |
|
f"---\n" |
|
f"title: \"{space_name}\"\n" |
|
f"emoji: \"🚀\"\n" |
|
f"colorFrom: blue\n" |
|
f"colorTo: green\n" |
|
f"sdk: docker\n" |
|
f"app_port: {space_port}\n" |
|
f"---\n" |
|
) |
|
badge = ( |
|
f"### 🚀 一键部署\n" |
|
f"[](https://github.com/kfcx/HFSpaceDeploy)\n\n" |
|
f"本项目由[HFSpaceDeploy](https://github.com/kfcx/HFSpaceDeploy)一键部署\n" |
|
) |
|
readme.write_text(f"{header}\n{badge}\n{description}\n", encoding="utf-8") |
|
api.upload_folder(folder_path=tmp, repo_id=repo_id, repo_type="space", ignore_patterns=[".git"]) |
|
finally: |
|
shutil.rmtree(tmp, ignore_errors=True) |
|
|
|
deadline = time.time() + 15 * 60 |
|
while time.time() < deadline: |
|
stage = api.get_space_runtime(repo_id).stage |
|
if stage == "RUNNING": |
|
return f"https://huggingface.co/spaces/{repo_id}" |
|
if stage in ("BUILD_ERROR", "CONFIG_ERROR", "RUNTIME_ERROR",): |
|
raise SpaceDeployError("Space failed during config/build/runtime") |
|
if stage in ("NO_APP_FILE", "STOPPED", "PAUSED", "DELETING", ): |
|
raise SpaceDeployError("Space is currently unavailable") |
|
time.sleep(5) |
|
raise SpaceBuildTimeoutError() |
|
|