Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-huggingface-hub for 
openSUSE:Factory checked in at 2026-07-28 18:15:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-huggingface-hub (Old)
 and      /work/SRC/openSUSE:Factory/.python-huggingface-hub.new.2004 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-huggingface-hub"

Tue Jul 28 18:15:33 2026 rev:9 rq:1368071 version:1.25.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-huggingface-hub/python-huggingface-hub.changes
    2026-07-17 18:52:09.854701101 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-huggingface-hub.new.2004/python-huggingface-hub.changes
  2026-07-28 18:17:15.513164518 +0200
@@ -1,0 +2,12 @@
+Mon Jul 27 16:45:10 UTC 2026 - Martin Pluskal <[email protected]>
+
+- update to 1.25.1:
+  * Stop injecting the HF token into sandbox jobs as SBX_DL_TOKEN
+  * Auto-name jobs on creation from the image plus a command hash
+  * Add a timeout parameter to the safetensors metadata methods
+  * Warn on cache inconsistencies and fix the file-count progress bar
+    on completion
+  * Fix catastrophic backtracking (ReDoS) in the model card YAML
+    block regular expression
+
+-------------------------------------------------------------------

Old:
----
  huggingface_hub-1.24.0.tar.gz

New:
----
  huggingface_hub-1.25.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-huggingface-hub.spec ++++++
--- /var/tmp/diff_new_pack.Cz5Bk4/_old  2026-07-28 18:17:18.189258069 +0200
+++ /var/tmp/diff_new_pack.Cz5Bk4/_new  2026-07-28 18:17:18.205258628 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-huggingface-hub
-Version:        1.24.0
+Version:        1.25.1
 Release:        0
 Summary:        Client library for interaction with the huggingface hub
 License:        Apache-2.0

++++++ huggingface_hub-1.24.0.tar.gz -> huggingface_hub-1.25.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/PKG-INFO 
new/huggingface_hub-1.25.1/PKG-INFO
--- old/huggingface_hub-1.24.0/PKG-INFO 2026-07-17 11:53:43.926415700 +0200
+++ new/huggingface_hub-1.25.1/PKG-INFO 2026-07-27 11:23:51.058730000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: huggingface_hub
-Version: 1.24.0
+Version: 1.25.1
 Summary: Client library to download and publish models, datasets and other 
repos on the huggingface.co hub
 Home-page: https://github.com/huggingface/huggingface_hub
 Author: Hugging Face, Inc.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/__init__.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/__init__.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/__init__.py  2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/__init__.py  2026-07-27 
11:23:46.000000000 +0200
@@ -46,7 +46,7 @@
 from typing import TYPE_CHECKING
 
 
-__version__ = "1.24.0"
+__version__ = "1.25.1"
 
 # Alphabetical order of definitions is ensured in tests
 # WARNING: any comment added in this dictionary definition will be lost when
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/_commit_api.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/_commit_api.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/_commit_api.py       
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/_commit_api.py       
2026-07-27 11:23:46.000000000 +0200
@@ -16,8 +16,6 @@
 from pathlib import Path, PurePosixPath
 from typing import TYPE_CHECKING, Any, BinaryIO, Literal, NamedTuple, Union
 
-from tqdm.contrib.concurrent import thread_map
-
 from . import constants
 from .errors import EntryNotFoundError
 from .file_download import hf_hub_url
@@ -28,6 +26,7 @@
     chunk_iterable,
     get_session,
     hf_raise_for_status,
+    hf_thread_map,
     http_backoff,
     logging,
     sha,
@@ -536,7 +535,7 @@
         logger.debug(
             f"Uploading {len(filtered_actions)} LFS files to the Hub using up 
to {num_threads} threads concurrently"
         )
