Skip to content

Commit 6cace2b

Browse files
Wauplinclaudehanouticelina
authored
[CLI] Add hf spaces volumes commands (#4109)
* [CLI] Add `hf spaces volumes` commands (ls, set, delete) Adds a `volumes` subcommand group under `hf spaces` to manage Space volumes from the CLI, wrapping `set_space_volumes` and `delete_space_volumes` API methods. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add hints after volume commands Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix hf handle * Update src/huggingface_hub/_space_api.py Co-authored-by: célina <hanouticelina@gmail.com> * code quality * quality --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: célina <hanouticelina@gmail.com>
1 parent 0cbf4dc commit 6cace2b

4 files changed

Lines changed: 212 additions & 0 deletions

File tree

docs/source/en/guides/manage-spaces.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,21 @@ To remove all volumes from your Space:
283283
> [!WARNING]
284284
> Setting volumes replaces any previously mounted volumes. To add a volume to an existing list, first read the current volumes from the runtime and include them in the new list.
285285
286+
All volume operations are also available from the CLI:
287+
288+
```bash
289+
# List current volumes
290+
hf spaces volumes ls username/my-space
291+
292+
# Set (replace) volumes
293+
hf spaces volumes set username/my-space \
294+
-v hf://models/username/my-model:/models \
295+
-v hf://buckets/username/my-bucket:/data
296+
297+
# Remove all volumes
298+
hf spaces volumes delete username/my-space
299+
```
300+
286301
## More advanced: temporarily upgrade your Space !
287302

288303
Spaces allow for a lot of different use cases. Sometimes, you might want

docs/source/en/package_reference/cli.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3404,6 +3404,7 @@ $ hf spaces [OPTIONS] COMMAND [ARGS]...
34043404
* `info`: Get info about a space on the Hub.
34053405
* `list`: List spaces on the Hub. [alias: ls]
34063406
* `search`: Search spaces on the Hub using semantic...
3407+
* `volumes`: Manage volumes for a Space on the Hub.
34073408

34083409
### `hf spaces dev-mode`
34093410

@@ -3582,6 +3583,114 @@ Learn more
35823583
Read the documentation at https://huggingface.co/docs/huggingface_hub/en/guides/cli
35833584

35843585

3586+
### `hf spaces volumes`
3587+
3588+
Manage volumes for a Space on the Hub.
3589+
3590+
**Usage**:
3591+
3592+
```console
3593+
$ hf spaces volumes [OPTIONS] COMMAND [ARGS]...
3594+
```
3595+
3596+
**Options**:
3597+
3598+
* `--help`: Show this message and exit.
3599+
3600+
**Commands**:
3601+
3602+
* `delete`: Remove all volumes from a Space.
3603+
* `list`: List volumes mounted in a Space. [alias: ls]
3604+
* `set`: Set (replace) volumes for a Space.
3605+
3606+
#### `hf spaces volumes delete`
3607+
3608+
Remove all volumes from a Space.
3609+
3610+
**Usage**:
3611+
3612+
```console
3613+
$ hf spaces volumes delete [OPTIONS] SPACE_ID
3614+
```
3615+
3616+
**Arguments**:
3617+
3618+
* `SPACE_ID`: The space ID (e.g. `username/repo-name`). [required]
3619+
3620+
**Options**:
3621+
3622+
* `-y, --yes`: Answer Yes to prompt automatically.
3623+
* `--format [agent|auto|human|json|quiet]`: Output format. [default: auto]
3624+
* `--token TEXT`: A User Access Token generated from https://huggingface.co/settings/tokens.
3625+
* `--help`: Show this message and exit.
3626+
3627+
Examples
3628+
$ hf spaces volumes delete username/my-space
3629+
$ hf spaces volumes delete username/my-space --yes
3630+
3631+
Learn more
3632+
Use `hf <command> --help` for more information about a command.
3633+
Read the documentation at https://huggingface.co/docs/huggingface_hub/en/guides/cli
3634+
3635+
3636+
#### `hf spaces volumes list`
3637+
3638+
List volumes mounted in a Space. [alias: ls]
3639+
3640+
**Usage**:
3641+
3642+
```console
3643+
$ hf spaces volumes list [OPTIONS] SPACE_ID
3644+
```
3645+
3646+
**Arguments**:
3647+
3648+
* `SPACE_ID`: The space ID (e.g. `username/repo-name`). [required]
3649+
3650+
**Options**:
3651+
3652+
* `--format [agent|auto|human|json|quiet]`: Output format. [default: auto]
3653+
* `--token TEXT`: A User Access Token generated from https://huggingface.co/settings/tokens.
3654+
* `--help`: Show this message and exit.
3655+
3656+
Examples
3657+
$ hf spaces volumes ls username/my-space
3658+
3659+
Learn more
3660+
Use `hf <command> --help` for more information about a command.
3661+
Read the documentation at https://huggingface.co/docs/huggingface_hub/en/guides/cli
3662+
3663+
3664+
#### `hf spaces volumes set`
3665+
3666+
Set (replace) volumes for a Space.
3667+
3668+
**Usage**:
3669+
3670+
```console
3671+
$ hf spaces volumes set [OPTIONS] SPACE_ID
3672+
```
3673+
3674+
**Arguments**:
3675+
3676+
* `SPACE_ID`: The space ID (e.g. `username/repo-name`). [required]
3677+
3678+
**Options**:
3679+
3680+
* `-v, --volume TEXT`: Mount a volume. Format: hf://[TYPE/]SOURCE:/MOUNT_PATH[:ro]. TYPE is one of: models, datasets, spaces, buckets. TYPE defaults to models if omitted. models, datasets and spaces are always mounted read-only. buckets are read+write by default.E.g. -v hf://gpt2:/data or -v hf://datasets/org/ds:/data or -v hf://buckets/org/b:/mnt:ro
3681+
* `--format [agent|auto|human|json|quiet]`: Output format. [default: auto]
3682+
* `--token TEXT`: A User Access Token generated from https://huggingface.co/settings/tokens.
3683+
* `--help`: Show this message and exit.
3684+
3685+
Examples
3686+
$ hf spaces volumes set username/my-space -v hf://models/username/my-model:/models
3687+
$ hf spaces volumes set username/my-space -v hf://buckets/username/my-bucket:/data -v hf://datasets/username/my-dataset:/datasets:ro
3688+
3689+
Learn more
3690+
Use `hf <command> --help` for more information about a command.
3691+
Read the documentation at https://huggingface.co/docs/huggingface_hub/en/guides/cli
3692+
3693+
35853694
## `hf sync`
35863695

35873696
Sync files between local directory and a bucket.

src/huggingface_hub/_space_api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ def to_dict(self) -> dict:
161161
data["path"] = self.path
162162
return data
163163

164+
def to_hf_handle(self) -> str:
165+
"""Return the volume as an HF handle in the format expected by the CLI."""
166+
path = f"/{self.path}" if self.path else ""
167+
revision = f"@{self.revision}" if self.revision else ""
168+
ro = {True: ":ro", False: ":rw", None: ""}.get(self.read_only, "")
169+
return f"hf://{self.type}s/{self.source}{revision}{path}:{self.mount_path}{ro}"
170+
164171

165172
@dataclass
166173
class SpaceHotReloading:

src/huggingface_hub/cli/spaces.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@
5656
RevisionOpt,
5757
SearchOpt,
5858
TokenOpt,
59+
VolumesOpt,
5960
api_object_to_dict,
6061
get_hf_api,
6162
make_expand_properties_parser,
63+
parse_volumes,
6264
typer_factory,
6365
)
6466
from ._output import OutputFormatWithAuto, out
@@ -81,6 +83,8 @@
8183
]
8284

