| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- # Copyright 2025 The HuggingFace Team. All rights reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """Contains commands to interact with repositories on the Hugging Face Hub.
- Usage:
- # create a new dataset repo on the Hub
- hf repos create my-cool-dataset --repo-type=dataset
- # create a private model repo on the Hub
- hf repos create my-cool-model --private
- # delete files from a repo on the Hub
- hf repos delete-files my-model file.txt
- """
- import enum
- from typing import Annotated
- import typer
- from huggingface_hub import SpaceHardware, SpaceStorage
- from huggingface_hub.errors import CLIError, HfHubHTTPError, RepositoryNotFoundError, RevisionNotFoundError
- from ._cli_utils import (
- EnvFileOpt,
- EnvOpt,
- FormatWithAutoOpt,
- PrivateOpt,
- RepoIdArg,
- RepoType,
- RepoTypeOpt,
- RevisionOpt,
- SecretsFileOpt,
- SecretsOpt,
- TokenOpt,
- VolumesOpt,
- env_map_to_key_value_list,
- get_hf_api,
- parse_env_map,
- parse_volumes,
- typer_factory,
- )
- from ._output import OutputFormatWithAuto, out
- repos_cli = typer_factory(help="Manage repos on the Hub.")
- @repos_cli.callback(invoke_without_command=True)
- def _repos_callback(ctx: typer.Context) -> None:
- if ctx.info_name == "repo":
- out.warning("`hf repo` is deprecated in favor of `hf repos`.")
- tag_cli = typer_factory(help="Manage tags for a repo on the Hub.")
- branch_cli = typer_factory(help="Manage branches for a repo on the Hub.")
- repos_cli.add_typer(tag_cli, name="tag")
- repos_cli.add_typer(branch_cli, name="branch")
- class GatedChoices(str, enum.Enum):
- auto = "auto"
- manual = "manual"
- false = "false"
- PublicOpt = Annotated[
- bool | None,
- typer.Option(
- "--public",
- help="Whether to make the repo public. Ignored if the repo already exists.",
- ),
- ]
- ProtectedOpt = Annotated[
- bool | None,
- typer.Option(
- "--protected",
- help="Whether to make the Space protected (Spaces only). Ignored if the repo already exists.",
- ),
- ]
- SpaceHardwareOpt = Annotated[
- SpaceHardware | None,
- typer.Option(
- "--flavor",
- help="Space hardware flavor (e.g. 'cpu-basic', 't4-medium', 'l4x4'). Only for Spaces.",
- ),
- ]
- SpaceStorageOpt = Annotated[
- SpaceStorage | None,
- typer.Option(
- "--storage",
- help="(Deprecated, use volumes instead) Space persistent storage tier ('small', 'medium', or 'large'). Only for Spaces.",
- ),
- ]
- SpaceSleepTimeOpt = Annotated[
- int | None,
- typer.Option(
- "--sleep-time",
- help="Seconds of inactivity before the Space is put to sleep. Use -1 to disable. Only for Spaces.",
- ),
- ]
- @repos_cli.command(
- "create",
- examples=[
- "hf repos create my-model",
- "hf repos create my-dataset --repo-type dataset --private",
- "hf repos create my-space --type space --space-sdk gradio --flavor t4-medium --secrets HF_TOKEN -e THEME=dark --protected",
- "hf repos create my-space --type space --space-sdk gradio -v hf://gpt2:/models -v hf://buckets/org/b:/data",
- ],
- )
- def repo_create(
- repo_id: RepoIdArg,
- repo_type: RepoTypeOpt = RepoType.model,
- space_sdk: Annotated[
- str | None,
- typer.Option(
- help="Hugging Face Spaces SDK type. Required when --type is set to 'space'.",
- ),
- ] = None,
- private: PrivateOpt = None,
- public: PublicOpt = None,
- protected: ProtectedOpt = None,
- token: TokenOpt = None,
- exist_ok: Annotated[
- bool,
- typer.Option(
- help="Do not raise an error if repo already exists.",
- ),
- ] = False,
- resource_group_id: Annotated[
- str | None,
- typer.Option(
- help="Resource group in which to create the repo. Resource groups is only available for Enterprise Hub organizations.",
- ),
- ] = None,
- hardware: SpaceHardwareOpt = None,
- storage: SpaceStorageOpt = None,
- sleep_time: SpaceSleepTimeOpt = None,
- secrets: SecretsOpt = None,
- secrets_file: SecretsFileOpt = None,
- env: EnvOpt = None,
- env_file: EnvFileOpt = None,
- volume: VolumesOpt = None,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Create a new repo on the Hub."""
- api = get_hf_api(token=token)
- repo_url = api.create_repo(
- repo_id=repo_id,
- repo_type=repo_type.value,
- visibility="private" if private else "public" if public else "protected" if protected else None, # type: ignore [arg-type]
- token=token,
- exist_ok=exist_ok,
- resource_group_id=resource_group_id,
- space_sdk=space_sdk,
- space_hardware=hardware,
- space_storage=storage,
- space_sleep_time=sleep_time,
- space_secrets=env_map_to_key_value_list(parse_env_map(secrets, secrets_file)),
- space_variables=env_map_to_key_value_list(parse_env_map(env, env_file)),
- space_volumes=parse_volumes(volume),
- )
- out.result("Repo created", repo_id=repo_url.repo_id, url=str(repo_url))
- @repos_cli.command(
- "duplicate",
- examples=[
- "hf repos duplicate openai/gdpval --type dataset",
- "hf repos duplicate multimodalart/dreambooth-training my-dreambooth --type space --flavor l4x4 --secrets HF_TOKEN --private",
- "hf repos duplicate org/my-space my-space --type space -v hf://gpt2:/models -v hf://buckets/org/b:/data",
- ],
- )
- def repo_duplicate(
- from_id: RepoIdArg,
- to_id: Annotated[
- str | None,
- typer.Argument(
- help="Destination repo ID (e.g. `myorg/my-copy`). Defaults to your namespace with the same repo name.",
- ),
- ] = None,
- repo_type: RepoTypeOpt = RepoType.model,
- private: PrivateOpt = None,
- public: PublicOpt = None,
- protected: ProtectedOpt = None,
- token: TokenOpt = None,
- exist_ok: Annotated[
- bool,
- typer.Option(
- help="Do not raise an error if repo already exists.",
- ),
- ] = False,
- hardware: SpaceHardwareOpt = None,
- storage: SpaceStorageOpt = None,
- sleep_time: SpaceSleepTimeOpt = None,
- secrets: SecretsOpt = None,
- secrets_file: SecretsFileOpt = None,
- env: EnvOpt = None,
- env_file: EnvFileOpt = None,
- volume: VolumesOpt = None,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Duplicate a repo on the Hub (model, dataset, or Space)."""
- api = get_hf_api(token=token)
- repo_url = api.duplicate_repo(
- from_id=from_id,
- to_id=to_id,
- repo_type=repo_type.value,
- visibility="private" if private else "public" if public else "protected" if protected else None, # type: ignore [arg-type]
- token=token,
- exist_ok=exist_ok,
- space_hardware=hardware,
- space_storage=storage,
- space_sleep_time=sleep_time,
- space_secrets=env_map_to_key_value_list(parse_env_map(secrets, secrets_file)),
- space_variables=env_map_to_key_value_list(parse_env_map(env, env_file)),
- space_volumes=parse_volumes(volume),
- )
- out.result("Repo duplicated", from_id=from_id, to_id=repo_url.repo_id, url=str(repo_url))
- @repos_cli.command("delete", examples=["hf repos delete my-model"])
- def repo_delete(
- repo_id: RepoIdArg,
- repo_type: RepoTypeOpt = RepoType.model,
- token: TokenOpt = None,
- missing_ok: Annotated[
- bool,
- typer.Option(
- help="If set to True, do not raise an error if repo does not exist.",
- ),
- ] = False,
- yes: Annotated[
- bool,
- typer.Option(
- "-y",
- "--yes",
- help="Answer Yes to prompt automatically.",
- ),
- ] = False,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Delete a repo from the Hub. This is an irreversible operation."""
- out.confirm(f"You are about to permanently delete {repo_type.value} '{repo_id}'. Proceed?", yes=yes)
- api = get_hf_api(token=token)
- api.delete_repo(
- repo_id=repo_id,
- repo_type=repo_type.value,
- missing_ok=missing_ok,
- )
- out.result("Repo deleted", repo_id=repo_id)
- @repos_cli.command("move", examples=["hf repos move old-namespace/my-model new-namespace/my-model"])
- def repo_move(
- from_id: RepoIdArg,
- to_id: RepoIdArg,
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Move a repository from a namespace to another namespace."""
- api = get_hf_api(token=token)
- api.move_repo(
- from_id=from_id,
- to_id=to_id,
- repo_type=repo_type.value,
- )
- out.result("Repo moved", from_id=from_id, to_id=to_id)
- @repos_cli.command(
- "settings",
- examples=[
- "hf repos settings my-model --private",
- "hf repos settings my-model --gated auto",
- "hf repos settings my-space --repo-type space --protected",
- ],
- )
- def repo_settings(
- repo_id: RepoIdArg,
- gated: Annotated[
- GatedChoices | None,
- typer.Option(
- help="The gated status for the repository.",
- ),
- ] = None,
- private: PrivateOpt = None,
- public: PublicOpt = None,
- protected: ProtectedOpt = None,
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Update the settings of a repository."""
- api = get_hf_api(token=token)
- api.update_repo_settings(
- repo_id=repo_id,
- gated=(None if gated is None else False if gated is GatedChoices.false else gated.value),
- visibility="private" if private else "public" if public else "protected" if protected else None, # type: ignore [arg-type]
- repo_type=repo_type.value,
- )
- out.result("Repo settings updated", repo_id=repo_id)
- @repos_cli.command(
- "delete-files",
- examples=[
- "hf repos delete-files my-model file.txt",
- 'hf repos delete-files my-model "*.json"',
- "hf repos delete-files my-model folder/",
- ],
- )
- def repo_delete_files(
- repo_id: RepoIdArg,
- patterns: Annotated[
- list[str],
- typer.Argument(
- help="Glob patterns to match files to delete. Based on fnmatch, '*' matches files recursively.",
- ),
- ],
- repo_type: RepoTypeOpt = RepoType.model,
- revision: RevisionOpt = None,
- commit_message: Annotated[
- str | None,
- typer.Option(
- help="The summary / title / first line of the generated commit.",
- ),
- ] = None,
- commit_description: Annotated[
- str | None,
- typer.Option(
- help="The description of the generated commit.",
- ),
- ] = None,
- create_pr: Annotated[
- bool,
- typer.Option(
- help="Whether to create a new Pull Request for these changes.",
- ),
- ] = False,
- token: TokenOpt = None,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Delete files from a repo on the Hub."""
- api = get_hf_api(token=token)
- url = api.delete_files(
- delete_patterns=patterns,
- repo_id=repo_id,
- repo_type=repo_type.value,
- revision=revision,
- commit_message=commit_message,
- commit_description=commit_description,
- create_pr=create_pr,
- )
- out.result("Files deleted", repo_id=repo_id, commit_url=url)
- @branch_cli.command(
- "create",
- examples=[
- "hf repos branch create my-model dev",
- "hf repos branch create my-model dev --revision abc123",
- ],
- )
- def branch_create(
- repo_id: RepoIdArg,
- branch: Annotated[
- str,
- typer.Argument(
- help="The name of the branch to create.",
- ),
- ],
- revision: RevisionOpt = None,
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- exist_ok: Annotated[
- bool,
- typer.Option(
- help="If set to True, do not raise an error if branch already exists.",
- ),
- ] = False,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Create a new branch for a repo on the Hub."""
- api = get_hf_api(token=token)
- api.create_branch(
- repo_id=repo_id,
- branch=branch,
- revision=revision,
- repo_type=repo_type.value,
- exist_ok=exist_ok,
- )
- out.result("Branch created", branch=branch, repo_type=repo_type.value, repo_id=repo_id)
- @branch_cli.command("delete", examples=["hf repos branch delete my-model dev"])
- def branch_delete(
- repo_id: RepoIdArg,
- branch: Annotated[
- str,
- typer.Argument(
- help="The name of the branch to delete.",
- ),
- ],
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Delete a branch from a repo on the Hub."""
- api = get_hf_api(token=token)
- api.delete_branch(
- repo_id=repo_id,
- branch=branch,
- repo_type=repo_type.value,
- )
- out.result("Branch deleted", branch=branch, repo_type=repo_type.value, repo_id=repo_id)
- @tag_cli.command(
- "create",
- examples=[
- "hf repos tag create my-model v1.0",
- 'hf repos tag create my-model v1.0 -m "First release"',
- ],
- )
- def tag_create(
- repo_id: RepoIdArg,
- tag: Annotated[
- str,
- typer.Argument(
- help="The name of the tag to create.",
- ),
- ],
- message: Annotated[
- str | None,
- typer.Option(
- "-m",
- "--message",
- help="The description of the tag to create.",
- ),
- ] = None,
- revision: RevisionOpt = None,
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Create a tag for a repo."""
- repo_type_str = repo_type.value
- api = get_hf_api(token=token)
- try:
- api.create_tag(repo_id=repo_id, tag=tag, tag_message=message, revision=revision, repo_type=repo_type_str)
- except RepositoryNotFoundError as e:
- raise CLIError(f"{repo_type_str.capitalize()} '{repo_id}' not found.") from e
- except RevisionNotFoundError as e:
- raise CLIError(f"Revision '{revision}' not found.") from e
- except HfHubHTTPError as e:
- if e.response.status_code == 409:
- raise CLIError(f"Tag '{tag}' already exists on '{repo_id}'.") from e
- raise
- out.result("Tag created", tag=tag, repo_type=repo_type_str, repo_id=repo_id)
- @tag_cli.command("list | ls", examples=["hf repos tag list my-model"])
- def tag_list(
- repo_id: RepoIdArg,
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """List tags for a repo."""
- repo_type_str = repo_type.value
- api = get_hf_api(token=token)
- try:
- refs = api.list_repo_refs(repo_id=repo_id, repo_type=repo_type_str)
- except RepositoryNotFoundError as e:
- raise CLIError(f"{repo_type_str.capitalize()} '{repo_id}' not found.") from e
- items = [{"name": t.name, "target_commit": t.target_commit, "ref": t.ref} for t in refs.tags]
- out.table(items)
- @tag_cli.command("delete", examples=["hf repos tag delete my-model v1.0"])
- def tag_delete(
- repo_id: RepoIdArg,
- tag: Annotated[
- str,
- typer.Argument(
- help="The name of the tag to delete.",
- ),
- ],
- yes: Annotated[
- bool,
- typer.Option(
- "-y",
- "--yes",
- help="Answer Yes to prompt automatically",
- ),
- ] = False,
- token: TokenOpt = None,
- repo_type: RepoTypeOpt = RepoType.model,
- format: FormatWithAutoOpt = OutputFormatWithAuto.auto,
- ) -> None:
- """Delete a tag for a repo."""
- repo_type_str = repo_type.value
- out.text(f"You are about to delete tag {tag} on {repo_type_str} {repo_id}")
- out.confirm("Proceed?", yes=yes)
- api = get_hf_api(token=token)
- try:
- api.delete_tag(repo_id=repo_id, tag=tag, repo_type=repo_type_str)
- except RepositoryNotFoundError as e:
- raise CLIError(f"{repo_type_str.capitalize()} '{repo_id}' not found.") from e
- except RevisionNotFoundError as e:
- raise CLIError(f"Tag '{tag}' not found on '{repo_id}'.") from e
- out.result("Tag deleted", tag=tag, repo_type=repo_type_str, repo_id=repo_id)
|