-        thread_map(
+        hf_thread_map(
             _wrapped_lfs_upload,
             filtered_actions,
             desc=f"Upload {len(filtered_actions)} LFS files",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/_jobs_api.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/_jobs_api.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/_jobs_api.py 2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/_jobs_api.py 2026-07-27 
11:23:46.000000000 +0200
@@ -13,6 +13,7 @@
 # limitations under the License.
 import hashlib
 import platform
+import re
 from dataclasses import dataclass
 from datetime import datetime
 from enum import Enum
@@ -91,6 +92,14 @@
 # Stages indicating the Job has reached a terminal state and will not run 
further.
 TERMINAL_JOB_STAGES = (JobStage.COMPLETED, JobStage.CANCELED, JobStage.ERROR, 
JobStage.DELETED)
 
+# URL prefixes identifying an image that points to a HF Space rather than a 
Docker image.
+_SPACE_IMAGE_PREFIXES = (
+    "https://huggingface.co/spaces/";,
+    "https://hf.co/spaces/";,
+    "huggingface.co/spaces/",
+    "hf.co/spaces/",
+)
+
 
 @dataclass
 class JobStatus:
@@ -510,6 +519,55 @@
     return f"{dirname}-{digest}"
 
 
+def _sanitize_job_name(name: str) -> str:
+    """Sanitize a string so it is a valid Job `name` label.
+
+    Job names must match `^[a-zA-Z0-9._-]*$` and are also stored as tags, 
which only allow
+    alphanumerics, `-` and `_`. Every other character (including `/`, `:` and 
`.`) is replaced with `-`.
+    """
+    return re.sub(r"[^a-zA-Z0-9_-]", "-", name)
+
+
+def _short_invocation_hash(parts: list[str]) -> str:
+    """Short, stable hash of a Job invocation (image/script + command line).
+
+    Appended to auto-generated names so reruns of the same command share a 
name while different ones don't.
+    """
+    return hashlib.sha256("\x00".join(parts).encode()).hexdigest()[:8]
+
+
+def _default_job_name_from_image(image: str, command: list[str]) -> str:
+    """Derive a default Job name from a Docker image or Space reference and 
its command.
+
+    A short hash of the full command line is appended to group reruns of the 
same command.
+    e.g. python:3.12 + [foo, --truc]             -> python-3-12-1a2b3c4d
+         hf.co/spaces/lhoestq/duckdb             -> lhoestq-duckdb-<hash>
+         pytorch/pytorch:2.6.0-cuda12.4-...      -> 
pytorch-2-6-0-cuda12-4-...-<hash>
+    """
+    for prefix in _SPACE_IMAGE_PREFIXES:
+        if image.startswith(prefix):
+            base = _sanitize_job_name(image[len(prefix) :] or image)
+            break
+    else:
+        base = _sanitize_job_name(image.rstrip("/").split("/")[-1] or image)  
# drop registry host and namespace
+    return f"{base}-{_short_invocation_hash([image, *command])}"
+
+
+def _default_job_name_from_script(script: str, script_args: list[str]) -> str:
+    """Derive a default Job name from a UV script path, URL, or command and 
its arguments.
+
+    A short hash of the full command line is appended to group reruns of the 
same command.
+    e.g. my_script.py + [--epochs, 3]  -> my_script-1a2b3c4d
+         https://.../sft.py?raw=1      -> sft-<hash>
+         lighteval                     -> lighteval-<hash>
+    """
+    name = script.split("?", 1)[0].split("#", 1)[0].rstrip("/").split("/")[-1]
+    if name.endswith(".py"):
+        name = name[: -len(".py")]
+    base = _sanitize_job_name(name or script)
+    return f"{base}-{_short_invocation_hash([script, *script_args])}"
+
+
 def _create_job_spec(
     *,
     image: str,
@@ -559,12 +617,7 @@
     if ssh:
         job_spec["ssh"] = {"enabled": True}
     # input is either from docker hub or from HF spaces
-    for prefix in (
-        "https://huggingface.co/spaces/";,
-        "https://hf.co/spaces/";,
-        "huggingface.co/spaces/",
-        "hf.co/spaces/",
-    ):
+    for prefix in _SPACE_IMAGE_PREFIXES:
         if image.startswith(prefix):
             job_spec["spaceId"] = image[len(prefix) :]
             break
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/_local_folder.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/_local_folder.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/_local_folder.py     
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/_local_folder.py     
2026-07-27 11:23:46.000000000 +0200
@@ -431,15 +431,26 @@
     """Return the path to the `.cache/huggingface` directory in a local 
directory."""
     # Wrap in lru_cache to avoid overwriting the .gitignore file if called 
multiple times
     path = local_dir / ".cache" / "huggingface"
-    path.mkdir(exist_ok=True, parents=True)
+    # Without long path support enabled, Windows caps directory paths at 247 
characters
+    # (MAX_PATH minus room for an 8.3 file name), so creating the 
`.cache/huggingface` directory
+    # (and the bookkeeping files below) fails for a deep `local_dir`.
+    # Use the extended-length "\\?\" prefix for the filesystem operations, 
matching what
+    # `get_local_download_paths`/`get_local_upload_paths` already do for the 
download/upload paths.
+    # The un-prefixed `path` is still returned so callers keep 
re-deriving/prefixing as before.
+    target = path
+    if os.name == "nt":
+        abs_path = os.path.abspath(path)
+        if len(abs_path) > 247 and not abs_path.startswith("\\\\?\\"):
+            target = Path("\\\\?\\" + abs_path)
+    target.mkdir(exist_ok=True, parents=True)
 
     # Create a CACHEDIR.TAG so backup tools can skip this directory.
-    _create_cachedir_tag(path)
+    _create_cachedir_tag(target)
 
     # Create a .gitignore file in the .cache/huggingface directory if it 
doesn't exist
     # Should be thread-safe enough like this.
-    gitignore = path / ".gitignore"
-    gitignore_lock = path / ".gitignore.lock"
+    gitignore = target / ".gitignore"
+    gitignore_lock = target / ".gitignore.lock"
     if not gitignore.exists():
         try:
             with WeakFileLock(gitignore_lock, timeout=0.1):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/_sandbox.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/_sandbox.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/_sandbox.py  2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/_sandbox.py  2026-07-27 
11:23:46.000000000 +0200
@@ -77,15 +77,16 @@
 # hf-mount path where the server bucket is mounted on every sandbox job
 _SERVER_MOUNT_PATH = "/.hf-sbx-server"
 
-# Job startup script (needs only /bin/sh)
+# Job startup script (needs only /bin/sh). The server bucket is public, so the 
download is
+# unauthenticated: no HF credential is ever placed in the job environment (see 
`_derive_sandbox_token`).
 _BOOTSTRAP_DOWNLOAD = """\
 set -e
 d=/tmp/.sbx-server
-if command -v wget >/dev/null 2>&1; then wget -q --header "Authorization: 
Bearer $SBX_DL_TOKEN" -O "$d" "$SBX_SERVER_URL"
-elif command -v curl >/dev/null 2>&1; then curl -fsSL -H "Authorization: 
Bearer $SBX_DL_TOKEN" -o "$d" "$SBX_SERVER_URL"
+if command -v wget >/dev/null 2>&1; then wget -q -O "$d" "$SBX_SERVER_URL"
+elif command -v curl >/dev/null 2>&1; then curl -fsSL -o "$d" "$SBX_SERVER_URL"
 else cp "$SBX_SERVER_MOUNT/sbx-server" "$d"; fi
 chmod +x "$d"
-unset SBX_DL_TOKEN SBX_SERVER_URL SBX_SERVER_MOUNT
+unset SBX_SERVER_URL SBX_SERVER_MOUNT
 exec "$d"
 """
 
@@ -1685,7 +1686,6 @@
         job_secrets["HF_TOKEN"] = hf_token
 
     job_env["SBX_SERVER_URL"] = 
f"{api.endpoint}/buckets/{constants.SANDBOX_SERVER_BUCKET}/resolve/sbx-server"
-    job_secrets["SBX_DL_TOKEN"] = hf_token
     # Always mount the server bucket as a transparent fallback for images 
without wget/curl.
     # It's only read (paying the ~2-3s FUSE cost) when the bootstrap script 
can't download.
     job_env["SBX_SERVER_MOUNT"] = _SERVER_MOUNT_PATH
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/_snapshot_download.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/_snapshot_download.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/_snapshot_download.py        
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/_snapshot_download.py        
2026-07-27 11:23:46.000000000 +0200
@@ -4,7 +4,6 @@
 
 import httpx
 from tqdm.auto import tqdm as base_tqdm
-from tqdm.contrib.concurrent import thread_map
 
 from . import constants
 from ._tree_cache import TreeCacheEntry, read_tree_cache, 
tree_cache_folder_for_local_dir, write_tree_cache
@@ -28,7 +27,7 @@
     _set_aggregate_rate_postfix,
     _update_transfer_bar,
 )
-from .utils.tqdm import _create_progress_bar
+from .utils.tqdm import _create_progress_bar, hf_thread_map
 from .utils.tqdm import tqdm as hf_tqdm
 
 
@@ -512,7 +511,7 @@
             )
         )
 
-    thread_map(
+    hf_thread_map(
         _inner_hf_hub_download,
         filtered_repo_files,
         desc=tqdm_desc,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/_upload_pipeline.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/_upload_pipeline.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/_upload_pipeline.py  
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/_upload_pipeline.py  
2026-07-27 11:23:46.000000000 +0200
@@ -622,7 +622,9 @@
             # Nothing was committed (everything unchanged/ignored): mimic 
`create_commit` and
             # return info about the latest commit on the target revision.
             logger.warning("No files have been modified since last commit. 
Skipping to prevent empty commit.")
-            info = self.api.repo_info(repo_id=self.repo_id, 
repo_type=self.repo_type, revision=self.revision)
+            info = self.api.repo_info(
+                repo_id=self.repo_id, repo_type=self.repo_type, 
revision=self.revision, token=self.token
+            )
             url_prefix = self.api.endpoint
             if self.repo_type != constants.REPO_TYPE_MODEL:
                 url_prefix = f"{url_prefix}/{self.repo_type}s"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/cli/cache.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/cli/cache.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/cli/cache.py 2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/cli/cache.py 2026-07-27 
11:23:46.000000000 +0200
@@ -418,6 +418,12 @@
             help="Limit the number of results returned. Returns only the top N 
entries after sorting.",
         ),
     ] = None,
+    show_warnings: Annotated[
+        bool,
+        Option(
+            help="Show warnings about cache inconsistencies.",
+        ),
+    ] = False,
 ) -> None:
     """List cached repositories or revisions."""
     try:
@@ -508,6 +514,15 @@
             )
         )
 