8385
spaces_cli = typer_factory(help="Interact with spaces on the Hub.")
86+
volumes_cli = typer_factory(help="Manage volumes for a Space on the Hub.")
87+
spaces_cli.add_typer(volumes_cli, name="volumes")
8488

8589

8690
@spaces_cli.command(
@@ -482,3 +486,80 @@ def _editor_open(local_path: str) -> int | Literal["no-tty", "no-editor"]:
482486
command = [*shlex.split(editor_command), local_path]
483487
res = subprocess.run(command, start_new_session=True)
484488
return res.returncode
489+
490+
491+
@volumes_cli.command(
492+
"list | ls",
493+
examples=[
494+
"hf spaces volumes ls username/my-space",
495+
],
496+
)
497+
def volumes_ls(
498+
space_id: Annotated[str, typer.Argument(help="The space ID (e.g. `username/repo-name`).")],
499+
format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
500+
token: TokenOpt = None,
501+
) -> None:
502+
"""List volumes mounted in a Space."""
503+
api = get_hf_api(token=token)
504+
info = api.space_info(space_id)
505+
if info.runtime is None:
506+
raise CLIError(f"Runtime not available for Space '{space_id}'.")
507+
volumes = info.runtime.volumes or []
508+
items = [api_object_to_dict(v) for v in volumes]
509+
out.table(items)
510+
out.hint(
511+
f"Use `hf spaces volumes set {space_id} -v hf://<repo_type>/<repo_id>:/<mount_path>` to set volumes for a Space."
512+
)
513+
514+
515+
@volumes_cli.command(
516+
"set",
517+
examples=[
518+
"hf spaces volumes set username/my-space -v hf://models/username/my-model:/models",
519+
"hf spaces volumes set username/my-space -v hf://buckets/username/my-bucket:/data -v hf://datasets/username/my-dataset:/datasets:ro",
520+
],
521+
)
522+
def volumes_set(
523+
space_id: Annotated[str, typer.Argument(help="The space ID (e.g. `username/repo-name`).")],
524+
volume: VolumesOpt = None,
525+
format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
526+
token: TokenOpt = None,
527+
) -> None:
528+
"""Set (replace) volumes for a Space."""
529+
volumes = parse_volumes(volume)
530+
if not volumes:
531+
raise CLIError("At least one volume must be specified with -v/--volume.")
532+
api = get_hf_api(token=token)
533+
api.set_space_volumes(space_id, volumes=volumes)
534+
out.result("Volumes set", space_id=space_id, volumes=[v.to_hf_handle() for v in volumes])
535+
out.hint(f"Use `hf spaces volumes ls {space_id}` to list volumes for a Space.")
536+
537+
538+
@volumes_cli.command(
539+
"delete",
540+
examples=[
541+
"hf spaces volumes delete username/my-space",
542+
"hf spaces volumes delete username/my-space --yes",
543+
],
544+
)
545+
def volumes_delete(
546+
space_id: Annotated[str, typer.Argument(help="The space ID (e.g. `username/repo-name`).")],
547+
yes: Annotated[
548+
bool,
549+
typer.Option(
550+
"-y",
551+
"--yes",
552+
help="Answer Yes to prompt automatically.",
553+
),
554+
] = False,
555+
format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
556+
token: TokenOpt = None,
557+
) -> None:
558+
"""Remove all volumes from a Space."""
559+
out.confirm(f"You are about to remove all volumes from Space '{space_id}'. Proceed?", yes=yes)
560+
api = get_hf_api(token=token)
561+
api.delete_space_volumes(space_id)
562+
out.result("Volumes deleted", space_id=space_id)
563+
out.hint(
564+
f"Use `hf spaces volumes set {space_id} -v hf://<repo_type>/<repo_id>:/<mount_path>` to set volumes for a Space."
565+
)

0 commit comments

Comments
 (0)