修改为东南天坐标系

This commit is contained in:
2026-01-20 09:49:52 +08:00
parent 9538757047
commit 333fad40ac
7201 changed files with 1030888 additions and 85410 deletions

View File

@@ -46,7 +46,7 @@ import sys
from typing import TYPE_CHECKING
__version__ = "1.2.4"
__version__ = "1.3.2"
# Alphabetical order of definitions is ensured in tests
# WARNING: any comment added in this dictionary definition will be lost when
@@ -112,6 +112,7 @@ _SUBMOD_ATTRS = {
"webhook_endpoint",
],
"cli._cli_utils": [
"check_cli_update",
"typer_factory",
],
"community": [
@@ -213,12 +214,14 @@ _SUBMOD_ATTRS = {
"edit_discussion_comment",
"enable_webhook",
"fetch_job_logs",
"fetch_job_metrics",
"file_exists",
"get_collection",
"get_dataset_tags",
"get_discussion_details",
"get_full_repo_name",
"get_inference_endpoint",
"get_local_safetensors_metadata",
"get_model_tags",
"get_organization_overview",
"get_paths_info",
@@ -259,6 +262,7 @@ _SUBMOD_ATTRS = {
"model_info",
"move_repo",
"paper_info",
"parse_local_safetensors_file_metadata",
"parse_safetensors_file_metadata",
"pause_inference_endpoint",
"pause_space",
@@ -380,6 +384,14 @@ _SUBMOD_ATTRS = {
"ImageSegmentationOutputElement",
"ImageSegmentationParameters",
"ImageSegmentationSubtask",
"ImageTextToImageInput",
"ImageTextToImageOutput",
"ImageTextToImageParameters",
"ImageTextToImageTargetSize",
"ImageTextToVideoInput",
"ImageTextToVideoOutput",
"ImageTextToVideoParameters",
"ImageTextToVideoTargetSize",
"ImageToImageInput",
"ImageToImageOutput",
"ImageToImageParameters",
@@ -665,6 +677,14 @@ __all__ = [
"ImageSegmentationOutputElement",
"ImageSegmentationParameters",
"ImageSegmentationSubtask",
"ImageTextToImageInput",
"ImageTextToImageOutput",
"ImageTextToImageParameters",
"ImageTextToImageTargetSize",
"ImageTextToVideoInput",
"ImageTextToVideoOutput",
"ImageTextToVideoParameters",
"ImageTextToVideoTargetSize",
"ImageToImageInput",
"ImageToImageOutput",
"ImageToImageParameters",
@@ -828,6 +848,7 @@ __all__ = [
"cancel_access_request",
"cancel_job",
"change_discussion_status",
"check_cli_update",
"close_session",
"comment_discussion",
"create_branch",
@@ -864,6 +885,7 @@ __all__ = [
"export_entries_as_dduf",
"export_folder_as_dduf",
"fetch_job_logs",
"fetch_job_metrics",
"file_exists",
"from_pretrained_fastai",
"get_async_session",
@@ -873,6 +895,7 @@ __all__ = [
"get_full_repo_name",
"get_hf_file_metadata",
"get_inference_endpoint",
"get_local_safetensors_metadata",
"get_model_tags",
"get_organization_overview",
"get_paths_info",
@@ -934,6 +957,7 @@ __all__ = [
"notebook_login",
"paper_info",
"parse_huggingface_oauth",
"parse_local_safetensors_file_metadata",
"parse_safetensors_file_metadata",
"pause_inference_endpoint",
"pause_space",
@@ -1139,7 +1163,10 @@ if TYPE_CHECKING: # pragma: no cover
WebhooksServer, # noqa: F401
webhook_endpoint, # noqa: F401
)
from .cli._cli_utils import typer_factory # noqa: F401
from .cli._cli_utils import (
check_cli_update, # noqa: F401
typer_factory, # noqa: F401
)
from .community import (
Discussion, # noqa: F401
DiscussionComment, # noqa: F401
@@ -1239,12 +1266,14 @@ if TYPE_CHECKING: # pragma: no cover
edit_discussion_comment, # noqa: F401
enable_webhook, # noqa: F401
fetch_job_logs, # noqa: F401
fetch_job_metrics, # noqa: F401
file_exists, # noqa: F401
get_collection, # noqa: F401
get_dataset_tags, # noqa: F401
get_discussion_details, # noqa: F401
get_full_repo_name, # noqa: F401
get_inference_endpoint, # noqa: F401
get_local_safetensors_metadata, # noqa: F401
get_model_tags, # noqa: F401
get_organization_overview, # noqa: F401
get_paths_info, # noqa: F401
@@ -1285,6 +1314,7 @@ if TYPE_CHECKING: # pragma: no cover
model_info, # noqa: F401
move_repo, # noqa: F401
paper_info, # noqa: F401
parse_local_safetensors_file_metadata, # noqa: F401
parse_safetensors_file_metadata, # noqa: F401
pause_inference_endpoint, # noqa: F401
pause_space, # noqa: F401
@@ -1404,6 +1434,14 @@ if TYPE_CHECKING: # pragma: no cover
ImageSegmentationOutputElement, # noqa: F401
ImageSegmentationParameters, # noqa: F401
ImageSegmentationSubtask, # noqa: F401
ImageTextToImageInput, # noqa: F401
ImageTextToImageOutput, # noqa: F401
ImageTextToImageParameters, # noqa: F401
ImageTextToImageTargetSize, # noqa: F401
ImageTextToVideoInput, # noqa: F401
ImageTextToVideoOutput, # noqa: F401
ImageTextToVideoParameters, # noqa: F401
ImageTextToVideoTargetSize, # noqa: F401
ImageToImageInput, # noqa: F401
ImageToImageOutput, # noqa: F401
ImageToImageParameters, # noqa: F401

View File

@@ -13,17 +13,19 @@
# limitations under the License.
"""Contains CLI utilities (styling, helpers)."""
import dataclasses
import datetime
import importlib.metadata
import os
import time
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Optional
from typing import TYPE_CHECKING, Annotated, Literal, Optional, Union
import click
import typer
from huggingface_hub import __version__, constants
from huggingface_hub import DatasetInfo, ModelInfo, SpaceInfo, __version__, constants
from huggingface_hub.utils import ANSI, get_session, hf_raise_for_status, installation_method, logging
@@ -110,28 +112,78 @@ RevisionOpt = Annotated[
]
LimitOpt = Annotated[
int,
typer.Option(help="Limit the number of results."),
]
AuthorOpt = Annotated[
Optional[str],
typer.Option(help="Filter by author or organization."),
]
FilterOpt = Annotated[
Optional[list[str]],
typer.Option(help="Filter by tags (e.g. 'text-classification'). Can be used multiple times."),
]
SearchOpt = Annotated[
Optional[str],
typer.Option(help="Search query."),
]
def repo_info_to_dict(info: Union[ModelInfo, DatasetInfo, SpaceInfo]) -> dict[str, object]:
"""Convert repo info dataclasses to json-serializable dicts."""
return {
k: v.isoformat() if isinstance(v, datetime.datetime) else v
for k, v in dataclasses.asdict(info).items()
if v is not None
}
def make_expand_properties_parser(valid_properties: list[str]):
"""Create a callback to parse and validate comma-separated expand properties."""
def _parse_expand_properties(value: Optional[str]) -> Optional[list[str]]:
if value is None:
return None
properties = [p.strip() for p in value.split(",")]
for prop in properties:
if prop not in valid_properties:
raise typer.BadParameter(
f"Invalid expand property: '{prop}'. Valid values are: {', '.join(valid_properties)}"
)
return properties
return _parse_expand_properties
### PyPI VERSION CHECKER
def check_cli_update() -> None:
def check_cli_update(library: Literal["huggingface_hub", "transformers"]) -> None:
"""
Check whether a newer version of `huggingface_hub` is available on PyPI.
Check whether a newer version of a library is available on PyPI.
If a newer version is found, notify the user and suggest updating.
If current version is a pre-release (e.g. `1.0.0.rc1`), or a dev version (e.g. `1.0.0.dev1`), no check is performed.
This function is called at the entry point of the CLI. It only performs the check once every 24 hours, and any error
during the check is caught and logged, to avoid breaking the CLI.
Args:
library: The library to check for updates. Currently supports "huggingface_hub" and "transformers".
"""
try:
_check_cli_update()
_check_cli_update(library)
except Exception:
# We don't want the CLI to fail on version checks, no matter the reason.
logger.debug("Error while checking for CLI update.", exc_info=True)
def _check_cli_update() -> None:
current_version = importlib.metadata.version("huggingface_hub")
def _check_cli_update(library: Literal["huggingface_hub", "transformers"]) -> None:
current_version = importlib.metadata.version(library)
# Skip if current version is a pre-release or dev version
if any(tag in current_version for tag in ["rc", "dev"]):
@@ -148,27 +200,46 @@ def _check_cli_update() -> None:
Path(constants.CHECK_FOR_UPDATE_DONE_PATH).touch()
# Check latest version from PyPI
response = get_session().get("https://pypi.org/pypi/huggingface_hub/json", timeout=2)
response = get_session().get(f"https://pypi.org/pypi/{library}/json", timeout=2)
hf_raise_for_status(response)
data = response.json()
latest_version = data["info"]["version"]
# If latest version is different from current, notify user
if current_version != latest_version:
method = installation_method()
if method == "brew":
update_command = "brew upgrade huggingface-cli"
elif method == "hf_installer" and os.name == "nt":
update_command = 'powershell -NoProfile -Command "iwr -useb https://hf.co/cli/install.ps1 | iex"'
elif method == "hf_installer":
update_command = "curl -LsSf https://hf.co/cli/install.sh | bash -"
else: # unknown => likely pip
update_command = "pip install -U huggingface_hub"
if library == "huggingface_hub":
update_command = _get_huggingface_hub_update_command()
else:
update_command = _get_transformers_update_command()
click.echo(
ANSI.yellow(
f"A new version of huggingface_hub ({latest_version}) is available! "
f"A new version of {library} ({latest_version}) is available! "
f"You are using version {current_version}.\n"
f"To update, run: {ANSI.bold(update_command)}\n",
)
)
def _get_huggingface_hub_update_command() -> str:
"""Return the command to update huggingface_hub."""
method = installation_method()
if method == "brew":
return "brew upgrade huggingface-cli"
elif method == "hf_installer" and os.name == "nt":
return 'powershell -NoProfile -Command "iwr -useb https://hf.co/cli/install.ps1 | iex"'
elif method == "hf_installer":
return "curl -LsSf https://hf.co/cli/install.sh | bash -"
else: # unknown => likely pip
return "pip install -U huggingface_hub"
def _get_transformers_update_command() -> str:
"""Return the command to update transformers."""
method = installation_method()
if method == "hf_installer" and os.name == "nt":
return 'powershell -NoProfile -Command "iwr -useb https://hf.co/cli/install.ps1 | iex" -WithTransformers'
elif method == "hf_installer":
return "curl -LsSf https://hf.co/cli/install.sh | bash -s -- --with-transformers"
else: # brew/unknown => likely pip
return "pip install -U transformers"

View File

@@ -0,0 +1,110 @@
# Copyright 2026 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 datasets on the Hugging Face Hub.
Usage:
# list datasets on the Hub
hf datasets ls
# list datasets with a search query
hf datasets ls --search "code"
# get info about a dataset
hf datasets info HuggingFaceFW/fineweb
"""
import enum
import json
from typing import Annotated, Optional, get_args
import typer
from huggingface_hub.errors import RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.hf_api import DatasetSort_T, ExpandDatasetProperty_T
from huggingface_hub.utils import ANSI
from ._cli_utils import (
AuthorOpt,
FilterOpt,
LimitOpt,
RevisionOpt,
SearchOpt,
TokenOpt,
get_hf_api,
make_expand_properties_parser,
repo_info_to_dict,
typer_factory,
)
_EXPAND_PROPERTIES = sorted(get_args(ExpandDatasetProperty_T))
_SORT_OPTIONS = get_args(DatasetSort_T)
DatasetSortEnum = enum.Enum("DatasetSortEnum", {s: s for s in _SORT_OPTIONS}, type=str) # type: ignore[misc]
ExpandOpt = Annotated[
Optional[str],
typer.Option(
help=f"Comma-separated properties to expand. Example: '--expand=downloads,likes,tags'. Valid: {', '.join(_EXPAND_PROPERTIES)}.",
callback=make_expand_properties_parser(_EXPAND_PROPERTIES),
),
]
datasets_cli = typer_factory(help="Interact with datasets on the Hub.")
@datasets_cli.command("ls")
def datasets_ls(
search: SearchOpt = None,
author: AuthorOpt = None,
filter: FilterOpt = None,
sort: Annotated[
Optional[DatasetSortEnum],
typer.Option(help="Sort results."),
] = None,
limit: LimitOpt = 10,
expand: ExpandOpt = None,
token: TokenOpt = None,
) -> None:
"""List datasets on the Hub."""
api = get_hf_api(token=token)
sort_key = sort.value if sort else None
results = [
repo_info_to_dict(dataset_info)
for dataset_info in api.list_datasets(
filter=filter, author=author, search=search, sort=sort_key, limit=limit, expand=expand
)
]
print(json.dumps(results, indent=2))
@datasets_cli.command("info")
def datasets_info(
dataset_id: Annotated[str, typer.Argument(help="The dataset ID (e.g. `username/repo-name`).")],
revision: RevisionOpt = None,
expand: ExpandOpt = None,
token: TokenOpt = None,
) -> None:
"""Get info about a dataset on the Hub."""
api = get_hf_api(token=token)
try:
info = api.dataset_info(repo_id=dataset_id, revision=revision, expand=expand) # type: ignore[arg-type]
except RepositoryNotFoundError:
print(f"Dataset {ANSI.bold(dataset_id)} not found.")
raise typer.Exit(code=1)
except RevisionNotFoundError:
print(f"Revision {ANSI.bold(str(revision))} not found on {ANSI.bold(dataset_id)}.")
raise typer.Exit(code=1)
print(json.dumps(repo_info_to_dict(info), indent=2))

View File

@@ -83,7 +83,7 @@ def download(
local_dir: Annotated[
Optional[str],
typer.Option(
help="If set, the downloaded file will be placed under this directory. Check out https://huggingface.co/docs/huggingface_hub/guides/download#download-files-to-local-folder for more details.",
help="If set, the downloaded file will be placed under this directory. Check out https://huggingface.co/docs/huggingface_hub/guides/download#download-files-to-a-local-folder for more details.",
),
] = None,
force_download: Annotated[

View File

@@ -17,12 +17,15 @@ from huggingface_hub import constants
from huggingface_hub.cli._cli_utils import check_cli_update, typer_factory
from huggingface_hub.cli.auth import auth_cli
from huggingface_hub.cli.cache import cache_cli
from huggingface_hub.cli.datasets import datasets_cli
from huggingface_hub.cli.download import download
from huggingface_hub.cli.inference_endpoints import ie_cli
from huggingface_hub.cli.jobs import jobs_cli
from huggingface_hub.cli.lfs import lfs_enable_largefiles, lfs_multipart_upload
from huggingface_hub.cli.models import models_cli
from huggingface_hub.cli.repo import repo_cli
from huggingface_hub.cli.repo_files import repo_files_cli
from huggingface_hub.cli.spaces import spaces_cli
from huggingface_hub.cli.system import env, version
from huggingface_hub.cli.upload import upload
from huggingface_hub.cli.upload_large_folder import upload_large_folder
@@ -45,16 +48,19 @@ app.command(help="Upload large files to the Hub.", hidden=True)(lfs_multipart_up
# command groups
app.add_typer(auth_cli, name="auth")
app.add_typer(cache_cli, name="cache")
app.add_typer(datasets_cli, name="datasets")
app.add_typer(jobs_cli, name="jobs")
app.add_typer(models_cli, name="models")
app.add_typer(repo_cli, name="repo")
app.add_typer(repo_files_cli, name="repo-files")
app.add_typer(jobs_cli, name="jobs")
app.add_typer(spaces_cli, name="spaces")
app.add_typer(ie_cli, name="endpoints")
def main():
if not constants.HF_DEBUG:
logging.set_verbosity_info()
check_cli_update()
check_cli_update("huggingface_hub")
app()

View File

@@ -23,6 +23,9 @@ Usage:
# Stream logs from a job
hf jobs logs <job-id>
# Stream resources usage stats and metrics from a job
hf jobs stats <job-id>
# Inspect detailed information about a job
hf jobs inspect <job-id>
@@ -53,17 +56,22 @@ Usage:
"""
import json
import multiprocessing
import multiprocessing.pool
import os
import re
import time
from dataclasses import asdict
from pathlib import Path
from typing import Annotated, Dict, Optional, Union
from queue import Empty, Queue
from typing import Annotated, Any, Callable, Dict, Iterable, Optional, TypeVar, Union
import typer
from huggingface_hub import SpaceHardware, get_token
from huggingface_hub.errors import HfHubHTTPError
from huggingface_hub.utils import logging
from huggingface_hub.utils._cache_manager import _format_size
from huggingface_hub.utils._dotenv import load_dotenv
from ._cli_utils import TokenOpt, get_hf_api, typer_factory
@@ -72,6 +80,7 @@ from ._cli_utils import TokenOpt, get_hf_api, typer_factory
logger = logging.get_logger(__name__)
SUGGESTED_FLAVORS = [item.value for item in SpaceHardware if item.value != "zero-a10g"]
STATS_UPDATE_MIN_INTERVAL = 0.1 # we set a limit here since there is one update per second per job
# Common job-related options
ImageArg = Annotated[
@@ -217,6 +226,13 @@ JobIdArg = Annotated[
),
]
JobIdsArg = Annotated[
Optional[list[str]],
typer.Argument(
help="Job IDs",
),
]
ScheduledJobIdArg = Annotated[
str,
typer.Argument(
@@ -224,18 +240,11 @@ ScheduledJobIdArg = Annotated[
),
]
RepoOpt = Annotated[
Optional[str],
typer.Option(
help="Repository name for the script (creates ephemeral if not specified)",
),
]
jobs_cli = typer_factory(help="Run and manage Jobs on the Hub.")
@jobs_cli.command("run", help="Run a Job")
@jobs_cli.command("run", help="Run a Job", context_settings={"ignore_unknown_options": True})
def jobs_run(
image: ImageArg,
command: CommandArg,
@@ -312,14 +321,16 @@ def _matches_filters(job_properties: dict[str, str], filters: dict[str, str]) ->
return True
def _print_output(rows: list[list[Union[str, int]]], headers: list[str], fmt: Optional[str]) -> None:
def _print_output(
rows: list[list[Union[str, int]]], headers: list[str], aliases: list[str], fmt: Optional[str]
) -> None:
"""Print output according to the chosen format."""
if fmt:
# Use custom template if provided
template = fmt
for row in rows:
line = template
for i, field in enumerate(["id", "image", "command", "created", "status"]):
for i, field in enumerate(aliases):
placeholder = f"{{{{.{field}}}}}"
if placeholder in line:
line = line.replace(placeholder, str(row[i]))
@@ -329,6 +340,112 @@ def _print_output(rows: list[list[Union[str, int]]], headers: list[str], fmt: Op
print(_tabulate(rows, headers=headers))
def _clear_line(n: int) -> None:
LINE_UP = "\033[1A"
LINE_CLEAR = "\x1b[2K"
for i in range(n):
print(LINE_UP, end=LINE_CLEAR)
def _get_jobs_stats_rows(
job_id: str, metrics_stream: Iterable[dict[str, Any]], table_headers: list[str]
) -> Iterable[tuple[bool, str, list[list[Union[str, int]]]]]:
for metrics in metrics_stream:
row = [
job_id,
f"{metrics['cpu_usage_pct']}%",
round(metrics["cpu_millicores"] / 1000.0, 1),
f"{round(100 * metrics['memory_used_bytes'] / metrics['memory_total_bytes'], 2)}%",
f"{_format_size(metrics['memory_used_bytes'])}B / {_format_size(metrics['memory_total_bytes'])}B",
f"{_format_size(metrics['rx_bps'])}bps / {_format_size(metrics['tx_bps'])}bps",
]
if metrics["gpus"] and isinstance(metrics["gpus"], dict):
rows = [row] + [[""] * len(row)] * (len(metrics["gpus"]) - 1)
for row, gpu_id in zip(rows, sorted(metrics["gpus"])):
gpu = metrics["gpus"][gpu_id]
row += [
f"{gpu['utilization']}%",
f"{round(100 * gpu['memory_used_bytes'] / gpu['memory_total_bytes'], 2)}%",
f"{_format_size(gpu['memory_used_bytes'])}B / {_format_size(gpu['memory_total_bytes'])}B",
]
else:
row += ["N/A"] * (len(table_headers) - len(row))
rows = [row]
yield False, job_id, rows
yield True, job_id, []
@jobs_cli.command("stats", help="Fetch the resource usage statistics and metrics of Jobs")
def jobs_stats(
job_ids: JobIdsArg = None,
namespace: NamespaceOpt = None,
token: TokenOpt = None,
) -> None:
api = get_hf_api(token=token)
if namespace is None:
namespace = api.whoami()["name"]
if job_ids is None:
job_ids = [
job.id
for job in api.list_jobs(namespace=namespace)
if (job.status.stage if job.status else "UNKNOWN") in ("RUNNING", "UPDATING")
]
if len(job_ids) == 0:
print("No running jobs found")
return
table_headers = [
"JOB ID",
"CPU %",
"NUM CPU",
"MEM %",
"MEM USAGE",
"NET I/O",
"GPU UTIL %",
"GPU MEM %",
"GPU MEM USAGE",
]
headers_aliases = [
"id",
"cpu_usage_pct",
"cpu_millicores",
"memory_used_bytes_pct",
"memory_used_bytes_and_total_bytes",
"rx_bps_and_tx_bps",
"gpu_utilization",
"gpu_memory_used_bytes_pct",
"gpu_memory_used_bytes_and_total_bytes",
]
with multiprocessing.pool.ThreadPool(len(job_ids)) as pool:
rows_per_job_id: dict[str, list[list[Union[str, int]]]] = {}
for job_id in job_ids:
row: list[Union[str, int]] = [job_id]
row += ["-- / --" if ("/" in header or "USAGE" in header) else "--" for header in table_headers[1:]]
rows_per_job_id[job_id] = [row]
last_update_time = time.time()
total_rows = [row for job_id in rows_per_job_id for row in rows_per_job_id[job_id]]
_print_output(total_rows, table_headers, headers_aliases, None)
kwargs_list = [
{
"job_id": job_id,
"metrics_stream": api.fetch_job_metrics(job_id=job_id, namespace=namespace),
"table_headers": table_headers,
}
for job_id in job_ids
]
for done, job_id, rows in iflatmap_unordered(pool, _get_jobs_stats_rows, kwargs_list=kwargs_list):
if done:
rows_per_job_id.pop(job_id, None)
else:
rows_per_job_id[job_id] = rows
now = time.time()
if now - last_update_time >= STATS_UPDATE_MIN_INTERVAL:
_clear_line(2 + len(total_rows))
total_rows = [row for job_id in rows_per_job_id for row in rows_per_job_id[job_id]]
_print_output(total_rows, table_headers, headers_aliases, None)
last_update_time = now
@jobs_cli.command("ps", help="List Jobs")
def jobs_ps(
all: Annotated[
@@ -362,6 +479,7 @@ def jobs_ps(
jobs = api.list_jobs(namespace=namespace)
# Define table headers
table_headers = ["JOB ID", "IMAGE/SPACE", "COMMAND", "CREATED", "STATUS"]
headers_aliases = ["id", "image", "command", "created", "status"]
rows: list[list[Union[str, int]]] = []
filters: dict[str, str] = {}
@@ -407,7 +525,7 @@ def jobs_ps(
print(f"No jobs found{filters_msg}")
return
# Apply custom format if provided or use default tabular format
_print_output(rows, table_headers, format)
_print_output(rows, table_headers, headers_aliases, format)
except HfHubHTTPError as e:
print(f"Error fetching jobs data: {e}")
@@ -447,12 +565,15 @@ uv_app = typer_factory(help="Run UV scripts (Python with inline dependencies) on
jobs_cli.add_typer(uv_app, name="uv")
@uv_app.command("run", help="Run a UV script (local file or URL) on HF infrastructure")
@uv_app.command(
"run",
help="Run a UV script (local file or URL) on HF infrastructure",
context_settings={"ignore_unknown_options": True},
)
def jobs_uv_run(
script: ScriptArg,
script_args: ScriptArgsArg = None,
image: ImageOpt = None,
repo: RepoOpt = None,
flavor: FlavorOpt = None,
env: EnvOpt = None,
secrets: SecretsOpt = None,
@@ -489,7 +610,6 @@ def jobs_uv_run(
flavor=flavor, # type: ignore[arg-type]
timeout=timeout,
namespace=namespace,
_repo=repo,
)
# Always print the job ID to the user
print(f"Job started with ID: {job.id}")
@@ -505,7 +625,7 @@ scheduled_app = typer_factory(help="Create and manage scheduled Jobs on the Hub.
jobs_cli.add_typer(scheduled_app, name="scheduled")
@scheduled_app.command("run", help="Schedule a Job")
@scheduled_app.command("run", help="Schedule a Job", context_settings={"ignore_unknown_options": True})
def scheduled_run(
schedule: ScheduleArg,
image: ImageArg,
@@ -581,6 +701,7 @@ def scheduled_ps(
api = get_hf_api(token=token)
scheduled_jobs = api.list_scheduled_jobs(namespace=namespace)
table_headers = ["ID", "SCHEDULE", "IMAGE/SPACE", "COMMAND", "LAST RUN", "NEXT RUN", "SUSPEND"]
headers_aliases = ["id", "schedule", "image", "command", "last", "next", "suspend"]
rows: list[list[Union[str, int]]] = []
filters: dict[str, str] = {}
for f in filter or []:
@@ -620,7 +741,7 @@ def scheduled_ps(
)
print(f"No scheduled jobs found{filters_msg}")
return
_print_output(rows, table_headers, format)
_print_output(rows, table_headers, headers_aliases, format)
except HfHubHTTPError as e:
print(f"Error fetching scheduled jobs data: {e}")
@@ -683,7 +804,11 @@ scheduled_uv_app = typer_factory(help="Schedule UV scripts on HF infrastructure"
scheduled_app.add_typer(scheduled_uv_app, name="uv")
@scheduled_uv_app.command("run", help="Run a UV script (local file or URL) on HF infrastructure")
@scheduled_uv_app.command(
"run",
help="Run a UV script (local file or URL) on HF infrastructure",
context_settings={"ignore_unknown_options": True},
)
def scheduled_uv_run(
schedule: ScheduleArg,
script: ScriptArg,
@@ -691,7 +816,6 @@ def scheduled_uv_run(
suspend: SuspendOpt = None,
concurrency: ConcurrencyOpt = None,
image: ImageOpt = None,
repo: RepoOpt = None,
flavor: FlavorOpt = None,
env: EnvOpt = None,
secrets: SecretsOpt = None,
@@ -730,7 +854,6 @@ def scheduled_uv_run(
flavor=flavor, # type: ignore[arg-type]
timeout=timeout,
namespace=namespace,
_repo=repo,
)
print(f"Scheduled Job created with ID: {job.id}")
@@ -770,3 +893,45 @@ def _get_extended_environ() -> Dict[str, str]:
if (token := get_token()) is not None:
extended_environ["HF_TOKEN"] = token
return extended_environ
T = TypeVar("T")
def _write_generator_to_queue(queue: Queue[T], func: Callable[..., Iterable[T]], kwargs: dict) -> None:
for result in func(**kwargs):
queue.put(result)
def iflatmap_unordered(
pool: multiprocessing.pool.ThreadPool,
func: Callable[..., Iterable[T]],
*,
kwargs_list: list[dict],
) -> Iterable[T]:
"""
Takes a function that returns an iterable of items, and run it in parallel using threads to return the flattened iterable of items as they arrive.
This is inspired by those three `map()` variants, and is the mix of all three:
* `imap()`: like `map()` but returns an iterable instead of a list of results
* `imap_unordered()`: like `imap()` but the output is sorted by time of arrival
* `flatmap()`: like `map()` but given a function which returns a list, `flatmap()` returns the flattened list that is the concatenation of all the output lists
"""
queue: Queue[T] = Queue()
async_results = [pool.apply_async(_write_generator_to_queue, (queue, func, kwargs)) for kwargs in kwargs_list]
try:
while True:
try:
yield queue.get(timeout=0.05)
except Empty:
if all(async_result.ready() for async_result in async_results) and queue.empty():
break
except KeyboardInterrupt:
pass
finally:
# we get the result in case there's an error to raise
try:
[async_result.get(timeout=0.05) for async_result in async_results]
except multiprocessing.TimeoutError:
pass

View File

@@ -0,0 +1,110 @@
# Copyright 2026 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 models on the Hugging Face Hub.
Usage:
# list models on the Hub
hf models ls
# list models with a search query
hf models ls --search "llama"
# get info about a model
hf models info Lightricks/LTX-2
"""
import enum
import json
from typing import Annotated, Optional, get_args
import typer
from huggingface_hub.errors import RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.hf_api import ExpandModelProperty_T, ModelSort_T
from huggingface_hub.utils import ANSI
from ._cli_utils import (
AuthorOpt,
FilterOpt,
LimitOpt,
RevisionOpt,
SearchOpt,
TokenOpt,
get_hf_api,
make_expand_properties_parser,
repo_info_to_dict,
typer_factory,
)
_EXPAND_PROPERTIES = sorted(get_args(ExpandModelProperty_T))
_SORT_OPTIONS = get_args(ModelSort_T)
ModelSortEnum = enum.Enum("ModelSortEnum", {s: s for s in _SORT_OPTIONS}, type=str) # type: ignore[misc]
ExpandOpt = Annotated[
Optional[str],
typer.Option(
help=f"Comma-separated properties to expand. Example: '--expand=downloads,likes,tags'. Valid: {', '.join(_EXPAND_PROPERTIES)}.",
callback=make_expand_properties_parser(_EXPAND_PROPERTIES),
),
]
models_cli = typer_factory(help="Interact with models on the Hub.")
@models_cli.command("ls")
def models_ls(
search: SearchOpt = None,
author: AuthorOpt = None,
filter: FilterOpt = None,
sort: Annotated[
Optional[ModelSortEnum],
typer.Option(help="Sort results."),
] = None,
limit: LimitOpt = 10,
expand: ExpandOpt = None,
token: TokenOpt = None,
) -> None:
"""List models on the Hub."""
api = get_hf_api(token=token)
sort_key = sort.value if sort else None
results = [
repo_info_to_dict(model_info)
for model_info in api.list_models(
filter=filter, author=author, search=search, sort=sort_key, limit=limit, expand=expand
)
]
print(json.dumps(results, indent=2))
@models_cli.command("info")
def models_info(
model_id: Annotated[str, typer.Argument(help="The model ID (e.g. `username/repo-name`).")],
revision: RevisionOpt = None,
expand: ExpandOpt = None,
token: TokenOpt = None,
) -> None:
"""Get info about a model on the Hub."""
api = get_hf_api(token=token)
try:
info = api.model_info(repo_id=model_id, revision=revision, expand=expand) # type: ignore[arg-type]
except RepositoryNotFoundError:
print(f"Model {ANSI.bold(model_id)} not found.")
raise typer.Exit(code=1)
except RevisionNotFoundError:
print(f"Revision {ANSI.bold(str(revision))} not found on {ANSI.bold(model_id)}.")
raise typer.Exit(code=1)
print(json.dumps(repo_info_to_dict(info), indent=2))

View File

@@ -27,22 +27,11 @@ from typing import Annotated, Optional
import typer
from huggingface_hub.errors import HfHubHTTPError, RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.utils import ANSI, logging
from huggingface_hub.utils import ANSI
from ._cli_utils import (
PrivateOpt,
RepoIdArg,
RepoType,
RepoTypeOpt,
RevisionOpt,
TokenOpt,
get_hf_api,
typer_factory,
)
from ._cli_utils import PrivateOpt, RepoIdArg, RepoType, RepoTypeOpt, RevisionOpt, TokenOpt, get_hf_api, typer_factory
logger = logging.get_logger(__name__)
repo_cli = typer_factory(help="Manage repos on the Hub.")
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.")

View File

@@ -0,0 +1,110 @@
# Copyright 2026 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 spaces on the Hugging Face Hub.
Usage:
# list spaces on the Hub
hf spaces ls
# list spaces with a search query
hf spaces ls --search "chatbot"
# get info about a space
hf spaces info enzostvs/deepsite
"""
import enum
import json
from typing import Annotated, Optional, get_args
import typer
from huggingface_hub.errors import RepositoryNotFoundError, RevisionNotFoundError
from huggingface_hub.hf_api import ExpandSpaceProperty_T, SpaceSort_T
from huggingface_hub.utils import ANSI
from ._cli_utils import (
AuthorOpt,
FilterOpt,
LimitOpt,
RevisionOpt,
SearchOpt,
TokenOpt,
get_hf_api,
make_expand_properties_parser,
repo_info_to_dict,
typer_factory,
)
_EXPAND_PROPERTIES = sorted(get_args(ExpandSpaceProperty_T))
_SORT_OPTIONS = get_args(SpaceSort_T)
SpaceSortEnum = enum.Enum("SpaceSortEnum", {s: s for s in _SORT_OPTIONS}, type=str) # type: ignore[misc]
ExpandOpt = Annotated[
Optional[str],
typer.Option(
help=f"Comma-separated properties to expand. Example: '--expand=likes,tags'. Valid: {', '.join(_EXPAND_PROPERTIES)}.",
callback=make_expand_properties_parser(_EXPAND_PROPERTIES),
),
]
spaces_cli = typer_factory(help="Interact with spaces on the Hub.")
@spaces_cli.command("ls")
def spaces_ls(
search: SearchOpt = None,
author: AuthorOpt = None,
filter: FilterOpt = None,
sort: Annotated[
Optional[SpaceSortEnum],
typer.Option(help="Sort results."),
] = None,
limit: LimitOpt = 10,
expand: ExpandOpt = None,
token: TokenOpt = None,
) -> None:
"""List spaces on the Hub."""
api = get_hf_api(token=token)
sort_key = sort.value if sort else None
results = [
repo_info_to_dict(space_info)
for space_info in api.list_spaces(
filter=filter, author=author, search=search, sort=sort_key, limit=limit, expand=expand
)
]
print(json.dumps(results, indent=2))
@spaces_cli.command("info")
def spaces_info(
space_id: Annotated[str, typer.Argument(help="The space ID (e.g. `username/repo-name`).")],
revision: RevisionOpt = None,
expand: ExpandOpt = None,
token: TokenOpt = None,
) -> None:
"""Get info about a space on the Hub."""
api = get_hf_api(token=token)
try:
info = api.space_info(repo_id=space_id, revision=revision, expand=expand) # type: ignore[arg-type]
except RepositoryNotFoundError:
print(f"Space {ANSI.bold(space_id)} not found.")
raise typer.Exit(code=1)
except RevisionNotFoundError:
print(f"Revision {ANSI.bold(str(revision))} not found on {ANSI.bold(space_id)}.")
raise typer.Exit(code=1)
print(json.dumps(repo_info_to_dict(info), indent=2))

View File

@@ -1,4 +1,6 @@
import inspect
import sys
import types
from dataclasses import _MISSING_TYPE, MISSING, Field, field, fields, make_dataclass
from functools import lru_cache, wraps
from typing import (
@@ -597,6 +599,10 @@ _BASIC_TYPE_VALIDATORS = {
set: _validate_set,
}
if sys.version_info >= (3, 10):
# TODO: make it first class citizen when bumping to Python 3.10+
_BASIC_TYPE_VALIDATORS[types.UnionType] = _validate_union # x | y syntax, available only Python 3.10+
__all__ = [
"strict",

View File

@@ -14,6 +14,7 @@
# limitations under the License.
from __future__ import annotations
import base64
import inspect
import json
import re
@@ -27,7 +28,6 @@ from datetime import datetime
from functools import wraps
from itertools import islice
from pathlib import Path
from textwrap import dedent
from typing import (
TYPE_CHECKING,
Any,
@@ -37,12 +37,14 @@ from typing import (
Iterator,
Literal,
Optional,
Type,
TypeVar,
Union,
overload,
)
from urllib.parse import quote
import httpcore
import httpx
from tqdm.auto import tqdm as base_tqdm
from tqdm.contrib.concurrent import thread_map
@@ -193,6 +195,10 @@ ExpandSpaceProperty_T = Literal[
"usedStorage",
]
ModelSort_T = Literal["created_at", "downloads", "last_modified", "likes", "trending_score"]
DatasetSort_T = Literal["created_at", "downloads", "last_modified", "likes", "trending_score"]
SpaceSort_T = Literal["created_at", "last_modified", "likes", "trending_score"]
USERNAME_PLACEHOLDER = "hf_user"
_REGEX_DISCUSSION_URL = re.compile(r".*/discussions/(\d+)$")
_REGEX_HTTP_PROTOCOL = re.compile(r"https?://")
@@ -423,6 +429,7 @@ class CommitInfo(str):
commit_message: str
commit_description: str
oid: str
_endpoint: Optional[str] = field(repr=False)
pr_url: Optional[str] = None
# Computed from `commit_url` in `__post_init__`
@@ -441,7 +448,7 @@ class CommitInfo(str):
See https://docs.python.org/3.10/library/dataclasses.html#post-init-processing.
"""
# Repo info
self.repo_url = RepoUrl(self.commit_url.split("/commit/")[0])
self.repo_url = RepoUrl(self.commit_url.split("/commit/")[0], endpoint=self._endpoint)
# PR info
if self.pr_url is not None:
@@ -1524,6 +1531,42 @@ class User:
self.__dict__.update(**kwargs)
@dataclass
class PaperAuthor:
"""
Contains information about a paper author on the Hub.
Attributes:
name (`str`):
Name of the author.
user (`User`, *optional*):
Information about the author as a [`User`] object.
status (`str`, *optional*):
Status of the author on the Hub.
status_last_changed_at (`datetime`, *optional*):
Date when the status of the author changed.
hidden (`bool`, *optional*):
Whether the author is hidden on the Hub.
"""
name: str
user: Optional[User]
status: Optional[str]
status_last_changed_at: Optional[datetime]
hidden: Optional[bool]
def __init__(self, **kwargs) -> None:
self.name = kwargs.pop("name", "")
user = kwargs.pop("user", None)
self.user = User(**user) if user else None
self.status = kwargs.pop("status", None)
status_last_changed_at = kwargs.pop("statusLastChangedAt", None)
self.status_last_changed_at = parse_datetime(status_last_changed_at) if status_last_changed_at else None
self.hidden = kwargs.pop("hidden", None)
self.__dict__.update(**kwargs)
@dataclass
class PaperInfo:
"""
@@ -1532,30 +1575,42 @@ class PaperInfo:
Attributes:
id (`str`):
arXiv paper ID.
authors (`list[str]`, **optional**):
Names of paper authors
published_at (`datetime`, **optional**):
authors (`list[PaperAuthor]`, *optional*):
Authors of the paper.
published_at (`datetime`, *optional*):
Date paper published.
title (`str`, **optional**):
title (`str`, *optional*):
Title of the paper.
summary (`str`, **optional**):
summary (`str`, *optional*):
Summary of the paper.
upvotes (`int`, **optional**):
upvotes (`int`, *optional*):
Number of upvotes for the paper on the Hub.
discussion_id (`str`, **optional**):
discussion_id (`str`, *optional*):
Discussion ID for the paper on the Hub.
source (`str`, **optional**):
source (`str`, *optional*):
Source of the paper.
comments (`int`, **optional**):
comments (`int`, *optional*):
Number of comments for the paper on the Hub.
submitted_at (`datetime`, **optional**):
submitted_at (`datetime`, *optional*):
Date paper appeared in daily papers on the Hub.
submitted_by (`User`, **optional**):
submitted_by (`User`, *optional*):
Information about who submitted the daily paper.
ai_summary (`str`, *optional*):
AI summary of the paper.
ai_keywords (`list[str]`, *optional*):
AI keywords of the paper.
organization (`Organization`, *optional*):
Information about the organization associated with the paper.
project_page (`str`, *optional*):
URL of the project page for the paper.
github_repo (`str`, *optional*):
URL of the GitHub repository for the paper.
github_stars (`int`, *optional*):
Number of stars of the GitHub repository for the paper.
"""
id: str
authors: Optional[list[str]]
authors: Optional[list[PaperAuthor]]
published_at: Optional[datetime]
title: Optional[str]
summary: Optional[str]
@@ -1565,12 +1620,18 @@ class PaperInfo:
comments: Optional[int]
submitted_at: Optional[datetime]
submitted_by: Optional[User]
ai_summary: Optional[str]
ai_keywords: Optional[list[str]]
organization: Optional[Organization]
project_page: Optional[str]
github_repo: Optional[str]
github_stars: Optional[int]
def __init__(self, **kwargs) -> None:
paper = kwargs.pop("paper", {})
self.id = kwargs.pop("id", None) or paper.pop("id", None)
authors = paper.pop("authors", None) or kwargs.pop("authors", None)
self.authors = [author.pop("name", None) for author in authors] if authors else None
self.authors = [PaperAuthor(**author) for author in authors] if authors else None
published_at = paper.pop("publishedAt", None) or kwargs.pop("publishedAt", None)
self.published_at = parse_datetime(published_at) if published_at else None
self.title = kwargs.pop("title", None)
@@ -1583,6 +1644,13 @@ class PaperInfo:
self.submitted_at = parse_datetime(submitted_at) if submitted_at else None
submitted_by = kwargs.pop("submittedBy", None) or kwargs.pop("submittedOnDailyBy", None)
self.submitted_by = User(**submitted_by) if submitted_by else None
self.ai_summary = kwargs.pop("ai_summary", None)
self.ai_keywords = kwargs.pop("ai_keywords", None)
organization = kwargs.pop("organization", None)
self.organization = Organization(**organization) if organization else None
self.project_page = kwargs.pop("projectPage", None)
self.github_repo = kwargs.pop("githubRepo", None)
self.github_stars = kwargs.pop("githubStars", None)
# forward compatibility
self.__dict__.update(**kwargs)
@@ -1682,6 +1750,85 @@ def future_compatible(fn: CallableT) -> CallableT:
return _inner # type: ignore
def _get_safetensors_metadata_size(size_bytes: bytes, filename: str, context_msg: str) -> int:
"""
Parse and validate safetensors metadata size from the first 8 bytes.
This is a shared helper function used by both remote and local safetensors parsing.
Args:
size_bytes: First 8 bytes of the safetensors file.
filename: Filename for error messages.
context_msg: Additional context for error messages.
Returns:
The metadata size as an integer.
Raises:
SafetensorsParsingError: If size_bytes is too short or metadata size exceeds limit.
"""
if len(size_bytes) < 8:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' ({context_msg}): file is too small to be a valid "
"safetensors file."
)
metadata_size = struct.unpack("<Q", size_bytes[:8])[0]
if metadata_size > constants.SAFETENSORS_MAX_HEADER_LENGTH:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' ({context_msg}): safetensors header is too big. "
f"Maximum supported size is {constants.SAFETENSORS_MAX_HEADER_LENGTH} bytes (got {metadata_size})."
)
return metadata_size
def _parse_safetensors_header(metadata_as_bytes: bytes, filename: str, context_msg: str) -> SafetensorsFileMetadata:
"""
Parse safetensors metadata from raw header bytes.
This is a shared helper function used by both remote and local safetensors parsing.
Args:
metadata_as_bytes: Raw bytes of the JSON metadata header (without the 8-byte size prefix).
filename: Filename for error messages.
context_msg: Additional context for error messages (e.g., repo info or local path).
Returns:
SafetensorsFileMetadata object.
Raises:
SafetensorsParsingError: If the header cannot be parsed.
"""
# Parse json header
try:
metadata_as_dict = json.loads(metadata_as_bytes.decode(errors="ignore"))
except json.JSONDecodeError as e:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' ({context_msg}): header is not json-encoded string. "
"Please make sure this is a correctly formatted safetensors file."
) from e
try:
return SafetensorsFileMetadata(
metadata=metadata_as_dict.get("__metadata__", {}),
tensors={
key: TensorInfo(
dtype=tensor["dtype"],
shape=tensor["shape"],
data_offsets=tuple(tensor["data_offsets"]), # type: ignore
)
for key, tensor in metadata_as_dict.items()
if key != "__metadata__"
},
)
except (KeyError, IndexError) as e:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' ({context_msg}): header format not recognized. "
"Please make sure this is a correctly formatted safetensors file."
) from e
class HfApi:
"""
Client to interact with the Hugging Face Hub via HTTP.
@@ -1863,6 +2010,7 @@ class HfApi:
hf_raise_for_status(r)
return r.json()
@_deprecate_arguments(version="1.5", deprecated_args=["direction"], custom_message="Sorting is always descending.")
@validate_hf_hub_args
def list_models(
self,
@@ -1880,7 +2028,7 @@ class HfApi:
pipeline_tag: Optional[str] = None,
emissions_thresholds: Optional[tuple[float, float]] = None,
# Sorting and pagination parameters
sort: Union[Literal["last_modified"], str, None] = None,
sort: Optional[ModelSort_T] = None,
direction: Optional[Literal[-1]] = None,
limit: Optional[int] = None,
# Additional data to fetch
@@ -1925,12 +2073,11 @@ class HfApi:
emissions_thresholds (`Tuple`, *optional*):
A tuple of two ints or floats representing a minimum and maximum
carbon footprint to filter the resulting models with in grams.
sort (`Literal["last_modified"]` or `str`, *optional*):
The key with which to sort the resulting models. Possible values are "last_modified", "trending_score",
"created_at", "downloads" and "likes".
sort (`ModelSort_T`, *optional*):
The key with which to sort the resulting models. Possible values are "created_at", "downloads",
"last_modified", "likes" and "trending_score".
direction (`Literal[-1]` or `int`, *optional*):
Direction in which to sort. The value `-1` sorts by descending
order while all other values sort by ascending order.
Deprecated. This parameter is not used and will be removed in version 1.5.
limit (`int`, *optional*):
The limit on the number of models fetched. Leaving this option
to `None` fetches all models.
@@ -2063,7 +2210,7 @@ class HfApi:
if emissions_thresholds is None or _is_emission_within_threshold(model_info, *emissions_thresholds):
yield model_info
@_deprecate_arguments(version="1.0", deprecated_args=["tags"], custom_message="Use `filter` instead.")
@_deprecate_arguments(version="1.5", deprecated_args=["direction"], custom_message="Sorting is always descending.")
@validate_hf_hub_args
def list_datasets(
self,
@@ -2082,15 +2229,13 @@ class HfApi:
task_ids: Optional[Union[str, list[str]]] = None,
search: Optional[str] = None,
# Sorting and pagination parameters
sort: Optional[Union[Literal["last_modified"], str]] = None,
sort: Optional[DatasetSort_T] = None,
direction: Optional[Literal[-1]] = None,
limit: Optional[int] = None,
# Additional data to fetch
expand: Optional[list[ExpandDatasetProperty_T]] = None,
full: Optional[bool] = None,
token: Union[bool, str, None] = None,
# Deprecated arguments - use `filter` instead
tags: Optional[Union[str, list[str]]] = None,
) -> Iterable[DatasetInfo]:
"""
List datasets hosted on the Huggingface Hub, given some filters.
@@ -2136,12 +2281,11 @@ class HfApi:
`paraphrase`.
search (`str`, *optional*):
A string that will be contained in the returned datasets.
sort (`Literal["last_modified"]` or `str`, *optional*):
The key with which to sort the resulting models. Possible values are "last_modified", "trending_score",
"created_at", "downloads" and "likes".
sort (`DatasetSort_T`, *optional*):
The key with which to sort the resulting datasets. Possible values are "created_at", "downloads",
"last_modified", "likes" and "trending_score".
direction (`Literal[-1]` or `int`, *optional*):
Direction in which to sort. The value `-1` sorts by descending
order while all other values sort by ascending order.
Deprecated. This parameter is not used and will be removed in version 1.5.
limit (`int`, *optional*):
The limit on the number of datasets fetched. Leaving this option
to `None` fetches all datasets.
@@ -2230,8 +2374,6 @@ class HfApi:
if not value_item.startswith(f"{key}:"):
data = f"{key}:{value_item}"
filter_list.append(data)
if tags is not None:
filter_list.extend([tags] if isinstance(tags, str) else tags)
if len(filter_list) > 0:
params["filter"] = filter_list
@@ -2276,6 +2418,7 @@ class HfApi:
item["siblings"] = None
yield DatasetInfo(**item)
@_deprecate_arguments(version="1.5", deprecated_args=["direction"], custom_message="Sorting is always descending.")
@validate_hf_hub_args
def list_spaces(
self,
@@ -2288,7 +2431,7 @@ class HfApi:
models: Union[str, Iterable[str], None] = None,
linked: bool = False,
# Sorting and pagination parameters
sort: Union[Literal["last_modified"], str, None] = None,
sort: Optional[SpaceSort_T] = None,
direction: Optional[Literal[-1]] = None,
limit: Optional[int] = None,
# Additional data to fetch
@@ -2314,12 +2457,11 @@ class HfApi:
The name of a specific model can be passed as a string.
linked (`bool`, *optional*):
Whether to return Spaces that make use of either a model or a dataset.
sort (`Literal["last_modified"]` or `str`, *optional*):
The key with which to sort the resulting models. Possible values are "last_modified", "trending_score",
"created_at" and "likes".
sort (`SpaceSort_T`, *optional*):
The key with which to sort the resulting spaces. Possible values are "created_at", "last_modified",
"likes" and "trending_score".
direction (`Literal[-1]` or `int`, *optional*):
Direction in which to sort. The value `-1` sorts by descending
order while all other values sort by ascending order.
Deprecated. This parameter is not used and will be removed in version 1.5.
limit (`int`, *optional*):
The limit on the number of Spaces fetched. Leaving this option
to `None` fetches all Spaces.
@@ -4267,6 +4409,7 @@ class HfApi:
commit_message=commit_message,
commit_description=commit_description,
oid=info.sha, # type: ignore[arg-type]
_endpoint=self.endpoint,
)
commit_payload = _prepare_commit_payload(
@@ -4316,6 +4459,7 @@ class HfApi:
commit_description=commit_description,
oid=commit_data["commitOid"],
pr_url=commit_data["pullRequestUrl"] if create_pr else None,
_endpoint=self.endpoint,
)
def preupload_lfs_files(
@@ -5766,6 +5910,8 @@ class HfApi:
)
_headers = self._build_hf_headers(token=token)
context_msg = f"repo '{repo_id}', revision '{revision or constants.DEFAULT_REVISION}'"
# 1. Fetch first 100kb
# Empirically, 97% of safetensors files have a metadata size < 100kb (over the top 1000 models on the Hub).
# We assume fetching 100kb is faster than making 2 GET requests. Therefore we always fetch the first 100kb to
@@ -5774,14 +5920,8 @@ class HfApi:
response = get_session().get(url, headers={**_headers, "range": "bytes=0-100000"})
hf_raise_for_status(response)
# 2. Parse metadata size
metadata_size = struct.unpack("<Q", response.content[:8])[0]
if metadata_size > constants.SAFETENSORS_MAX_HEADER_LENGTH:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
f"'{revision or constants.DEFAULT_REVISION}'): safetensors header is too big. Maximum supported size is "
f"{constants.SAFETENSORS_MAX_HEADER_LENGTH} bytes (got {metadata_size})."
)
# 2. Parse and validate metadata size using shared helper
metadata_size = _get_safetensors_metadata_size(response.content[:8], filename, context_msg)
# 3.a. Get metadata from payload
if metadata_size <= 100000:
@@ -5791,35 +5931,8 @@ class HfApi:
hf_raise_for_status(response)
metadata_as_bytes = response.content
# 4. Parse json header
try:
metadata_as_dict = json.loads(metadata_as_bytes.decode(errors="ignore"))
except json.JSONDecodeError as e:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
f"'{revision or constants.DEFAULT_REVISION}'): header is not json-encoded string. Please make sure this is a "
"correctly formatted safetensors file."
) from e
try:
return SafetensorsFileMetadata(
metadata=metadata_as_dict.get("__metadata__", {}),
tensors={
key: TensorInfo(
dtype=tensor["dtype"],
shape=tensor["shape"],
data_offsets=tuple(tensor["data_offsets"]), # type: ignore
)
for key, tensor in metadata_as_dict.items()
if key != "__metadata__"
},
)
except (KeyError, IndexError) as e:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
f"'{revision or constants.DEFAULT_REVISION}'): header format not recognized. Please make sure this is a correctly"
" formatted safetensors file."
) from e
# 4. Parse json header using shared helper
return _parse_safetensors_header(metadata_as_bytes, filename, context_msg)
@validate_hf_hub_args
def create_branch(
@@ -10101,6 +10214,85 @@ class HfApi:
job_info = response.json()
return JobInfo(**job_info, endpoint=self.endpoint)
def _fetch_running_job_sse(
self,
*,
job_id: str,
route: str,
timeout: int,
skip_previous_events_on_retry: bool,
double_check_job_has_finished_on_status_code_or_error: tuple[Union[int, Type[Exception]], ...],
namespace: Optional[str] = None,
token: Union[bool, str, None] = None,
) -> Iterable[dict[str, Any]]:
if namespace is None:
namespace = self.whoami(token=token)["name"]
# We don't use http_backoff since we need to check ourselves if the job is still running
nb_tries = 0
max_retries = 5
min_wait_time = 1
max_wait_time = 10
sleep_time = 0
start_event_idx = 0
error_to_retry = None
while True:
if error_to_retry is not None:
logger.warning(f"'{error_to_retry}' thrown while requesting jobs /{route} for {job_id=}")
logger.warning(f"Retrying in {sleep_time}s [Retry {nb_tries}/{max_retries}].")
error_to_retry = None
time.sleep(sleep_time)
try:
with get_session().stream(
"GET",
f"{self.endpoint}/api/jobs/{namespace}/{job_id}/{route}",
headers=self._build_hf_headers(token=token),
timeout=timeout,
) as response:
if response.status_code == 200:
event_idx = -1
for line in response.iter_lines():
if line and line.startswith("data: {"):
event_idx += 1
if event_idx >= start_event_idx:
if skip_previous_events_on_retry:
start_event_idx += 1
yield json.loads(line[len("data: ") :])
break
elif response.status_code not in double_check_job_has_finished_on_status_code_or_error:
hf_raise_for_status(response)
except httpx.HTTPStatusError:
raise
except httpx.DecodingError:
# Response ended prematurely
break
except KeyboardInterrupt:
break
except (httpx.HTTPError, httpcore.TimeoutException) as err:
is_no_new_line_timeout = (
isinstance(err, httpx.NetworkError)
and err.__context__
and isinstance(getattr(err.__context__, "__cause__", None), TimeoutError)
)
if is_no_new_line_timeout:
# job is likely finished
pass
elif type(err) in double_check_job_has_finished_on_status_code_or_error:
pass
elif nb_tries >= max_retries:
raise
else:
nb_tries += 1
sleep_time = min(max_wait_time, max(min_wait_time, sleep_time * 2))
error_to_retry = err
job_status_response = get_session().get(
f"{self.endpoint}/api/jobs/{namespace}/{job_id}",
headers=self._build_hf_headers(token=token),
)
hf_raise_for_status(job_status_response)
job_status = job_status_response.json()
if "status" in job_status and job_status["status"]["stage"] not in ("RUNNING", "UPDATING"):
break
def fetch_job_logs(
self,
*,
@@ -10128,15 +10320,11 @@ class HfApi:
```python
>>> from huggingface_hub import fetch_job_logs, run_job
>>> job = run_job(image="python:3.12", command=["python", "-c" ,"print('Hello from HF compute!')"])
>>> for log in fetch_job_logs(job.id):
>>> for log in fetch_job_logs(job_id=job.id):
... print(log)
Hello from HF compute!
```
"""
if namespace is None:
namespace = self.whoami(token=token)["name"]
logging_finished = logging_started = False
job_finished = False
# - We need to retry because sometimes the /logs doesn't return logs when the job just started.
# (for example it can return only two lines: one for "Job started" and one empty line)
# - Timeouts can happen in case of build errors
@@ -10145,52 +10333,86 @@ class HfApi:
# (the logs stream is infinite and empty except for the Job started message)
# - there is a ": keep-alive" every 30 seconds
# We don't use http_backoff since we need to check ourselves if ConnectionError.__context__ is a TimeoutError
max_retries = 5
min_wait_time = 1
max_wait_time = 10
sleep_time = 0
for _ in range(max_retries):
time.sleep(sleep_time)
sleep_time = min(max_wait_time, max(min_wait_time, sleep_time * 2))
try:
with get_session().stream(
"GET",
f"{self.endpoint}/api/jobs/{namespace}/{job_id}/logs",
headers=self._build_hf_headers(token=token),
timeout=120,
) as response:
log = None
for line in response.iter_lines():
if line and line.startswith("data: {"):
data = json.loads(line[len("data: ") :])
# timestamp = data["timestamp"]
if not data["data"].startswith("===== Job started"):
logging_started = True
log = data["data"]
yield log
logging_finished = logging_started
except httpx.DecodingError:
# Response ended prematurely
break
except KeyboardInterrupt:
break
except httpx.NetworkError as err:
is_timeout = err.__context__ and isinstance(getattr(err.__context__, "__cause__", None), TimeoutError)
if logging_started or not is_timeout:
raise
if logging_finished or job_finished:
break
job_status = (
get_session()
.get(
f"{self.endpoint}/api/jobs/{namespace}/{job_id}",
headers=self._build_hf_headers(token=token),
)
.json()
)
if "status" in job_status and job_status["status"]["stage"] not in ("RUNNING", "UPDATING"):
job_finished = True
seconds_between_keep_alive = 30
for event in self._fetch_running_job_sse(
job_id=job_id,
route="logs",
timeout=4 * seconds_between_keep_alive,
skip_previous_events_on_retry=True,
double_check_job_has_finished_on_status_code_or_error=tuple(),
namespace=namespace,
token=token,
):
# timestamp = event["timestamp"]
if not event["data"].startswith("===== Job started"):
log = event["data"]
yield log
def fetch_job_metrics(
self,
*,
job_id: str,
namespace: Optional[str] = None,
token: Union[bool, str, None] = None,
) -> Iterable[dict[str, Any]]:
"""
Fetch all the live metrics from a compute Job on Hugging Face infrastructure.
Args:
job_id (`str`):
ID of the Job.
namespace (`str`, *optional*):
The namespace where the Job is running. Defaults to the current user's namespace.
token `(Union[bool, str, None]`, *optional*):
A valid user access token. If not provided, the locally saved token will be used, which is the
recommended authentication method. Set to `False` to disable authentication.
Refer to: https://huggingface.co/docs/huggingface_hub/quick-start#authentication.
Example:
```python
>>> from huggingface_hub import fetch_job_metrics, run_job
>>> job = run_job(image="python:3.12", command=["python", "-c" ,"print('Hello from HF compute!')"], flavor="a10g-small")
>>> for metrics in fetch_job_metrics(job_id=job.id):
... print(metrics)
{
"cpu_usage_pct": 0,
"cpu_millicores": 3500,
"memory_used_bytes": 1306624,
"memory_total_bytes": 15032385536,
"rx_bps": 0,
"tx_bps": 0,
"gpus": {
"882fa930": {
"utilization": 0,
"memory_used_bytes": 0,
"memory_total_bytes": 22836000000
}
},
"replica": "57vr7"
}
```
"""
# - there is one "metric" event every second, like this:
# event: metric
# data: {"cpu_usage_pct":0,"cpu_millicores":3500,"memory_used_bytes":1417216,"memory_total_bytes":15032385536,"rx_bps":0,"tx_bps":0,"gpus":{"d901cd7f":{"utilization":0,"memory_used_bytes":0,"memory_total_bytes":22836000000}},"replica":"j6qz9"}
# - the stream doesn't end when the job finishes, so we rely on timeouts (httpx.NetworkError with Timeout as cause)
# - httpx.ReadTimeout can happen if the job is marked as running but the hardware is not available yet, that we can ignore
# - it returns an internal error 500 if the job has already finished, we simply ignore it
# - ChunkedEncodingError can happen in case of stopped logging in the middle of streaming
# - there is a ": keep-alive" every 30 seconds
seconds_between_events = 1
yield from self._fetch_running_job_sse(
job_id=job_id,
route="metrics",
timeout=10 * seconds_between_events,
skip_previous_events_on_retry=False,
double_check_job_has_finished_on_status_code_or_error=(500, httpx.ReadTimeout),
namespace=namespace,
token=token,
)
def list_jobs(
self,
@@ -10319,7 +10541,6 @@ class HfApi:
timeout: Optional[Union[int, float, str]] = None,
namespace: Optional[str] = None,
token: Union[bool, str, None] = None,
_repo: Optional[str] = None,
) -> JobInfo:
"""
Run a UV script Job on Hugging Face infrastructure.
@@ -10405,7 +10626,6 @@ class HfApi:
secrets=secrets,
namespace=namespace,
token=token,
_repo=_repo,
)
# Create RunCommand args
return self.run_job(
@@ -10709,7 +10929,6 @@ class HfApi:
timeout: Optional[Union[int, float, str]] = None,
namespace: Optional[str] = None,
token: Union[bool, str, None] = None,
_repo: Optional[str] = None,
) -> ScheduledJobInfo:
"""
Run a UV script Job on Hugging Face infrastructure.
@@ -10802,7 +11021,6 @@ class HfApi:
secrets=secrets,
namespace=namespace,
token=token,
_repo=_repo,
)
# Create RunCommand args
return self.create_scheduled_job(
@@ -10830,7 +11048,6 @@ class HfApi:
secrets: Optional[dict[str, Any]],
namespace: Optional[str],
token: Union[bool, str, None],
_repo: Optional[str],
) -> tuple[list[str], dict[str, Any], dict[str, Any]]:
env = env or {}
secrets = secrets or {}
@@ -10852,95 +11069,15 @@ class HfApi:
# Direct URL execution or command - no upload needed
command = ["uv", "run"] + uv_args + [script] + script_args
else:
# Local file - upload to HF
script_path = Path(script)
filename = script_path.name
# Parse repo
if _repo:
repo_id = _repo
if "/" not in repo_id:
repo_id = f"{namespace}/{repo_id}"
else:
repo_id = f"{namespace}/hf-cli-jobs-uv-run-scripts"
# Local file - embed as env variable
script_content = base64.b64encode(Path(script).read_bytes()).decode()
env["UV_SCRIPT_ENCODED"] = script_content
# Create repo if needed
try:
self.repo_info(repo_id, repo_type="dataset")
logger.debug(f"Using existing repository: {repo_id}")
except RepositoryNotFoundError:
logger.info(f"Creating repository: {repo_id}")
create_repo(repo_id, repo_type="dataset", private=True, exist_ok=True)
# Upload script
logger.info(f"Uploading {script_path.name} to {repo_id}...")
with open(script_path, "r") as f:
script_content = f.read()
commit_hash = self.upload_file(
path_or_fileobj=script_content.encode(),
path_in_repo=filename,
repo_id=repo_id,
repo_type="dataset",
).oid
script_url = f"{self.endpoint}/datasets/{repo_id}/resolve/{commit_hash}/{filename}"
repo_url = f"{self.endpoint}/datasets/{repo_id}"
logger.debug(f"✓ Script uploaded to: {repo_url}/blob/main/{filename}")
# Create and upload minimal README
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")
readme_content = dedent(
f"""
---
tags:
- hf-cli-jobs-uv-script
- ephemeral
viewer: false
---
# UV Script: {filename}
Executed via `hf jobs uv run` on {timestamp}
## Run this script
```bash
hf jobs uv run {filename}
```
---
*Created with [hf jobs](https://huggingface.co/docs/huggingface_hub/main/en/guides/jobs)*
"""
)
self.upload_file(
path_or_fileobj=readme_content.encode(),
path_in_repo="README.md",
repo_id=repo_id,
repo_type="dataset",
)
secrets["UV_SCRIPT_HF_TOKEN"] = token or self.token or get_token()
env["UV_SCRIPT_URL"] = script_url
pre_command = (
dedent(
"""
import urllib.request
import os
from pathlib import Path
o = urllib.request.build_opener()
o.addheaders = [("Authorization", "Bearer " + os.environ["UV_SCRIPT_HF_TOKEN"])]
Path("/tmp/script.py").write_bytes(o.open(os.environ["UV_SCRIPT_URL"]).read())
"""
)
.strip()
.replace('"', r"\"")
.split("\n")
)
pre_command = ["python", "-c", '"' + "; ".join(pre_command) + '"']
command = ["uv", "run"] + uv_args + ["/tmp/script.py"] + script_args
command = ["bash", "-c", " ".join(pre_command) + " && " + " ".join(command)]
command = [
"bash",
"-c",
f'echo "$UV_SCRIPT_ENCODED" | base64 -d > /tmp/script.py && uv run {" ".join(uv_args)} /tmp/script.py {" ".join(script_args)}',
]
return command, env, secrets
@@ -10959,6 +11096,158 @@ def _parse_revision_from_pr_url(pr_url: str) -> str:
return f"refs/pr/{re_match[1]}"
def parse_local_safetensors_file_metadata(path: Union[str, Path]) -> SafetensorsFileMetadata:
"""
Parse metadata from a local safetensors file.
For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
Args:
path (`str` or `Path`):
Path to the safetensors file.
Returns:
[`SafetensorsFileMetadata`]: information related to the safetensors file.
Raises:
[`SafetensorsParsingError`]:
If the safetensors file header couldn't be parsed correctly.
`FileNotFoundError`:
If the file does not exist.
Example:
```py
>>> metadata = parse_local_safetensors_file_metadata("path/to/model.safetensors")
>>> metadata
SafetensorsFileMetadata(
metadata={'format': 'pt'},
tensors={'layer.weight': TensorInfo(dtype='F32', shape=[512, 512], ...}, ...}
)
>>> metadata.parameter_count
{'F32': 262144}
```
"""
path = Path(path)
filename = path.name
context_msg = f"path '{path}'"
with open(path, "rb") as f:
# 1. Read first 8 bytes and parse/validate metadata size using shared helper
size_bytes = f.read(8)
metadata_size = _get_safetensors_metadata_size(size_bytes, filename, context_msg)
# 2. Read metadata bytes
metadata_as_bytes = f.read(metadata_size)
if len(metadata_as_bytes) < metadata_size:
raise SafetensorsParsingError(
f"Failed to parse safetensors header for '{filename}' ({context_msg}): file is truncated. Expected "
f"{metadata_size} bytes of metadata but got {len(metadata_as_bytes)}."
)
# 3. Parse using shared helper
return _parse_safetensors_header(metadata_as_bytes, filename, context_msg)
def get_local_safetensors_metadata(path: Union[str, Path]) -> SafetensorsRepoMetadata:
"""
Parse metadata for a local safetensors file or folder.
Supports:
- Single safetensors file (e.g., `model.safetensors`)
- Directory with non-sharded model (contains `model.safetensors`)
- Directory with sharded model (contains `model.safetensors.index.json`)
For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
Args:
path (`str` or `Path`):
Path to a safetensors file or directory containing safetensors files.
Returns:
[`SafetensorsRepoMetadata`]: information related to the safetensors repo.
Raises:
[`NotASafetensorsRepoError`]:
If the path is not a valid safetensors file or folder (i.e., doesn't have either a
`model.safetensors` or a `model.safetensors.index.json` file).
[`SafetensorsParsingError`]:
If a safetensors file header couldn't be parsed correctly.
`FileNotFoundError`:
If the path does not exist.
Example:
```py
# Parse single safetensors file
>>> metadata = get_local_safetensors_metadata("path/to/model.safetensors")
>>> metadata
SafetensorsRepoMetadata(metadata=None, sharded=False, weight_map={...}, files_metadata={...})
# Parse directory with sharded model
>>> metadata = get_local_safetensors_metadata("path/to/model_folder")
>>> metadata
SafetensorsRepoMetadata(metadata={'total_size': ...}, sharded=True, weight_map={...}, files_metadata={...})
>>> len(metadata.files_metadata)
3 # Number of safetensors shards
```
"""
path = Path(path)
# Case 1: Direct path to a safetensors file
if path.is_file():
file_metadata = parse_local_safetensors_file_metadata(path)
return SafetensorsRepoMetadata(
metadata=None,
sharded=False,
weight_map={tensor_name: path.name for tensor_name in file_metadata.tensors.keys()},
files_metadata={path.name: file_metadata},
)
# Case 2: Directory
if not path.is_dir():
raise FileNotFoundError(f"Path '{path}' does not exist.")
single_file_path = path / constants.SAFETENSORS_SINGLE_FILE
index_file_path = path / constants.SAFETENSORS_INDEX_FILE
# Case 2a: Non-sharded model (single model.safetensors file)
if single_file_path.exists():
file_metadata = parse_local_safetensors_file_metadata(single_file_path)
return SafetensorsRepoMetadata(
metadata=None,
sharded=False,
weight_map={
tensor_name: constants.SAFETENSORS_SINGLE_FILE for tensor_name in file_metadata.tensors.keys()
},
files_metadata={constants.SAFETENSORS_SINGLE_FILE: file_metadata},
)
# Case 2b: Sharded model (model.safetensors.index.json)
if index_file_path.exists():
with open(index_file_path) as f:
index = json.load(f)
weight_map = index.get("weight_map", {})
# Parse metadata from each shard
files_metadata = {}
for shard_filename in set(weight_map.values()):
shard_path = path / shard_filename
files_metadata[shard_filename] = parse_local_safetensors_file_metadata(shard_path)
return SafetensorsRepoMetadata(
metadata=index.get("metadata", None),
sharded=True,
weight_map=weight_map,
files_metadata=files_metadata,
)
# Not a valid safetensors folder
raise NotASafetensorsRepoError(
f"'{path}' is not a valid safetensors folder. Couldn't find '{constants.SAFETENSORS_INDEX_FILE}' or "
f"'{constants.SAFETENSORS_SINGLE_FILE}' files."
)
api = HfApi()
whoami = api.whoami
@@ -11105,6 +11394,7 @@ list_user_following = api.list_user_following
# Jobs API
run_job = api.run_job
fetch_job_logs = api.fetch_job_logs
fetch_job_metrics = api.fetch_job_metrics
list_jobs = api.list_jobs
inspect_job = api.inspect_job
cancel_job = api.cancel_job

View File

@@ -160,6 +160,8 @@ class InferenceClient:
follow the same pattern as `openai.OpenAI` client. Cannot be used if `token` is set. Defaults to None.
"""
provider: Optional[PROVIDER_OR_POLICY_T]
@validate_hf_hub_args
def __init__(
self,
@@ -227,7 +229,7 @@ class InferenceClient:
)
# Configure provider
self.provider = provider
self.provider = provider # type: ignore[assignment]
self.cookies = cookies
self.timeout = timeout
@@ -1030,6 +1032,8 @@ class InferenceClient:
prompt_name: Optional[str] = None,
truncate: Optional[bool] = None,
truncation_direction: Optional[Literal["left", "right"]] = None,
dimensions: Optional[int] = None,
encoding_format: Optional[Literal["float", "base64"]] = None,
model: Optional[str] = None,
) -> "np.ndarray":
"""
@@ -1056,6 +1060,12 @@ class InferenceClient:
Only available on server powered by Text-Embedding-Inference.
truncation_direction (`Literal["left", "right"]`, *optional*):
Which side of the input should be truncated when `truncate=True` is passed.
dimensions (`int`, *optional*):
The number of dimensions the resulting output embeddings should have.
Only available on OpenAI-compatible embedding endpoints.
encoding_format (`Literal["float", "base64"]`, *optional*):
The format of the output embeddings. Either "float" or "base64".
Only available on OpenAI-compatible embedding endpoints.
Returns:
`np.ndarray`: The embedding representing the input text as a float32 numpy array.
@@ -1086,6 +1096,8 @@ class InferenceClient:
"prompt_name": prompt_name,
"truncate": truncate,
"truncation_direction": truncation_direction,
"dimensions": dimensions,
"encoding_format": encoding_format,
},
headers=self.headers,
model=model_id,

View File

@@ -151,6 +151,8 @@ class AsyncInferenceClient:
follow the same pattern as `openai.OpenAI` client. Cannot be used if `token` is set. Defaults to None.
"""
provider: Optional[PROVIDER_OR_POLICY_T]
@validate_hf_hub_args
def __init__(
self,
@@ -218,7 +220,7 @@ class AsyncInferenceClient:
)
# Configure provider
self.provider = provider
self.provider = provider # type: ignore[assignment]
self.cookies = cookies
self.timeout = timeout
@@ -1057,6 +1059,8 @@ class AsyncInferenceClient:
prompt_name: Optional[str] = None,
truncate: Optional[bool] = None,
truncation_direction: Optional[Literal["left", "right"]] = None,
dimensions: Optional[int] = None,
encoding_format: Optional[Literal["float", "base64"]] = None,
model: Optional[str] = None,
) -> "np.ndarray":
"""
@@ -1083,6 +1087,12 @@ class AsyncInferenceClient:
Only available on server powered by Text-Embedding-Inference.
truncation_direction (`Literal["left", "right"]`, *optional*):
Which side of the input should be truncated when `truncate=True` is passed.
dimensions (`int`, *optional*):
The number of dimensions the resulting output embeddings should have.
Only available on OpenAI-compatible embedding endpoints.
encoding_format (`Literal["float", "base64"]`, *optional*):
The format of the output embeddings. Either "float" or "base64".
Only available on OpenAI-compatible embedding endpoints.
Returns:
`np.ndarray`: The embedding representing the input text as a float32 numpy array.
@@ -1114,6 +1124,8 @@ class AsyncInferenceClient:
"prompt_name": prompt_name,
"truncate": truncate,
"truncation_direction": truncation_direction,
"dimensions": dimensions,
"encoding_format": encoding_format,
},
headers=self.headers,
model=model_id,

View File

@@ -77,6 +77,18 @@ from .image_segmentation import (
ImageSegmentationParameters,
ImageSegmentationSubtask,
)
from .image_text_to_image import (
ImageTextToImageInput,
ImageTextToImageOutput,
ImageTextToImageParameters,
ImageTextToImageTargetSize,
)
from .image_text_to_video import (
ImageTextToVideoInput,
ImageTextToVideoOutput,
ImageTextToVideoParameters,
ImageTextToVideoTargetSize,
)
from .image_to_image import ImageToImageInput, ImageToImageOutput, ImageToImageParameters, ImageToImageTargetSize
from .image_to_text import (
ImageToTextEarlyStoppingEnum,

View File

@@ -0,0 +1,67 @@
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Optional
from .base import BaseInferenceType, dataclass_with_extra
@dataclass_with_extra
class ImageTextToImageTargetSize(BaseInferenceType):
"""The size in pixels of the output image. This parameter is only supported by some
providers and for specific models. It will be ignored when unsupported.
"""
height: int
width: int
@dataclass_with_extra
class ImageTextToImageParameters(BaseInferenceType):
"""Additional inference parameters for Image Text To Image"""
guidance_scale: Optional[float] = None
"""For diffusion models. A higher guidance scale value encourages the model to generate
images closely linked to the text prompt at the expense of lower image quality.
"""
negative_prompt: Optional[str] = None
"""One prompt to guide what NOT to include in image generation."""
num_inference_steps: Optional[int] = None
"""For diffusion models. The number of denoising steps. More denoising steps usually lead to
a higher quality image at the expense of slower inference.
"""
prompt: Optional[str] = None
"""The text prompt to guide the image generation. Either this or inputs (image) must be
provided.
"""
seed: Optional[int] = None
"""Seed for the random number generator."""
target_size: Optional[ImageTextToImageTargetSize] = None
"""The size in pixels of the output image. This parameter is only supported by some
providers and for specific models. It will be ignored when unsupported.
"""
@dataclass_with_extra
class ImageTextToImageInput(BaseInferenceType):
"""Inputs for Image Text To Image inference. Either inputs (image) or prompt (in parameters)
must be provided, or both.
"""
inputs: Optional[str] = None
"""The input image data as a base64-encoded string. If no `parameters` are provided, you can
also provide the image data as a raw bytes payload. Either this or prompt must be
provided.
"""
parameters: Optional[ImageTextToImageParameters] = None
"""Additional inference parameters for Image Text To Image"""
@dataclass_with_extra
class ImageTextToImageOutput(BaseInferenceType):
"""Outputs of inference for the Image Text To Image task"""
image: Any
"""The generated image returned as raw bytes in the payload."""

View File

@@ -0,0 +1,65 @@
# Inference code generated from the JSON schema spec in @huggingface/tasks.
#
# See:
# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
from typing import Any, Optional
from .base import BaseInferenceType, dataclass_with_extra
@dataclass_with_extra
class ImageTextToVideoTargetSize(BaseInferenceType):
"""The size in pixel of the output video frames."""
height: int
width: int
@dataclass_with_extra
class ImageTextToVideoParameters(BaseInferenceType):
"""Additional inference parameters for Image Text To Video"""
guidance_scale: Optional[float] = None
"""For diffusion models. A higher guidance scale value encourages the model to generate
videos closely linked to the text prompt at the expense of lower image quality.
"""
negative_prompt: Optional[str] = None
"""One prompt to guide what NOT to include in video generation."""
num_frames: Optional[float] = None
"""The num_frames parameter determines how many video frames are generated."""
num_inference_steps: Optional[int] = None
"""The number of denoising steps. More denoising steps usually lead to a higher quality
video at the expense of slower inference.
"""
prompt: Optional[str] = None
"""The text prompt to guide the video generation. Either this or inputs (image) must be
provided.
"""
seed: Optional[int] = None
"""Seed for the random number generator."""
target_size: Optional[ImageTextToVideoTargetSize] = None
"""The size in pixel of the output video frames."""
@dataclass_with_extra
class ImageTextToVideoInput(BaseInferenceType):
"""Inputs for Image Text To Video inference. Either inputs (image) or prompt (in parameters)
must be provided, or both.
"""
inputs: Optional[str] = None
"""The input image data as a base64-encoded string. If no `parameters` are provided, you can
also provide the image data as a raw bytes payload. Either this or prompt must be
provided.
"""
parameters: Optional[ImageTextToVideoParameters] = None
"""Additional inference parameters for Image Text To Video"""
@dataclass_with_extra
class ImageTextToVideoOutput(BaseInferenceType):
"""Outputs of inference for the Image Text To Video task"""
video: Any
"""The generated video returned as raw bytes in the payload."""

Some files were not shown because too many files have changed in this diff Show More