+    if len(hf_cache_info.warnings):
+        if show_warnings:
+            for warning in hf_cache_info.warnings:
+                out.warning(str(warning).rstrip(".") + ". Repo ignored.")
+        else:
+            out.warning(
+                f"Found {len(hf_cache_info.warnings)} cache inconsistencies. 
Re-run with `--show-warnings` to display them."
+            )
+
     incomplete_files = hf_cache_info.incomplete_files
     if incomplete_files:
         out.hint(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/cli/collections.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/cli/collections.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/cli/collections.py   
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/cli/collections.py   
2026-07-27 11:23:46.000000000 +0200
@@ -107,7 +107,7 @@
     ] = None,
     description: Annotated[
         str | None,
-        Option(help="A description for the collection."),
+        Option(help="A description for the collection (max 150 characters)."),
     ] = None,
     private: Annotated[
         bool,
@@ -147,7 +147,7 @@
     ] = None,
     description: Annotated[
         str | None,
-        Option(help="The new description for the collection."),
+        Option(help="The new description for the collection (max 150 
characters)."),
     ] = None,
     position: Annotated[
         int | None,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/cli/jobs.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/cli/jobs.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/cli/jobs.py  2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/cli/jobs.py  2026-07-27 
11:23:46.000000000 +0200
@@ -165,7 +165,7 @@
     str | None,
     Option(
         "--name",
-        help="Name the Job. Stored as the `name` label. Names do not have to 
be unique.",
+        help="Name the Job. Stored as the `name` label. Names do not have to 
be unique. Defaults to the image or script name plus a short hash of the 
command.",
     ),
 ]
 
@@ -367,8 +367,12 @@
         namespace=namespace,
     )
     out.result("Job started", id=job.id, url=job.url)
