| 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() |
|
|