"""Exception hierarchy. The queue decides what to do with a failed item purely from the exception type: * ``TransientError`` -> RETRY_WAIT with exponential backoff, bounded by MAX_ATTEMPTS. * ``PermanentError`` -> FAILED immediately (can still be retried manually with /retry). * ``QueueHaltError`` -> the item is parked for retry without consuming an attempt and the whole queue halts (disk full, Drive quota, expired auth). * ``SkipItem`` -> SKIPPED with a recorded reason (configured duplicate policy). * ``Interrupted`` -> /pause or /stop reached a resumable checkpoint. """ from __future__ import annotations class BotError(Exception): """Base error. ``public`` is a short summary that is safe to show in Telegram.""" def __init__(self, message: str, *, public: str | None = None): super().__init__(message) self.public = public or message class TransientError(BotError): def __init__( self, message: str, *, retry_after: float | None = None, rate_limited: bool = False, public: str | None = None, ): super().__init__(message, public=public) self.retry_after = retry_after # Rate-limit waits do not consume one of the item's attempts. self.rate_limited = rate_limited class UploadSessionExpired(TransientError): """The saved resumable-upload session URI is no longer valid.""" class PermanentError(BotError): """A failure that will not fix itself by waiting.""" class QueueHaltError(BotError): """A failure that would affect every following item as well.""" class DiskSpaceError(QueueHaltError): pass class DriveQuotaError(QueueHaltError): pass class AuthError(QueueHaltError): pass class SkipItem(Exception): def __init__(self, reason: str): super().__init__(reason) self.reason = reason class Interrupted(Exception): """Raised at a resumable checkpoint after /pause or /stop."""