-    if name is None:
-        out.hint(f"Name this Job with `hf jobs labels 
{job.owner.name}/{job.id} --name NAME`.")
+    if not _has_explicit_name(name, label):
+        auto_name = (job.labels or {}).get("name")
+        out.hint(
+            f"Job auto-named '{auto_name}'. Pass `--name` or run "
+            f"`hf jobs labels {job.owner.name}/{job.id} --name NAME` to rename 
it."
+        )
     if isinstance(job.status.expose_urls, list):
         urls = "\n".join(f"  {url}" for url in job.status.expose_urls)
         out.hint(f"Exposed ports are reachable at (requires an HF token with 
read access to the job):\n{urls}")
@@ -950,8 +954,12 @@
         namespace=namespace,
     )
     out.result("Job started", id=job.id, url=job.url)
-    if name is None:
-        out.hint(f"Name this Job with `hf jobs labels 
{job.owner.name}/{job.id} --name NAME`.")
+    if not _has_explicit_name(name, label):
+        auto_name = (job.labels or {}).get("name")
+        out.hint(
+            f"Job auto-named '{auto_name}'. Pass `--name` or run "
+            f"`hf jobs labels {job.owner.name}/{job.id} --name NAME` to rename 
it."
+        )
     if isinstance(job.status.expose_urls, list):
         urls = "\n".join(f"  {url}" for url in job.status.expose_urls)
         out.hint(f"Exposed ports are reachable at (requires an HF token with 
read access to the job):\n{urls}")
@@ -1014,9 +1022,11 @@
         namespace=namespace,
     )
     out.result("Scheduled Job created", id=scheduled_job.id)
-    if name is None:
+    if not _has_explicit_name(name, label):
+        auto_name = (scheduled_job.job_spec.labels or {}).get("name")
         out.hint(
-            f"Name this scheduled Job with `hf jobs scheduled labels 
{scheduled_job.owner.name}/{scheduled_job.id} --name NAME`."
+            f"Scheduled Job auto-named '{auto_name}'. Pass `--name` or run "
+            f"`hf jobs scheduled labels 
{scheduled_job.owner.name}/{scheduled_job.id} --name NAME` to rename it."
         )
     out.hint(f"Use `hf jobs scheduled inspect 
{scheduled_job.owner.name}/{scheduled_job.id}` to view its details.")
 
@@ -1282,14 +1292,23 @@
         namespace=namespace,
     )
     out.result("Scheduled Job created", id=job.id)
-    if name is None:
-        out.hint(f"Name this scheduled Job with `hf jobs scheduled labels 
{job.owner.name}/{job.id} --name NAME`.")
+    if not _has_explicit_name(name, label):
+        auto_name = (job.job_spec.labels or {}).get("name")
+        out.hint(
+            f"Scheduled Job auto-named '{auto_name}'. Pass `--name` or run "
+            f"`hf jobs scheduled labels {job.owner.name}/{job.id} --name NAME` 
to rename it."
+        )
     out.hint(f"Use `hf jobs scheduled inspect {job.owner.name}/{job.id}` to 
view its details.")
 
 
 ### UTILS
 
 
+def _has_explicit_name(name: str | None, label: list[str] | None) -> bool:
+    """Whether the user explicitly named the Job (via `--name` or a `name=` 
label)."""
+    return name is not None or any(item.split("=", 1)[0] == "name" for item in 
label or [])
+
+
 def _parse_labels_map(labels: list[str] | None, *, name: str | None = None) -> 
dict[str, str] | None:
     """Parse label key-value pairs from CLI arguments.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/constants.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/constants.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/constants.py 2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/constants.py 2026-07-27 
11:23:46.000000000 +0200
@@ -240,7 +240,7 @@
 HF_HUB_DISABLE_TELEMETRY = (
     _is_true(os.environ.get("HF_HUB_DISABLE_TELEMETRY"))  # HF-specific env 
variable
     or _is_true(os.environ.get("DISABLE_TELEMETRY"))
-    or _is_true(os.environ.get("DO_NOT_TRACK"))  # 
https://consoledonottrack.com/
+    or _is_true(os.environ.get("DO_NOT_TRACK"))  # https://donottrack.sh/
 )
 
 HF_TOKEN_PATH = os.path.expandvars(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/dataclasses.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/dataclasses.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/dataclasses.py       
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/dataclasses.py       
2026-07-27 11:23:46.000000000 +0200
@@ -117,7 +117,7 @@
 
         # List and store validators
         field_validators: dict[str, list[Validator_T]] = {}
-        for f in fields(cls):  # type: ignore [arg-type]
+        for f in fields(cls):  # type: ignore
             validators = []
             validators.append(_create_type_validator(f))
             custom_validator = f.metadata.get("validator")
@@ -157,7 +157,7 @@
             @wraps(original_init)
             def __init__(self, *args, **kwargs: Any) -> None:
                 # Extract only the fields that are part of the dataclass
-                dataclass_fields = {f.name for f in fields(cls)}  # type: 
ignore [arg-type]
+                dataclass_fields = {f.name for f in fields(cls)}  # type: 
ignore
                 standard_kwargs = {k: v for k, v in kwargs.items() if k in 
dataclass_fields}
 
                 # User shouldn't define custom `__init__` when 
`accepts_kwargs`, and instead
@@ -412,8 +412,8 @@
         metadata = {}
     metadata["validator"] = validator
     return field(  # type: ignore
-        default=default,  # type: ignore [arg-type]
-        default_factory=default_factory,  # type: ignore [arg-type]
+        default=default,  # type: ignore
+        default_factory=default_factory,  # type: ignore
         init=init,
         repr=repr,
         hash=hash,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/file_download.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/file_download.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/file_download.py     
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/file_download.py     
2026-07-27 11:23:46.000000000 +0200
@@ -271,7 +271,7 @@
         raise ValueError("Invalid repo type")
 
     if repo_type in constants.REPO_TYPES_URL_PREFIXES:
-        repo_id = constants.REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+        repo_id = constants.REPO_TYPES_URL_PREFIXES[repo_type] + repo_id  # 
type: ignore
 
     if revision is None:
         revision = constants.DEFAULT_REVISION
@@ -1183,6 +1183,15 @@
             will_download=force_download or not is_cached,
         )
 
+    # Pointer already exists -> update the ref best-effort, then return without
+    # attempting to write to the cache (which may be mounted read-only).
+    if not force_download and os.path.exists(pointer_path):
+        try:
+            _cache_commit_hash_for_specific_revision(storage_folder, revision, 
commit_hash)
+        except OSError:
+            pass
+        return pointer_path
+
     os.makedirs(os.path.dirname(blob_path), exist_ok=True)
     os.makedirs(os.path.dirname(pointer_path), exist_ok=True)
 
@@ -1219,10 +1228,6 @@
 
     Path(lock_path).parent.mkdir(parents=True, exist_ok=True)
 
-    # pointer already exists -> immediate return
-    if not force_download and os.path.exists(pointer_path):
-        return pointer_path
-
     # Blob exists but pointer must be (safely) created -> take the lock
     if not force_download and os.path.exists(blob_path):
         with WeakFileLock(lock_path):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/src/huggingface_hub/hf_api.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/hf_api.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/hf_api.py    2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/hf_api.py    2026-07-27 
11:23:46.000000000 +0200
@@ -35,7 +35,6 @@
 import httpcore
 import httpx
 from tqdm.auto import tqdm as base_tqdm
-from tqdm.contrib.concurrent import thread_map
 
 from . import constants
 from ._buckets import (
@@ -76,6 +75,8 @@
     JobStage,
     ScheduledJobInfo,
     _create_job_spec,
+    _default_job_name_from_image,
+    _default_job_name_from_script,
     _derive_job_volume_name,
 )
 from ._space_api import (
@@ -130,6 +131,7 @@
     get_session,
     get_token,
     hf_raise_for_status,
+    hf_thread_map,
     http_backoff,
     logging,
     paginate,
@@ -422,7 +424,7 @@
 
     # Check if repo type is known (mapping "spaces" => "space" + empty value 
=> `None`)
     if repo_type in constants.REPO_TYPES_MAPPING:
-        repo_type = constants.REPO_TYPES_MAPPING[repo_type]
+        repo_type = constants.REPO_TYPES_MAPPING[repo_type]  # type: ignore
     if repo_type == "":
         repo_type = None
     if repo_type not in constants.REPO_TYPES_WITH_KERNEL and repo_type != 
"bucket":
@@ -4693,10 +4695,11 @@
             if exist_ok and err.response.status_code == 409:
                 # Repo already exists and `exist_ok=True`
                 pass
-            elif exist_ok and err.response.status_code in (401, 403):
+            elif exist_ok and err.response.status_code in (401, 402, 403):
                 # 401 -> if JWT token without create repo scope
+                # 402 -> if payment required (e.g. if Gradio/Docker Space and 
free user)
                 # 403 -> if no write permission on the namespace
-                # In both cases, repo might already exist
+                # In all 3 cases, repo might already exist
                 try:
                     self.repo_info(repo_id=repo_id, repo_type=repo_type, 
token=token)
                     if repo_type is None or repo_type == 
constants.REPO_TYPE_MODEL:
@@ -6693,6 +6696,7 @@
         repo_type: str | None = None,
         revision: str | None = None,
         token: bool | str | None = None,
+        timeout: float | None = constants.HF_HUB_DOWNLOAD_TIMEOUT,
     ) -> SafetensorsRepoMetadata:
         """
         Parse metadata for a safetensors repo on the Hub.
@@ -6719,6 +6723,10 @@
                 token, which is the recommended method for authentication (see
                 
https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
                 To disable authentication, pass `False`.
+            timeout (`float`, *optional*, defaults to 10):
+                How many seconds to wait for the server to send data before 
giving up, passed to each request that
+                fetches a safetensors file header. Set to `None` to disable 
the timeout (not recommended, as a stalled
+                connection can hang the call indefinitely).
 
         Returns:
             [`SafetensorsRepoMetadata`]: information related to safetensors 
repo.
@@ -6770,6 +6778,7 @@
                 repo_type=repo_type,
                 revision=revision,
                 token=token,
+                timeout=timeout,
             )
             return SafetensorsRepoMetadata(
                 metadata=None,
@@ -6804,10 +6813,15 @@
 
             def _parse(filename: str) -> None:
                 files_metadata[filename] = 
self.parse_safetensors_file_metadata(
-                    repo_id=repo_id, filename=filename, repo_type=repo_type, 
revision=revision, token=token
+                    repo_id=repo_id,
+                    filename=filename,
+                    repo_type=repo_type,
+                    revision=revision,
+                    token=token,
+                    timeout=timeout,
                 )
 
-            thread_map(
+            hf_thread_map(
                 _parse,
                 set(weight_map.values()),
                 desc="Parse safetensors files",
@@ -6834,6 +6848,7 @@
         repo_type: str | None = None,
         revision: str | None = None,
         token: bool | str | None = None,
+        timeout: float | None = constants.HF_HUB_DOWNLOAD_TIMEOUT,
     ) -> SafetensorsFileMetadata:
         """
         Parse metadata from a safetensors file on the Hub.
@@ -6858,6 +6873,9 @@
                 token, which is the recommended method for authentication (see
                 
https://huggingface.co/docs/huggingface_hub/quick-start#authentication).
                 To disable authentication, pass `False`.
+            timeout (`float`, *optional*, defaults to 10):
+                How many seconds to wait for the server to send data before 
giving up. Set to `None` to disable the
+                timeout (not recommended, as a stalled connection can hang the 
call indefinitely).
 
         Returns:
             [`SafetensorsFileMetadata`]: information related to a safetensors 
file.
@@ -6881,7 +6899,7 @@
         # We assume fetching 100kb is faster than making 2 GET requests. 
Therefore we always fetch the first 100kb to
         # avoid the 2nd GET in most cases.
         # See 
https://github.com/huggingface/huggingface_hub/pull/1855#discussion_r1404286419.
-        response = get_session().get(url, headers={**_headers, "range": 
"bytes=0-100000"})
+        response = get_session().get(url, headers={**_headers, "range": 
"bytes=0-100000"}, timeout=timeout)
         hf_raise_for_status(response)
 
         # 2. Parse and validate metadata size using shared helper
@@ -6891,7 +6909,9 @@
         if metadata_size <= 100000:
             metadata_as_bytes = response.content[8 : 8 + metadata_size]
         else:  # 3.b. Request full metadata
-            response = get_session().get(url, headers={**_headers, "range": 
f"bytes=8-{metadata_size + 7}"})
+            response = get_session().get(
+                url, headers={**_headers, "range": f"bytes=8-{metadata_size + 
7}"}, timeout=timeout
+            )
             hf_raise_for_status(response)
             metadata_as_bytes = response.content
 
@@ -9997,7 +10017,7 @@
             namespace (`str`, *optional*):
                 Namespace of the collection to create (username or org). Will 
default to the owner name.
             description (`str`, *optional*):
-                Description of the collection to create.
+                Description of the collection to create. The maximum size for 
a description is 150 characters.
             private (`bool`, *optional*):
                 Whether the collection should be private or not. Defaults to 
`False` (i.e. public collection).
             exists_ok (`bool`, *optional*):
@@ -10068,7 +10088,7 @@
             title (`str`):
                 Title of the collection to update.
             description (`str`, *optional*):
-                Description of the collection to update.
+                Description of the collection to update. The maximum size for 
a description is 150 characters.
             position (`int`, *optional*):
                 New position of the collection in the list of collections of 
the user.
             private (`bool`, *optional*):
@@ -11844,7 +11864,7 @@
 
             name (`str`, *optional*):
                 A name for the Job. Stored as the `name` label. Cannot be 
passed together with a `name` key in
-                `labels`. Names do not have to be unique.
+                `labels`. Names do not have to be unique. Defaults to a name 
derived from image and command (with a short hash suffix).
 
             labels (`dict[str, str]`, *optional*):
                 Labels to attach to the job (key-value pairs).
@@ -11904,6 +11924,8 @@
         """
         if namespace is None:
             namespace = self.whoami(token=token)["name"]
+        if name is None and not (labels and "name" in labels):
+            name = _default_job_name_from_image(image, command)
         job_spec = _create_job_spec(
             image=image,
             command=command,
@@ -12483,7 +12505,7 @@
 
             name (`str`, *optional*):
                 A name for the Job. Stored as the `name` label. Cannot be 
passed together with a `name` key in
-                `labels`. Names do not have to be unique.
+                `labels`. Names do not have to be unique. Defaults to a name 
derived from script and its arguments (with a short hash suffix).
 
             labels (`dict[str, str]`, *optional*):
                 Labels to attach to the job (key-value pairs).
@@ -12555,6 +12577,9 @@
         env = env or {}
         secrets = secrets or {}
 
+        if name is None and not (labels and "name" in labels):
+            name = _default_job_name_from_script(script, script_args or [])
+
         # Build command
         command, env, secrets, extra_volumes = 
self._create_uv_command_env_and_secrets(
             script=script,
@@ -12643,7 +12668,7 @@
 
             name (`str`, *optional*):
                 A name for the scheduled Job. Stored as the `name` label. 
Cannot be passed together with a `name`
-                key in `labels`. Names do not have to be unique.
+                key in `labels`. Names do not have to be unique. Defaults to a 
name derived from image and command (with a short hash suffix).
 
             labels (`dict[str, str]`, *optional*):
                 Labels to attach to the job (key-value pairs).
@@ -12693,6 +12718,8 @@
         """
         if namespace is None:
             namespace = self.whoami(token=token)["name"]
+        if name is None and not (labels and "name" in labels):
+            name = _default_job_name_from_image(image, command)
 
         # prepare payload to send to HF Jobs API
         job_spec = _create_job_spec(
@@ -13038,7 +13065,7 @@
 
             name (`str`, *optional*):
                 A name for the scheduled Job. Stored as the `name` label. 
Cannot be passed together with a `name`
-                key in `labels`. Names do not have to be unique.
+                key in `labels`. Names do not have to be unique. Defaults to a 
name derived from script and its arguments (with a short hash suffix).
 
             labels (`dict[str, str]`, *optional*):
                 Labels to attach to the job (key-value pairs).
@@ -13091,6 +13118,9 @@
             ```
         """
         image = image or "ghcr.io/astral-sh/uv:python3.12-bookworm"
+        if name is None and not (labels and "name" in labels):
+            name = _default_job_name_from_script(script, script_args or [])
+
         # Build command
         command, env, secrets, extra_volumes = 
self._create_uv_command_env_and_secrets(
             script=script,
@@ -13931,7 +13961,7 @@
                 )
                 all_adds.append((local_path, target_path))
 
-            thread_map(_download_and_collect, pending_downloads, 
desc="Downloading text files for copy")
+            hf_thread_map(_download_and_collect, pending_downloads, 
desc="Downloading text files for copy")
 
         # Send copies first (no upload needed), then adds (may need upload)
         if all_copies:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/hf_file_system.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/hf_file_system.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/hf_file_system.py    
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/hf_file_system.py    
2026-07-27 11:23:46.000000000 +0200
@@ -1258,13 +1258,9 @@
             raise ValueError(f"HfFileSystemStreamFile only supports 
cache_type='none' but got {cache_type}")
         if "w" in mode:
             raise ValueError(f"HfFileSystemStreamFile only supports reading 
but got mode='{mode}'")
-        try:
-            self.resolved_path = fs.resolve_path(path, revision=revision)
-        except FileNotFoundError as e:
-            if "w" in kwargs.get("mode", ""):
-                raise FileNotFoundError(
-                    f"{e}.\nMake sure the repository and revision exist before 
writing data."
-                ) from e
+        # Let a missing repo/revision/file surface as FileNotFoundError (as 
the buffered
+        # `HfFileSystemFile` does) instead of an AttributeError on 
`self.resolved_path` below.
+        self.resolved_path = fs.resolve_path(path, revision=revision)
         # avoid an unnecessary .info() call to instantiate .details
         self.details = {"name": self.resolved_path.unresolve(), "size": None}
         super().__init__(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/repocard.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/repocard.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/repocard.py  2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/repocard.py  2026-07-27 
11:23:46.000000000 +0200
@@ -31,7 +31,7 @@
 
 # exact same regex as in the Hub server. Please keep in sync.
 # See 
https://github.com/huggingface/moon-landing/blob/main/server/lib/ViewMarkdown.ts#L18
-REGEX_YAML_BLOCK = 
re.compile(r"^(\s*---[\r\n]+)([\S\s]*?)([\r\n]+---(\r\n|\n|$))")
+REGEX_YAML_BLOCK = 
re.compile(r"^(\s*---(?:\r\n|\r|\n))([\S\s]*?)((?:\r\n|\r|\n)---[ 
\t]*(\r\n|\n|$))")
 
 
 class RepoCard:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/repocard_data.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/repocard_data.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/repocard_data.py     
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/repocard_data.py     
2026-07-27 11:23:46.000000000 +0200
@@ -211,9 +211,10 @@
             `str`: CardData represented as a YAML block.
         """
         if original_order:
+            original_order_set = set(original_order)
             self.__dict__ = {
                 k: self.__dict__[k]
-                for k in original_order + list(set(self.__dict__.keys()) - 
set(original_order))
+                for k in original_order + [k for k in self.__dict__ if k not 
in original_order_set]
                 if k in self.__dict__
             }
         return yaml_dump(self.to_dict(), sort_keys=False, 
line_break=line_break).strip()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/utils/__init__.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/utils/__init__.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/utils/__init__.py    
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/utils/__init__.py    
2026-07-27 11:23:46.000000000 +0200
@@ -126,6 +126,7 @@
     are_progress_bars_disabled,
     disable_progress_bars,
     enable_progress_bars,
+    hf_thread_map,
     is_tqdm_disabled,
     silent_tqdm,
     tqdm,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub/utils/tqdm.py 
new/huggingface_hub-1.25.1/src/huggingface_hub/utils/tqdm.py
--- old/huggingface_hub-1.24.0/src/huggingface_hub/utils/tqdm.py        
2026-07-17 11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub/utils/tqdm.py        
2026-07-27 11:23:46.000000000 +0200
@@ -84,10 +84,11 @@
 import os
 import threading
 import warnings
-from collections.abc import Iterator
+from collections.abc import Iterable, Iterator
+from concurrent.futures import ThreadPoolExecutor, as_completed
 from contextlib import contextmanager, nullcontext
 from pathlib import Path
-from typing import ContextManager
+from typing import Callable, ContextManager, TypeVar, cast
 
 from tqdm.auto import tqdm as old_tqdm
 
@@ -370,3 +371,79 @@
         initial=initial,
         desc=desc,
     )
