diff --git a/docs/source/en/package_reference/cli.md b/docs/source/en/package_reference/cli.md index 25c90d7c7d..17f6625529 100644 --- a/docs/source/en/package_reference/cli.md +++ b/docs/source/en/package_reference/cli.md @@ -3414,7 +3414,7 @@ $ hf spaces hot-reload [OPTIONS] SPACE_ID [FILENAME] **Options**: -* `-f, --local-file TEXT`: Path of local file. Interactive editor mode if not specified +* `-f, --local-file PATH`: Path of local file. Interactive editor mode if not specified * `--skip-checks / --no-skip-checks`: Skip hot-reload compatibility checks. [default: no-skip-checks] * `--skip-summary / --no-skip-summary`: Skip summary display after hot-reload is triggered [default: no-skip-summary] * `--token TEXT`: A User Access Token generated from https://huggingface.co/settings/tokens. diff --git a/src/huggingface_hub/_hot_reload/client.py b/src/huggingface_hub/_hot_reload/client.py index 2d6cd702bd..0405ec061c 100644 --- a/src/huggingface_hub/_hot_reload/client.py +++ b/src/huggingface_hub/_hot_reload/client.py @@ -27,6 +27,7 @@ HOT_RELOADING_PORT = 7887 +CLIENT_TIMEOUT = 10 class MultiReplicaStreamEvent(TypedDict): @@ -57,6 +58,7 @@ def __init__( self.client = httpx.Client( base_url=f"{base_host}/--replicas/+{replica_hash}", headers=build_hf_headers(token=token), + timeout=CLIENT_TIMEOUT, ) def get_reload(self, reload_id: str) -> Iterator[ApiGetReloadEventSourceData]: diff --git a/src/huggingface_hub/cli/spaces.py b/src/huggingface_hub/cli/spaces.py index 3f62988d98..4641130879 100644 --- a/src/huggingface_hub/cli/spaces.py +++ b/src/huggingface_hub/cli/spaces.py @@ -33,6 +33,7 @@ import sys import tempfile import time +from pathlib import Path from typing import Annotated, Literal, get_args import typer @@ -235,7 +236,7 @@ def spaces_hot_reload( ), ] = None, local_file: Annotated[ - str | None, + Path | None, typer.Option( "--local-file", "-f", @@ -277,10 +278,14 @@ def spaces_hot_reload( raise CLIError(f"Unable to read sdk_version from {space_id} cardData") if version.parse(sdk_version) < version.Version(HOT_RELOADING_MIN_GRADIO): raise CLIError(f"Hot-reloading requires Gradio >= {HOT_RELOADING_MIN_GRADIO} (found {sdk_version})") + if (current_sha := space_info.sha) is None: + raise CLIError(f"Unexpected `None` running SHA for Space {space_id}") + else: + current_sha = None if local_file: - local_path = local_file - filename = local_file if filename is None else filename + local_path = str(local_file) + filename = local_path if filename is None else filename elif filename: if not skip_checks: try: @@ -309,7 +314,13 @@ def spaces_hot_reload( enable_progress_bars() editor_res = _editor_open(local_path) if editor_res == "no-tty": - raise CLIError("Cannot open an editor (no TTY). Use -f flag to hot-reload from local path") + persistent_temp_dir = tempfile.mkdtemp() + shutil.copytree(temp_dir.name, persistent_temp_dir, dirs_exist_ok=True) + local_path = os.path.join(persistent_temp_dir, filename) + typer.secho("No TTY detected. Non-interactive fallback:") + typer.secho(f"- Edit {local_path}") + typer.secho(f"- Run `hf spaces hot-reload {space_id} {filename} -f {local_path}`") + return if editor_res == "no-editor": raise CLIError("No editor found in local environment. Use -f flag to hot-reload from local path") if editor_res != 0: @@ -322,15 +333,22 @@ def spaces_hot_reload( repo_id=space_id, path_or_fileobj=local_path, path_in_repo=filename, + parent_commit=current_sha, _hot_reload=True, ) + if local_file is not None and local_file.resolve().is_relative_to(Path.cwd()): + typer.secho(f"Created commit {commit_info.oid} in remote Space repository.") + typer.secho("Consider running `git pull --autostash` to stay synced if you are working from a local clone.") + if not skip_summary: + typer.secho("Hot-reload summary:") _spaces_hot_reload_summary( api=api, space_id=space_id, + current_sha=current_sha, commit_sha=commit_info.oid, - local_path=local_path if local_file else os.path.basename(local_path), + local_path=local_path if local_file else filename, token=token, ) @@ -338,11 +356,18 @@ def spaces_hot_reload( def _spaces_hot_reload_summary( api: HfApi, space_id: str, + current_sha: str | None, commit_sha: str, local_path: str | None, token: str | None, ) -> None: - space_info = api.space_info(space_id) + while (space_info := api.space_info(space_id)).sha == current_sha: + if current_sha is None or current_sha == commit_sha: + break + typer.secho("Waiting for up-to-date Space infos", fg=typer.colors.BRIGHT_BLACK, err=True) + time.sleep(2) + if space_info.sha != commit_sha: + raise CLIError(f"Expected SHA {commit_sha} after hot-reload but got {space_info.sha}") if (runtime := space_info.runtime) is None: raise CLIError(f"Unable to read SpaceRuntime from {space_id} infos") if (hot_reloading := runtime.hot_reloading) is None: