Three items from the latest review round. A scoped download job carries a deliberate file subset, and every file set of one repo rides the same "@scope" slot. A client that adopts a live job from the backend had no file list to compare against: the active-downloads response never carried one, so an adopted job's set was unknown and any later scoped request for the same repo read as "already started". Selecting a different checkpoint then waited on the wrong transfer and tried to load a file nobody fetched. The response now publishes the scoped file list, adoption records it, and an unknown set no longer satisfies a scoped request. A gallery record can be deleted while its blob is still downloading. The delete revokes the URL present at that moment, so the fetch that lands afterwards inserted a fresh object URL for a record no card renders and nothing can revoke: a full MP4, tens to hundreds of MB, pinned for the rest of the session, and once per raced fetch. Both galleries now discard a blob whose record went away, with an epoch covering the video page's Clear all. The video backend keeps the last completed job until the next one starts, and the Video page merges that record on mount to cover a job that finished after the gallery fetch. Deleting the clip left the record in place, so every reload prepended a ghost card whose file request 404s until another generation replaced it. Deleting the clip, or clearing the gallery, now clears the matching terminal record, and the page skips a record it deleted itself.
180 lines
5.5 KiB
Python
180 lines
5.5 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""Pydantic schemas for the Hub download manager (/api/hub/downloads/*)."""
|
|
|
|
from pydantic import BaseModel, Field
|
|
from typing import List, Literal, Optional
|
|
|
|
|
|
DownloadJobState = Literal["idle", "running", "cancelling", "cancelled", "complete", "error"]
|
|
|
|
|
|
class DownloadModelRequest(BaseModel):
|
|
"""Body for POST /api/hub/download.
|
|
|
|
The HuggingFace token travels in the internal Hub token header.
|
|
"""
|
|
|
|
repo_id: str = Field(
|
|
...,
|
|
description = "HuggingFace repo ID, e.g. 'unsloth/Qwen3-4B-GGUF'",
|
|
)
|
|
gguf_variant: Optional[str] = Field(
|
|
None,
|
|
description = "Quantization label (e.g. 'Q4_K_M'). Required for GGUF repos.",
|
|
)
|
|
use_xet: bool = Field(
|
|
True,
|
|
description = "Use Xet parallel chunked transport. Default True; set False for HTTP Range-resume.",
|
|
)
|
|
scope_id: Optional[str] = Field(
|
|
None,
|
|
description = "Marks a partial-by-design download of `files` only (e.g. 'diffusion', "
|
|
"whose loader reads a scoped subset of a repo). Keyed separately from the full "
|
|
"snapshot of the same repo, so neither one's manifest describes the other.",
|
|
)
|
|
files: List[str] = Field(
|
|
default_factory = list,
|
|
description = "Exact files to fetch. Required with scope_id, ignored without it.",
|
|
)
|
|
|
|
|
|
class CancelDownloadRequest(BaseModel):
|
|
repo_id: str = Field(..., description = "HuggingFace repo ID")
|
|
gguf_variant: Optional[str] = Field(
|
|
None,
|
|
description = "GGUF variant label; omit for safetensors snapshots",
|
|
)
|
|
generation: Optional[int] = Field(
|
|
None,
|
|
description = "Download generation tag from a prior start; passing it scopes the cancel to that exact run.",
|
|
)
|
|
|
|
|
|
class DownloadJobStatus(BaseModel):
|
|
"""Live state of a background download job."""
|
|
|
|
state: DownloadJobState = Field(
|
|
...,
|
|
description = "Current download job state.",
|
|
)
|
|
error: Optional[str] = Field(None, description = "Error message if state == 'error'")
|
|
generation: int = Field(
|
|
0,
|
|
description = "Current run generation; an adopting client stores it so a later cancel is scoped to this exact run.",
|
|
)
|
|
|
|
|
|
class DownloadStartResponse(BaseModel):
|
|
job_key: str
|
|
state: str
|
|
accepted: bool
|
|
generation: int
|
|
|
|
|
|
class CancelDownloadResponse(BaseModel):
|
|
job_key: str
|
|
state: str
|
|
|
|
|
|
class ActiveDownload(BaseModel):
|
|
"""One in-flight download for a repo. ``variant`` is null for safetensors."""
|
|
|
|
repo_id: Optional[str] = None
|
|
variant: Optional[str] = None
|
|
transport: Optional[str] = None
|
|
state: str
|
|
files: Optional[List[str]] = Field(
|
|
None,
|
|
description = (
|
|
"For a SCOPED job (variant '@name'), the exact file list it is fetching; null for a "
|
|
"full-snapshot or variant download. Every file set of one repo shares the scope slot, "
|
|
"so an adopting client needs this to tell whether a live job is its own transfer or a "
|
|
"sibling checkpoint's."
|
|
),
|
|
)
|
|
generation: int = Field(
|
|
0,
|
|
description = "Current run generation; an adopting client stores it so a later cancel is scoped to this exact run.",
|
|
)
|
|
|
|
|
|
class ActiveDownloadsResponse(BaseModel):
|
|
downloads: List[ActiveDownload]
|
|
|
|
|
|
class TransportCapability(BaseModel):
|
|
available: bool
|
|
reason: Optional[str] = None
|
|
|
|
|
|
class TransportCapabilities(BaseModel):
|
|
http: TransportCapability
|
|
xet: TransportCapability
|
|
|
|
|
|
class TransportStatusResponse(BaseModel):
|
|
has_partial: bool
|
|
last_transport: Optional[str] = None
|
|
resumable: bool
|
|
|
|
|
|
class DownloadProgressResponse(BaseModel):
|
|
downloaded_bytes: int
|
|
# Finalized-blob bytes only (no ``.incomplete``). Registry-loss completion
|
|
# fallbacks key off this so a partial isn't mistaken for a finished download.
|
|
completed_bytes: int = 0
|
|
complete_on_disk: bool = Field(
|
|
False,
|
|
description = (
|
|
"True only when the backend verified a usable completed snapshot/variant on disk."
|
|
),
|
|
)
|
|
expected_bytes: int
|
|
progress: float
|
|
cache_path: Optional[str] = None
|
|
|
|
|
|
class DownloadDatasetRequest(BaseModel):
|
|
"""Body for POST /api/hub/datasets/download.
|
|
|
|
The HuggingFace token travels in the internal Hub token header.
|
|
"""
|
|
|
|
repo_id: str = Field(..., description = "HuggingFace dataset repo ID")
|
|
use_xet: bool = Field(
|
|
True,
|
|
description = "Use Xet parallel chunked transport. Default True; set False for HTTP Range-resume.",
|
|
)
|
|
|
|
|
|
class CancelDatasetDownloadRequest(BaseModel):
|
|
repo_id: str = Field(..., description = "HuggingFace dataset repo ID")
|
|
generation: Optional[int] = Field(None, description = "Download generation")
|
|
|
|
|
|
class DatasetDownloadJobStatus(BaseModel):
|
|
"""Live state of a background dataset download job."""
|
|
|
|
state: DownloadJobState = Field(
|
|
...,
|
|
description = "Current dataset download job state.",
|
|
)
|
|
error: Optional[str] = Field(None, description = "Error message if state == 'error'")
|
|
generation: int = Field(
|
|
0,
|
|
description = "Current run generation; an adopting client stores it so a later cancel is scoped to this exact run.",
|
|
)
|
|
|
|
|
|
class DatasetDownloadStartResponse(BaseModel):
|
|
repo_id: str
|
|
state: str
|
|
accepted: bool
|
|
generation: int
|
|
|
|
|
|
class CancelDatasetDownloadResponse(BaseModel):
|
|
repo_id: str
|
|
state: str
|