+
+
+T = TypeVar("T")
+R = TypeVar("R")
+
+
+def _make_thread_pool_executor(*, max_workers: int | None, bar_class: 
type[old_tqdm]) -> ThreadPoolExecutor:
+    """Create a `ThreadPoolExecutor` that shares the tqdm lock with its worker 
threads.
+
+    Like `tqdm.contrib.concurrent.thread_map`, we make sure the bar class has 
a lock and pass it to the
+    worker threads, so that bars created inside the mapped function (e.g. the 
per-file download bars in
+    `snapshot_download`) don't race on concurrent `update()` calls. 
`get_lock()` creates the shared
+    class-level lock if needed. Classes that don't implement tqdm's locking 
API (e.g. `silent_tqdm`)
+    render nothing anyway, so no lock is needed.
+    """
+    if hasattr(bar_class, "get_lock") and hasattr(bar_class, "set_lock"):
+        return ThreadPoolExecutor(
+            max_workers=max_workers, initializer=bar_class.set_lock, 
initargs=(bar_class.get_lock(),)
+        )
+    return ThreadPoolExecutor(max_workers=max_workers)
+
+
+def hf_thread_map(
+    fn: Callable[[T], R],
+    iterable: Iterable[T],
+    *,
+    max_workers: int | None = None,
+    tqdm_class: type[old_tqdm] | None = None,
+    **tqdm_kwargs,
+) -> list[R]:
+    """Drop-in replacement for `tqdm.contrib.concurrent.thread_map`.
+
+    The version shipped by `tqdm` consumes results in submission order and the 
progress bar only advances when the "next
+    item in input order" finishes. A single slow early item pins the bar near 
zero while later items
+    complete, with everything flushed at once at the end. This helper avoids 
that problem by consuming items in completion
+    order.
+
+    See https://github.com/huggingface/huggingface_hub/issues/4518 for more 
details.
+
+    Args:
+        fn (`Callable`):
+            Function to apply to each item.
+        iterable (`Iterable`):
+            Items to process.
+        max_workers (`int`, *optional*):
+            Maximum number of worker threads. Defaults to tqdm's own default.
+        tqdm_class (`type`, *optional*):
+            Progress bar class to use. Defaults to `huggingface_hub`'s `tqdm`.
+        **tqdm_kwargs:
+            Additional keyword arguments forwarded to the progress bar (e.g. 
`desc`).
+
+    Returns:
+        `list`: Results in the same order as `iterable`.
+    """
+    items = list(iterable)
+    if max_workers is None:
+        max_workers = min(32, (os.cpu_count() or 1) + 4)
+    tqdm_kwargs.setdefault("total", len(items))
+    bar_class = tqdm_class or tqdm
+    results: list[R | None] = [None] * len(items)
+    with (
+        bar_class(**tqdm_kwargs) as pbar,
+        _make_thread_pool_executor(max_workers=max_workers, 
bar_class=bar_class) as executor,
+    ):
+        future_to_index = {executor.submit(fn, item): index for index, item in 
enumerate(items)}
+        try:
+            for future in as_completed(future_to_index):
+                results[future_to_index[future]] = future.result()
+                pbar.update(1)
+        except BaseException:
+            # Cancel not-yet-started tasks so a single failure doesn't keep 
the whole queue running
+            # (mirrors `tqdm.contrib.concurrent.thread_map`, which relies on 
`Executor.map` for this).
+            for future in future_to_index:
+                future.cancel()
+            raise
+    return cast("list[R]", results)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/huggingface_hub-1.24.0/src/huggingface_hub.egg-info/PKG-INFO 
new/huggingface_hub-1.25.1/src/huggingface_hub.egg-info/PKG-INFO
--- old/huggingface_hub-1.24.0/src/huggingface_hub.egg-info/PKG-INFO    
2026-07-17 11:53:43.000000000 +0200
+++ new/huggingface_hub-1.25.1/src/huggingface_hub.egg-info/PKG-INFO    
2026-07-27 11:23:51.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: huggingface_hub
-Version: 1.24.0
+Version: 1.25.1
 Summary: Client library to download and publish models, datasets and other 
