Add Hugging Face Hub integration for safetensor files

- Add `tsr hf search` to search HF models with safetensor files
- Add `tsr hf get` to view model info and list safetensor files
- Add `tsr hf files` to list safetensor files in a model
- Add `tsr hf dl` to download safetensor files from HF

Uses official huggingface_hub library for API access.
Only safetensor files are supported (enforced at search and download).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adam Ladachowski
2026-02-15 19:27:23 +01:00
parent 29d96e2a00
commit eb151dac8d
6 changed files with 634 additions and 1 deletions
+36
View File
@@ -92,6 +92,30 @@ tsr dl -H ABC123...
tsr dl -m 12345 -o ./models
```
### Hugging Face Integration
Search and download safetensor files from Hugging Face Hub.
```bash
# Search for models with safetensor files
tsr hf search "flux"
# Filter by author or pipeline
tsr hf search -a stabilityai -p text-to-image
# Get model info and list safetensor files
tsr hf get black-forest-labs/FLUX.1-schnell
# List safetensor files only
tsr hf files black-forest-labs/FLUX.1-schnell
# Download a specific safetensor file
tsr hf dl black-forest-labs/FLUX.1-schnell -f ae.safetensors
# Download all safetensor files from a model
tsr hf dl author/model --all -o ./models
```
### Inspect Local Files
```bash
@@ -293,6 +317,18 @@ Data is stored in XDG-compliant paths:
| `--commercial` | none, image, rent, sell |
| `--page` | Page number for pagination |
## Hugging Face Options
| Option | Description |
|--------|-------------|
| `-a, --author` | Filter by author/organization |
| `-p, --pipeline` | Pipeline tag (text-to-image, text-generation, etc.) |
| `-s, --sort` | Sort by (downloads, likes, created_at, trending_score) |
| `-n, --limit` | Number of results (default: 25) |
| `-f, --file` | Specific file to download |
| `--all` | Download all safetensor files |
| `-o, --output` | Output directory |
## Generate Options
| Option | Description |
+5
View File
@@ -10,6 +10,7 @@ dependencies = [
"rich>=13.0.0",
"typer>=0.15.0",
"websocket-client>=1.9.0",
"huggingface_hub>=0.25.0",
]
[project.optional-dependencies]
@@ -98,6 +99,10 @@ ignore_missing_imports = true
module = ["scalar_fastapi.*"]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = ["huggingface_hub.*"]
ignore_missing_imports = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=tensors --cov-report=term-missing"
+128
View File
@@ -37,10 +37,19 @@ from tensors.display import (
_format_size,
display_civitai_data,
display_file_info,
display_hf_model_info,
display_hf_search_results,
display_local_metadata,
display_model_info,
display_search_results,
)
from tensors.hf import (
download_all_safetensors,
download_hf_safetensor,
get_hf_model,
list_safetensor_files,
search_hf_models,
)
from tensors.safetensor import compute_sha256, get_base_name, read_safetensor_metadata
# Key masking threshold
@@ -720,6 +729,124 @@ def db_stats(
console.print(table)
# =============================================================================
# Hugging Face Commands
# =============================================================================
hf_app = typer.Typer(name="hf", help="Hugging Face Hub commands for safetensor files.")
app.add_typer(hf_app)
@hf_app.command("search")
def hf_search(
query: Annotated[str | None, typer.Argument(help="Search query")] = None,
author: Annotated[str | None, typer.Option("-a", "--author", help="Filter by author/org")] = None,
pipeline: Annotated[str | None, typer.Option("-p", "--pipeline", help="Pipeline tag (text-to-image, etc.)")] = None,
sort: Annotated[str | None, typer.Option("-s", "--sort", help="Sort by (downloads, likes, created_at)")] = None,
limit: Annotated[int, typer.Option("-n", "--limit", help="Max results")] = 25,
json_output: Annotated[bool, typer.Option("--json", "-j", help="Output as JSON")] = False,
) -> None:
"""Search Hugging Face for models with safetensor files."""
results = search_hf_models(
query=query,
author=author,
pipeline_tag=pipeline,
sort=sort,
limit=limit,
console=console,
)
if json_output:
console.print_json(data=results)
return
display_hf_search_results(results, console)
@hf_app.command("get")
def hf_get(
model_id: Annotated[str, typer.Argument(help="Model ID (e.g., stabilityai/stable-diffusion-xl-base-1.0)")],
json_output: Annotated[bool, typer.Option("--json", "-j", help="Output as JSON")] = False,
) -> None:
"""Get Hugging Face model info and list safetensor files."""
model = get_hf_model(model_id, console=console)
if not model:
raise typer.Exit(1)
if json_output:
console.print_json(data=model)
return
display_hf_model_info(model, console)
@hf_app.command("files")
def hf_files(
model_id: Annotated[str, typer.Argument(help="Model ID")],
json_output: Annotated[bool, typer.Option("--json", "-j", help="Output as JSON")] = False,
) -> None:
"""List safetensor files in a Hugging Face model."""
files = list_safetensor_files(model_id, console=console)
if json_output:
console.print_json(data=files)
return
if not files:
console.print("[yellow]No safetensor files found.[/yellow]")
return
console.print(f"[bold]Safetensor files in {model_id}:[/bold]")
for i, f in enumerate(files, 1):
console.print(f" {i}. {f}")
@hf_app.command("dl")
def hf_download(
model_id: Annotated[str, typer.Argument(help="Model ID (e.g., stabilityai/stable-diffusion-xl-base-1.0)")],
filename: Annotated[str | None, typer.Option("-f", "--file", help="Specific file to download")] = None,
output: Annotated[Path | None, typer.Option("-o", "--output", help="Output directory")] = None,
all_files: Annotated[bool, typer.Option("--all", "-a", help="Download all safetensor files")] = False,
) -> None:
"""Download safetensor files from Hugging Face.
Examples:
tsr hf dl stabilityai/stable-diffusion-xl-base-1.0 -f sd_xl_base_1.0.safetensors
tsr hf dl author/model --all
"""
output_dir = output or Path.cwd()
if all_files:
downloaded = download_all_safetensors(model_id, output_dir, console=console)
if downloaded:
console.print(f"[green]Downloaded {len(downloaded)} files[/green]")
else:
console.print("[red]No files downloaded[/red]")
raise typer.Exit(1)
return
if not filename:
# List files and prompt or show help
files = list_safetensor_files(model_id, console=console)
if not files:
console.print("[red]No safetensor files found in model[/red]")
raise typer.Exit(1)
if len(files) == 1:
filename = files[0]
console.print(f"[dim]Downloading only safetensor file: {filename}[/dim]")
else:
console.print("[yellow]Multiple safetensor files found. Specify one with -f or use --all:[/yellow]")
for i, f in enumerate(files, 1):
console.print(f" {i}. {f}")
raise typer.Exit(1)
result = download_hf_safetensor(model_id, filename, output_dir, console=console)
if not result:
raise typer.Exit(1)
def main() -> int:
"""Main entry point."""
# Handle legacy invocation: tsr <file.safetensors> -> tsr info <file>
@@ -732,6 +859,7 @@ def main() -> int:
"config",
"serve",
"db",
"hf",
)
if len(sys.argv) > 1 and not sys.argv[1].startswith("-"):
arg = sys.argv[1]
+132
View File
@@ -329,3 +329,135 @@ def display_search_results(results: dict[str, Any], console: Console) -> None:
total = metadata.get("totalItems", len(items))
console.print(f"\n[dim]Showing {len(items)} of {total:,} results[/dim]")
console.print("[dim]Use 'tsr get <id>' to view details or 'tsr dl -m <id>' to download[/dim]")
# =============================================================================
# Hugging Face Display Functions
# =============================================================================
def _format_bytes(size_bytes: int) -> str:
"""Format size in bytes to human-readable string."""
if size_bytes < KB:
return f"{size_bytes} B"
if size_bytes < KB * KB:
return f"{size_bytes / KB:.1f} KB"
if size_bytes < KB * KB * KB:
return f"{size_bytes / KB / KB:.1f} MB"
return f"{size_bytes / KB / KB / KB:.2f} GB"
def _build_hf_search_table(console: Console) -> Table:
"""Build Hugging Face search results table."""
id_width = 40
dls_width = 8
likes_width = 6
files_width = 5
terminal_width = console.size.width
fixed_width = id_width + dls_width + likes_width + files_width
overhead = 17
author_width = max(15, (terminal_width - fixed_width - overhead) // 2)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Model ID", style="cyan", width=id_width, no_wrap=True, overflow="ellipsis")
table.add_column("Author", style="yellow", width=author_width, no_wrap=True, overflow="ellipsis")
table.add_column("DLs", justify="right", width=dls_width, no_wrap=True)
table.add_column("Likes", justify="right", width=likes_width, no_wrap=True)
table.add_column("Files", justify="right", width=files_width, no_wrap=True)
return table
def display_hf_search_results(models: list[dict[str, Any]], console: Console) -> None:
"""Display Hugging Face search results in a table."""
if not models:
console.print("[yellow]No results found.[/yellow]")
return
table = _build_hf_search_table(console)
for model in models:
model_id = model.get("id", "N/A")
author = model.get("author", model_id.split("/")[0] if "/" in model_id else "N/A")
downloads = _format_count(model.get("downloads", 0))
likes = _format_count(model.get("likes", 0))
safetensor_files = model.get("_safetensor_files", [])
files_count = str(len(safetensor_files))
table.add_row(model_id, author, downloads, likes, files_count)
console.print()
console.print(table)
console.print(f"\n[dim]Showing {len(models)} models with safetensor files[/dim]")
console.print("[dim]Use 'tsr hf get <model_id>' to view details or 'tsr hf dl <model_id>' to download[/dim]")
def _build_hf_model_table(console: Console) -> Table:
"""Build Hugging Face model info table."""
prop_width = 12
terminal_width = console.size.width
overhead = 7
value_width = max(40, terminal_width - prop_width - overhead)
table = Table(title="Hugging Face Model", show_header=True, header_style="bold magenta")
table.add_column("Property", style="cyan", width=prop_width, no_wrap=True)
table.add_column("Value", style="green", width=value_width, no_wrap=True, overflow="ellipsis")
return table
def display_hf_model_info(model: dict[str, Any], console: Console) -> None:
"""Display Hugging Face model information."""
if not model:
console.print("[yellow]Model not found.[/yellow]")
return
table = _build_hf_model_table(console)
model_id = model.get("id", "N/A")
table.add_row("Model ID", model_id)
table.add_row("Author", model.get("author", "N/A"))
table.add_row("Downloads", f"{model.get('downloads', 0):,}")
table.add_row("Likes", f"{model.get('likes', 0):,}")
# Handle datetime or string values
created = model.get("created_at") or model.get("createdAt")
if created:
created_str = created.strftime("%Y-%m-%d") if hasattr(created, "strftime") else str(created)[:10]
table.add_row("Created", created_str)
updated = model.get("last_modified") or model.get("lastModified")
if updated:
updated_str = updated.strftime("%Y-%m-%d") if hasattr(updated, "strftime") else str(updated)[:10]
table.add_row("Updated", updated_str)
tags = model.get("tags", [])
if tags:
table.add_row("Tags", ", ".join(tags[:MAX_TAGS_DISPLAY]) + ("..." if len(tags) > MAX_TAGS_DISPLAY else ""))
pipeline = model.get("pipeline_tag")
if pipeline:
table.add_row("Pipeline", pipeline)
console.print()
console.print(table)
# Display safetensor files
safetensor_files = model.get("_safetensor_files", [])
if safetensor_files:
files_table = Table(title="Safetensor Files", show_header=True, header_style="bold magenta")
files_table.add_column("#", style="dim", width=3, justify="right")
files_table.add_column("Filename", style="cyan", no_wrap=True, overflow="ellipsis")
files_table.add_column("Size", style="green", justify="right", width=10)
for i, f in enumerate(safetensor_files, 1):
filename = f.get("rfilename", "N/A")
size = _format_bytes(f.get("size", 0)) if f.get("size") else "N/A"
files_table.add_row(str(i), filename, size)
console.print()
console.print(files_table)
console.print()
console.print(f"[bold blue]View on HuggingFace:[/bold blue] https://huggingface.co/{model_id}")
+246
View File
@@ -0,0 +1,246 @@
"""Hugging Face Hub integration for safetensor files."""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING, Any
from huggingface_hub import HfApi, hf_hub_download, list_repo_files
from huggingface_hub.errors import RepositoryNotFoundError
if TYPE_CHECKING:
from rich.console import Console
# Shared API instance
_api: HfApi | None = None
def _get_api(token: str | None = None) -> HfApi:
"""Get or create HfApi instance."""
global _api # noqa: PLW0603
if _api is None:
_api = HfApi(token=token)
return _api
def search_hf_models(
query: str | None = None,
*,
author: str | None = None,
tags: list[str] | None = None,
pipeline_tag: str | None = None,
sort: str | None = None,
limit: int = 25,
token: str | None = None,
console: Console | None = None,
) -> list[dict[str, Any]]:
"""Search Hugging Face models with safetensor files.
Args:
query: Search query string
author: Filter by author/organization
tags: Additional tags to filter by
pipeline_tag: Pipeline type (text-generation, text-to-image, etc.)
sort: Sort field (downloads, likes, created_at, trending_score)
limit: Maximum results
token: HuggingFace API token
console: Rich console for output
Returns:
List of model info dictionaries with safetensor files
"""
api = _get_api(token)
# Build filter list - always include safetensors
filters = ["safetensors"]
if tags:
filters.extend(tags)
try:
models = api.list_models(
search=query,
author=author,
filter=filters,
pipeline_tag=pipeline_tag,
sort=sort or "downloads",
limit=limit,
expand=["siblings", "downloads", "likes", "author", "lastModified", "createdAt", "tags"],
)
results = []
for model in models:
model_dict = model.__dict__.copy()
# Get safetensor files from siblings
siblings = getattr(model, "siblings", None) or []
safetensor_files = [
{"rfilename": s.rfilename, "size": getattr(s, "size", None)}
for s in siblings
if s.rfilename.endswith(".safetensors")
]
if safetensor_files:
model_dict["_safetensor_files"] = safetensor_files
results.append(model_dict)
return results
except Exception as e:
if console:
console.print(f"[red]Error searching models: {e}[/red]")
return []
def get_hf_model(
model_id: str,
token: str | None = None,
console: Console | None = None,
) -> dict[str, Any] | None:
"""Get detailed model information from Hugging Face.
Args:
model_id: Model ID (e.g., "stabilityai/stable-diffusion-xl-base-1.0")
token: HuggingFace API token
console: Rich console for output
Returns:
Model info dictionary or None if not found
"""
api = _get_api(token)
try:
model = api.model_info(model_id, files_metadata=True)
model_dict = model.__dict__.copy()
# Get safetensor files
siblings = getattr(model, "siblings", None) or []
safetensor_files = [
{"rfilename": s.rfilename, "size": getattr(s, "size", None)}
for s in siblings
if s.rfilename.endswith(".safetensors")
]
model_dict["_safetensor_files"] = safetensor_files
return model_dict
except RepositoryNotFoundError:
if console:
console.print(f"[red]Model not found: {model_id}[/red]")
return None
except Exception as e:
if console:
console.print(f"[red]Error fetching model: {e}[/red]")
return None
def list_safetensor_files(
model_id: str,
token: str | None = None,
console: Console | None = None,
) -> list[str]:
"""List all safetensor files in a Hugging Face model.
Args:
model_id: Model ID
token: HuggingFace API token
console: Rich console for output
Returns:
List of safetensor filenames
"""
try:
files = list_repo_files(model_id, token=token)
return [f for f in files if f.endswith(".safetensors")]
except RepositoryNotFoundError:
if console:
console.print(f"[red]Model not found: {model_id}[/red]")
return []
except Exception as e:
if console:
console.print(f"[red]Error listing files: {e}[/red]")
return []
def download_hf_safetensor(
model_id: str,
filename: str,
output_dir: Path,
token: str | None = None,
console: Console | None = None,
*,
resume: bool = True,
) -> Path | None:
"""Download a safetensor file from Hugging Face.
Args:
model_id: Model ID (e.g., "stabilityai/stable-diffusion-xl-base-1.0")
filename: File name within the model repo
output_dir: Directory to save the file
token: HuggingFace API token
console: Rich console for progress output
resume: Whether to resume partial downloads
Returns:
Path to downloaded file, or None on failure
"""
if not filename.endswith(".safetensors"):
if console:
console.print("[red]Only .safetensors files are supported[/red]")
return None
try:
# hf_hub_download handles caching and resume automatically
downloaded_path = hf_hub_download(
repo_id=model_id,
filename=filename,
local_dir=output_dir,
token=token,
force_download=not resume,
)
if console:
console.print(f"[green]Downloaded: {downloaded_path}[/green]")
return Path(downloaded_path)
except RepositoryNotFoundError:
if console:
console.print(f"[red]Model not found: {model_id}[/red]")
return None
except Exception as e:
if console:
console.print(f"[red]Download failed: {e}[/red]")
return None
def download_all_safetensors(
model_id: str,
output_dir: Path,
token: str | None = None,
console: Console | None = None,
) -> list[Path]:
"""Download all safetensor files from a model.
Args:
model_id: Model ID
output_dir: Directory to save files
token: HuggingFace API token
console: Rich console for output
Returns:
List of downloaded file paths
"""
files = list_safetensor_files(model_id, token, console)
if not files:
return []
downloaded = []
for filename in files:
if console:
console.print(f"[dim]Downloading {filename}...[/dim]")
path = download_hf_safetensor(model_id, filename, output_dir, token, console)
if path:
downloaded.append(path)
return downloaded
Generated
+87 -1
View File
@@ -180,6 +180,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
]
[[package]]
name = "fsspec"
version = "2026.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@@ -189,6 +198,35 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "hf-xet"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" },
{ url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" },
{ url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" },
{ url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" },
{ url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" },
{ url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" },
{ url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" },
{ url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" },
{ url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" },
{ url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" },
{ url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" },
{ url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" },
{ url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" },
{ url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" },
{ url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" },
{ url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" },
{ url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" },
{ url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" },
{ url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" },
{ url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
@@ -217,6 +255,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "huggingface-hub"
version = "1.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
{ name = "fsspec" },
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
{ name = "httpx" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "shellingham" },
{ name = "tqdm" },
{ name = "typer-slim" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c4/fc/eb9bc06130e8bbda6a616e1b80a7aa127681c448d6b49806f61db2670b61/huggingface_hub-1.4.1.tar.gz", hash = "sha256:b41131ec35e631e7383ab26d6146b8d8972abc8b6309b963b306fbcca87f5ed5", size = 642156, upload-time = "2026-02-06T09:20:03.013Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/ae/2f6d96b4e6c5478d87d606a1934b5d436c4a2bce6bb7c6fdece891c128e3/huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18", size = 553326, upload-time = "2026-02-06T09:20:00.728Z" },
]
[[package]]
name = "identify"
version = "2.6.16"
@@ -716,10 +775,11 @@ wheels = [
[[package]]
name = "tensors"
version = "0.1.16"
version = "0.1.18"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
{ name = "huggingface-hub" },
{ name = "rich" },
{ name = "safetensors" },
{ name = "typer" },
@@ -751,6 +811,7 @@ dev = [
requires-dist = [
{ name = "fastapi", marker = "extra == 'server'", specifier = ">=0.115" },
{ name = "httpx", specifier = ">=0.27.0" },
{ name = "huggingface-hub", specifier = ">=0.25.0" },
{ name = "rich", specifier = ">=13.0.0" },
{ name = "safetensors", specifier = ">=0.4.0" },
{ name = "scalar-fastapi", marker = "extra == 'server'", specifier = ">=1.6" },
@@ -774,6 +835,18 @@ dev = [
{ name = "uvicorn", specifier = ">=0.30" },
]
[[package]]
name = "tqdm"
version = "4.67.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
]
[[package]]
name = "typer"
version = "0.21.1"
@@ -789,6 +862,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
]
[[package]]
name = "typer-slim"
version = "0.21.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
{ name = "click" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/ca/0d9d822fd8a4c7e830cba36a2557b070d4b4a9558a0460377a61f8fb315d/typer_slim-0.21.2.tar.gz", hash = "sha256:78f20d793036a62aaf9c3798306142b08261d4b2a941c6e463081239f062a2f9", size = 120497, upload-time = "2026-02-10T19:33:45.836Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/03/e09325cfc40a33a82b31ba1a3f1d97e85246736856a45a43b19fcb48b1c2/typer_slim-0.21.2-py3-none-any.whl", hash = "sha256:4705082bb6c66c090f60e47c8be09a93158c139ce0aa98df7c6c47e723395e5f", size = 56790, upload-time = "2026-02-10T19:33:47.221Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"