feat(downloads): return structured 429 rate-limit responses with retry_after

On a CivitAI/CivArchive 429, download-model and download-model-get now
return HTTP 429 with {"reason": "rate_limited", "retry_after": N}
instead of a generic 500 string, and the queue row goes back to
"queued" rather than history as failed — so queue drivers can
auto-pause and retry later instead of burning through the queue.

- new DownloadRateLimitError carrying retry_after/host (opt-in via
  raise_on_rate_limit on Downloader; other call sites keep the legacy
  string behavior)
- fail-fast pre-flight gate in DownloadManager consults
  RateLimitCoordinator before acquiring the semaphore slot: hosts in
  cooldown get an immediate structured 429, no HTTP request attempted
- best-effort 429 detection for the aria2 backend
This commit is contained in:
Will Miao
2026-10-02 21:16:06 +08:00
parent 2a667df98c
commit 034660d8c4
8 changed files with 627 additions and 9 deletions
+19
View File
@@ -20,6 +20,25 @@ class RateLimitError(RuntimeError):
self.provider = provider
class DownloadRateLimitError(RateLimitError):
"""Raised when a file download is rejected with HTTP 429.
Carries the vendor's ``Retry-After`` hint (when present) and the target
host so the download manager can build the structured rate-limit result
the download queue contract expects.
"""
def __init__(
self,
message: str,
*,
retry_after: Optional[float] = None,
host: Optional[str] = None,
) -> None:
super().__init__(message, retry_after=retry_after)
self.host = host
class ResourceNotFoundError(RuntimeError):
"""Raised when a remote resource is permanently missing."""