repos on the huggingface.co hub
 Home-page: https://github.com/huggingface/huggingface_hub
 Author: Hugging Face, Inc.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_hf_api.py 
new/huggingface_hub-1.25.1/tests/test_hf_api.py
--- old/huggingface_hub-1.24.0/tests/test_hf_api.py     2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_hf_api.py     2026-07-27 
11:23:46.000000000 +0200
@@ -2656,11 +2656,6 @@
             with pytest.raises(HfHubHTTPError, match=r".*Repository Not 
Found.*"):
                 _ = api.dataset_info(repo_id=self.repo_id)
 
-    def test_list_private_datasets(self, api: HfApi):
-        kwargs = {"sort": "created_at", "limit": 100, "author": USER}
-        assert all(dataset.id != self.repo_id for dataset in 
api.list_datasets(token=False, **kwargs))
-        assert any(dataset.id == self.repo_id for dataset in 
api.list_datasets(token=TOKEN, **kwargs))
-
     def test_list_private_models(self, api: HfApi):
         kwargs = {"sort": "created_at", "limit": 100, "author": USER}
         assert all(model.id != self.repo_id for model in 
api.list_models(token=False, **kwargs))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_hf_file_system.py 
new/huggingface_hub-1.25.1/tests/test_hf_file_system.py
--- old/huggingface_hub-1.24.0/tests/test_hf_file_system.py     2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_hf_file_system.py     2026-07-27 
11:23:46.000000000 +0200
@@ -817,6 +817,15 @@
         fs.resolve_path("username/my_model@dev", revision="main")
 
 
+def test_stream_file_open_if_not_found():
+    # Regression test: opening a missing file in streaming mode (block_size=0) 
must raise
+    # FileNotFoundError like the buffered HfFileSystemFile, not an 
AttributeError.
+    fs = HfFileSystem()
+    with patch.object(fs, "resolve_path", 
side_effect=FileNotFoundError("missing/repo")):
+        with pytest.raises(FileNotFoundError):
+            HfFileSystemStreamFile(fs, 
"hf://missing/repo/not_existing_file.txt")
+
+
 @pytest.mark.parametrize(
     ("not_supported_path", "expected_error"),
     [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_jobs_api.py 
new/huggingface_hub-1.25.1/tests/test_jobs_api.py
--- old/huggingface_hub-1.24.0/tests/test_jobs_api.py   2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_jobs_api.py   2026-07-27 
11:23:46.000000000 +0200
@@ -3,7 +3,11 @@
 import pytest
 
 from huggingface_hub import HfApi, JobStage
-from huggingface_hub._jobs_api import JobInfo
+from huggingface_hub._jobs_api import (
+    JobInfo,
+    _default_job_name_from_image,
+    _default_job_name_from_script,
+)
 
 
 def _job_info(stage: str, job_id: str = "job-id") -> JobInfo:
@@ -74,3 +78,54 @@
         ):
             job = self.api.wait_for_job(job_id="job-id", namespace="user", 
stages=[JobStage.RUNNING])
         assert job.status.stage == "ERROR"
+
+
[email protected](
+    "image, expected",
+    [
+        # Plain image (no registry, no tag).
+        ("ubuntu", "ubuntu"),
+        # Tag is kept, with disallowed chars replaced by '-'.
+        ("python:3.12", "python-3-12"),
+        # Registry host and namespace are dropped, last component + tag is 
kept.
+        ("pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel", 
"pytorch-2-6-0-cuda12-4-cudnn9-devel"),
+        ("ghcr.io/astral-sh/uv:python3.12-bookworm", "uv-python3-12-bookworm"),
+        # Space references keep the `namespace/repo` id (sanitized), for every 
supported prefix.
+        ("hf.co/spaces/lhoestq/duckdb", "lhoestq-duckdb"),
+        ("https://huggingface.co/spaces/lhoestq/duckdb";, "lhoestq-duckdb"),
+    ],
+)
+def test_default_job_name_from_image(image: str, expected: str) -> None:
+    # The base name is derived from the image; a short hash of the command 
line is appended.
+    assert _default_job_name_from_image(image, ["python", "-c", 
"print(1)"]).startswith(expected + "-")
+
+
[email protected](
+    "script, expected",
+    [
+        # Local script: keep the stem, drop the '.py' extension.
+        ("my_script.py", "my_script"),
+        ("./train.py", "train"),
+        # URL: keep the last path component, drop query/fragment and extension.
+        
("https://raw.githubusercontent.com/huggingface/trl/main/trl/scripts/sft.py";, 
"sft"),
+        ("https://example.co/a/sft.py?raw=1";, "sft"),
+        # Command (no extension): kept as-is.
+        ("lighteval", "lighteval"),
+        # Dots in the stem are disallowed chars and get replaced by '-'.
+        ("my.weird.script.py", "my-weird-script"),
+    ],
+)
+def test_default_job_name_from_script(script: str, expected: str) -> None:
+    # The base name is derived from the script; a short hash of the command 
line is appended.
+    assert _default_job_name_from_script(script, []).startswith(expected + "-")
+
+
+def test_default_job_name_hash_groups_and_splits_by_command() -> None:
+    # Same image but different commands must yield different names (splits 
distinct runs)...
+    truc = _default_job_name_from_image("python:3.12", ["foo", "--truc"])
+    bar = _default_job_name_from_image("python:3.12", ["foo", "--bar"])
+    assert truc.startswith("python-3-12-")
+    assert bar.startswith("python-3-12-")
+    assert truc != bar
+    # ...while the same command yields the same name (groups identical runs).
+    assert truc == _default_job_name_from_image("python:3.12", ["foo", 
"--truc"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_local_folder.py 
new/huggingface_hub-1.25.1/tests/test_local_folder.py
--- old/huggingface_hub-1.24.0/tests/test_local_folder.py       2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_local_folder.py       2026-07-27 
11:23:46.000000000 +0200
@@ -143,6 +143,33 @@
     assert str(paths.metadata_path).startswith("\\\\?\\")
 
 
[email protected](os.name != "nt", reason="Windows-specific test.")
[email protected]("cache_dir_len", [252, 300])
+def test_local_download_paths_long_local_dir(tmp_path: Path, cache_dir_len: 
int):
+    r"""A deep ``local_dir`` must not crash when its ``.cache/huggingface`` 
folder exceeds the
+    Windows directory path limit (247 chars, i.e. MAX_PATH minus room for an 
8.3 file name).
+
+    Unlike ``test_local_download_paths_long_paths`` (long *filename*, short 
dir), here the
+    ``local_dir`` itself is long, so the limit is first hit while 
``_huggingface_dir`` creates
+    ``<local_dir>/.cache/huggingface``. Without the extended-length ``\\?\`` 
prefix that ``mkdir``
+    raised ``FileNotFoundError`` (WinError 206) before the download/upload 
paths were built.
+    Both boundary cases are covered: 248-259 (only directory creation exceeds 
the limit) and
+    260+ (file creation would fail too).
+    """
+    # Create a `local_dir` so that `<local_dir>/.cache/huggingface` is 
`cache_dir_len` chars long.
+    # Use the extended-length prefix here since a plain mkdir of such a path 
would itself fail.
+    padding = "d" * max(1, cache_dir_len - len(str(tmp_path / ".cache" / 
"huggingface")) - 1)
+    local_dir = tmp_path / padding
+    os.makedirs("\\\\?\\" + os.path.abspath(local_dir), exist_ok=True)
+    assert len(str(local_dir / ".cache" / "huggingface")) >= 
max(cache_dir_len, 248)
+
+    # Must not raise, and the (extended-length) parent directories must have 
been created.
+    paths = get_local_download_paths(local_dir, "config.json")
+    assert str(paths.metadata_path).startswith("\\\\?\\")
+    assert paths.metadata_path.parent.is_dir()
+    assert paths.file_path.parent.is_dir()
+
+
 def test_write_download_metadata(tmp_path: Path):
     """Test download metadata content is valid."""
     # Write metadata
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_repocard.py 
new/huggingface_hub-1.25.1/tests/test_repocard.py
--- old/huggingface_hub-1.24.0/tests/test_repocard.py   2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_repocard.py   2026-07-27 
11:23:46.000000000 +0200
@@ -14,6 +14,7 @@
 import copy
 import os
 import re
+import time
 from pathlib import Path
 
 import pytest
@@ -730,6 +731,59 @@
     def test_does_not_match_with_leading_text(self):
         assert REGEX_YAML_BLOCK.search("something\n---\nmetadata: 1\n---") is 
None
 
+    def test_crlf_card_parses_metadata_and_retains_body(self):
+        # A valid CRLF card must parse to the correct metadata and retain the 
body: the
+        # opener must consume the full `\r\n` (not just the `\r`), so the YAML 
block is
+        # clean and the match ends right after the closing fence line.
+        content = "---\r\nlanguage: en\r\n---\r\nSome body\r\n"
+        match = REGEX_YAML_BLOCK.search(content)
+        assert match is not None
+        assert yaml.safe_load(match.group(2)) == {"language": "en"}
+        assert content[match.end() :] == "Some body\r\n"
+
+        card = RepoCard(content)
+        assert card.data.to_dict() == {"language": "en"}
+        assert "Some body" in card.text
+
+    def test_crlf_empty_frontmatter_retains_trailing_content(self):
+        # An empty-frontmatter card must not silently drop the trailing 
content, and the
+        # CRLF form must behave like the LF form: no metadata, text retained.
+        for content in ("---\r\n---\r\ntrailing content\r\n", 
"---\n---\ntrailing content\n"):
+            card = RepoCard(content, ignore_metadata_errors=True)
+            assert card.data.to_dict() == {}
+            assert "trailing content" in card.text
+
+    def test_crlf_empty_frontmatter_followed_by_real_frontmatter(self):
+        # Regression test: with a single-char newline class at the delimiters, 
the opener of
+        # a CRLF card consumed only the `\r` and the leftover `\n---` faked an 
early closer,
+        # demoting the real YAML block between the 2nd and 3rd fences into the 
card text.
+        content = "---\r\n---\r\nlanguage: en\r\n---\r\nbody\r\n"
+        match = REGEX_YAML_BLOCK.search(content)
+        assert match is not None
+        assert yaml.safe_load(match.group(2)) == {"language": "en"}
+        assert content[match.end() :] == "body\r\n"
+
+    @pytest.mark.parametrize(
+        "payload",
+        [
+            # Valid `---` opener followed by a long newline run and no closing 
fence.
+            pytest.param("---\n" + "\n" * 200_000, id="lf"),
+            # Same, but with a title line and trailing body so it resembles 
real card text.
+            pytest.param("---\ntitle: x\n" + "\n" * 200_000 + "not a fence", 
id="lf-with-body"),
+            # CRLF variant.
+            pytest.param("---\r\n" + "\r\n" * 200_000, id="crlf"),
+        ],
+    )
+    def test_no_catastrophic_backtracking(self, payload):
+        # A YAML opener followed by a large newline run with no closing fence 
must not
+        # trigger catastrophic backtracking. These inputs cannot match, and 
the search
+        # must fail quickly instead of backtracking for tens of seconds.
+        start = time.perf_counter()
+        match = REGEX_YAML_BLOCK.search(payload)
+        elapsed = time.perf_counter() - start
+        assert match is None
+        assert elapsed < 1.0, f"REGEX_YAML_BLOCK.search took {elapsed:.2f}s 
(catastrophic backtracking)"
+
 
 class TestModelCard:
     def test_model_card_with_invalid_model_index(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_repocard_data.py 
new/huggingface_hub-1.25.1/tests/test_repocard_data.py
--- old/huggingface_hub-1.24.0/tests/test_repocard_data.py      2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_repocard_data.py      2026-07-27 
11:23:46.000000000 +0200
@@ -73,6 +73,19 @@
         # .pop
         assert metadata.pop("foo") == "BAR"
 
+    def test_to_yaml_preserves_order_of_appended_keys(self):
+        # Keys not listed in `original_order` must be appended in their 
existing
+        # (insertion) order and deterministically, as documented -- not 
shuffled
+        # through set() ordering.
+        metadata = CardData(license="mit", tags=["a"])
+        for key in ["zzz", "aaa", "mmm", "bbb"]:
+            metadata[key] = "x"
+
+        yaml_block = metadata.to_yaml(original_order=["license", "tags"])
+        keys = [line.split(":")[0] for line in yaml_block.splitlines() if line 
and not line.startswith((" ", "-"))]
+
+        assert keys == ["license", "tags", "zzz", "aaa", "mmm", "bbb"]
+
 
 class TestModelCardData:
     def test_eval_results_to_model_index(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/huggingface_hub-1.24.0/tests/test_utils_tqdm.py 
new/huggingface_hub-1.25.1/tests/test_utils_tqdm.py
--- old/huggingface_hub-1.24.0/tests/test_utils_tqdm.py 2026-07-17 
11:53:37.000000000 +0200
+++ new/huggingface_hub-1.25.1/tests/test_utils_tqdm.py 2026-07-27 
11:23:46.000000000 +0200
@@ -1,6 +1,7 @@
 import io
 import logging
 import sys
+import threading
 import time
 from pathlib import Path
 from unittest.mock import patch
@@ -13,6 +14,7 @@
     are_progress_bars_disabled,
     disable_progress_bars,
     enable_progress_bars,
+    hf_thread_map,
     tqdm,
     tqdm_stream_file,
 )
@@ -257,6 +259,100 @@
         assert "10/10" in captured.err
 
 
+class TestHfThreadMap:
+    def test_returns_results_in_input_order(self):
+        # Earlier items finish later, so completion order differs from input 
order.
+        def fn(x):
+            time.sleep(0.02 * (4 - x))
+            return x * 10
+
+        assert hf_thread_map(fn, range(5), max_workers=5, disable=True) == [0, 
10, 20, 30, 40]
+
+    def test_bar_advances_on_completion_not_submission_order(self):
+        # Regression test for 
https://github.com/huggingface/huggingface_hub/issues/4518: a slow
+        # first-submitted item must not prevent the bar from advancing when 
later items complete.
+        slow_release = threading.Event()
+        fast_done = threading.Event()
+        updates = []
+
+        class RecordingTqdm(vanilla_tqdm):
+            def update(self, n=1):
+                updates.append(n)
+                return super().update(n)
+
+        def fn(x):
+            if x == 0:
+                slow_release.wait(timeout=5)  # first-submitted item stays 
blocked
+            else:
+                fast_done.set()
+            return x
+
+        results = []
+
+        def run():
+            results.append(hf_thread_map(fn, range(2), max_workers=2, 
tqdm_class=RecordingTqdm, disable=True))
+
+        worker = threading.Thread(target=run)
+        worker.start()
+        try:
+            assert fast_done.wait(timeout=5)
+            # The fast item completed; the bar should advance even though the 
slow item is still blocked.
+            deadline = time.time() + 5
+            while not updates and time.time() < deadline:
+                time.sleep(0.01)
+            assert updates
+        finally:
+            slow_release.set()
+            worker.join(timeout=5)
+
+        assert not worker.is_alive()
+        assert results == [[0, 1]]
+
+    def test_cancels_pending_tasks_on_error(self):
+        started = []
+        lock = threading.Lock()
+
+        def fn(x):
+            with lock:
+                started.append(x)
+            if x == 0:
+                raise ValueError("boom")
+            time.sleep(0.05)
+            return x
+
+        with pytest.raises(ValueError, match="boom"):
+            hf_thread_map(fn, range(50), max_workers=1, disable=True)
+
+        # The failing first task cancels the queued tasks instead of running 
all 50.
+        assert len(started) < 50
+
+    def test_shares_tqdm_lock_with_worker_threads(self):
+        # A custom bar class: hf_thread_map must share a single lock with the 
worker threads so bars
+        # created inside `fn` don't race on concurrent updates (mirrors 
tqdm.contrib's `ensure_lock`).
+        class CustomTqdm(vanilla_tqdm):
+            pass
+
+        seen_locks = []
+        lock = threading.Lock()
+
+        def fn(x):
+            with lock:
+                seen_locks.append(CustomTqdm.get_lock())
+            return x
+
+        hf_thread_map(fn, range(8), max_workers=4, tqdm_class=CustomTqdm, 
disable=True)
+
+        # Every worker thread observed the same, single shared lock.
+        assert seen_locks
+        assert all(observed is seen_locks[0] for observed in seen_locks)
+
+    def test_works_with_class_without_lock_api(self):
+        # Classes that don't implement tqdm's locking API (e.g. `silent_tqdm`) 
must not crash.
+        from huggingface_hub.utils import silent_tqdm
+
+        assert hf_thread_map(lambda x: x * 2, range(4), max_workers=2, 
tqdm_class=silent_tqdm) == [0, 2, 4, 6]
+
+
 class TestCreateProgressBarCustomClass:
     def test_custom_class_not_disabled_in_non_tty(self):
         fake_stderr = io.StringIO()

Reply via email to