Skip to content

Conversation

hanouticelina
Copy link
Contributor

Resolves #3363 and #3364.

This PR migrates the hf CLI from built-in argparse to Typer using idiomatic, function based commands. this significantly reduces boilerplate, making the CLI easier to maintain, less error prone, and faster to extend with new HfApi features.

What changed

  • new dependencies:

    • typer, latest version is 0.17.4, it should be stable enough and it's widely adopted.
    • click, typer s foundation, pure Python, should be stable too. latest version is 8.2.2.
    • ⚠️ rich has been disabled in all cases in commit (#3364) disable rich in all cases to keep a consistent UI regarding of having rich install or not.
  • Main entrypoint: cli/hf.py now uses typer.Typer and mounts:

    • Top level single commands: download, upload, upload-large-folder, env, version (all defined in their respective files).
    • Groups: auth, cache, repo (with tag subgroup), repo-files, jobs (with scheduled and uv subgroups).
    • LFS helpers are still available but hidden in help: lfs-enable-largefiles, lfs-multipart-upload.
    • All argparse classes removed and replaced by Typer commands.
> hf --help
Usage: hf [OPTIONS] COMMAND [ARGS]...

  Hugging Face Hub CLI

Options:
  --help  Show this message and exit.

Commands:
  download             Download files from the Hub
  upload               Upload a file or a folder to the Hub
  upload-large-folder  Upload a large folder to the Hub.
  env                  Print information about the environment.
  version              Print information about the hf version.
  auth                 Manage authentication (login, logout, etc.)
  cache                Manage local cache directory.
  repo                 Manage repos on the Hub.
  repo-files           Manage files in a repo on the Hub.
  jobs                 Run and manage Jobs on the Hub.

Copy link
Contributor

@Wauplin Wauplin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very excited about this switch! I left some very high level comments on code organization :)

Comment on lines +60 to +61
def _api() -> HfApi:
return HfApi()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really relevant, let's do a whoami() call directly

Comment on lines +70 to +74
dir: Optional[str] = typer.Option(
None,
"--dir",
help="Cache directory to scan (defaults to Hugging Face cache).",
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
dir: Optional[str] = typer.Option(
None,
"--dir",
help="Cache directory to scan (defaults to Hugging Face cache).",
),
dir: Annotated[Optional[str], typer.Option(help="Cache directory to scan (defaults to Hugging Face cache).")] = None

from what I understand in https://typer.tiangolo.com/tutorial/options/help/, the recommended way to use Typer is to define options with an Annotated annotation.

image

Also, providing --dir for the dir argument is redundant (and therefore can be avoided)

(same comment on all commands)

Comment on lines +80 to +92
help="Increase verbosity (-v, -vv, -vvv).",
),
) -> None:
_run_scan(cache_dir=dir, verbosity=verbose)


def _run_scan(cache_dir: Optional[str], verbosity: int) -> None:
try:
t0 = time.time()
hf_cache_info = scan_cache_dir(cache_dir)
t1 = time.time()
except CacheNotFound as exc:
cache_dir = exc.cache_dir
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid defining a new internal alias here. A good thing with Typer is that we shouldn't need to define twice some arguments type annotations (and therefore reduce risks of conflicts)

Suggested change
help="Increase verbosity (-v, -vv, -vvv).",
),
) -> None:
_run_scan(cache_dir=dir, verbosity=verbose)
def _run_scan(cache_dir: Optional[str], verbosity: int) -> None:
try:
t0 = time.time()
hf_cache_info = scan_cache_dir(cache_dir)
t1 = time.time()
except CacheNotFound as exc:
cache_dir = exc.cache_dir
help="Increase verbosity (-v, -vv, -vvv).",
),
) -> None:
try:
t0 = time.time()
hf_cache_info = scan_cache_dir(cache_dir)
t1 = time.time()
except CacheNotFound as exc:
cache_dir = exc.cache_dir

@@ -28,6 +28,7 @@ def get_version() -> str:

extras["cli"] = [
"InquirerPy==0.3.4", # Note: installs `prompt-toolkit` in the background
"typer",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move typer to a required dependency otherwise doing pip install huggingface_hub would install a CLI but unusable (since typer would be missing).

To reduce dependency overhead, especially the rich package, let's add typer-slim which is a lighter version without rich / shellingham included. More details here: https://typer.tiangolo.com/?h=typer+slim#typer-slim

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About shellingham I still think it's good to have it. Either by default or in the (soon-to-come?) "hf installer script". It's a pure-python package that helps detecting the current shell to install shell completion correctly.

(for now let's skip it but mentioning it to have it in mind)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants