修改为东南天坐标系
This commit is contained in:
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
@@ -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[
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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.")
|
||||
|
||||
@@ -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))
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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."""
|
||||
@@ -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
Reference in New Issue
Block a user