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