from __future__ import annotations from enum import StrEnum class GitLabErrorKind(StrEnum): RETRYABLE = "retryable" RATE_LIMITED = "rate_limited" AUTH = "auth" FORBIDDEN = "forbidden" NOT_FOUND = "not_found" DUPLICATE = "duplicate" VALIDATION = "validation" NETWORK = "network" FATAL = "fatal" def classify_error(status_code: int | None, response_body: dict | None) -> tuple[GitLabErrorKind, str, float | None]: message = "" if isinstance(response_body, dict): message = response_body.get("message", response_body.get("error", "")) if status_code == 429: return GitLabErrorKind.RATE_LIMITED, message, 60.0 if status_code == 401: return GitLabErrorKind.AUTH, message, None if status_code == 403: if "duplicate" in message.lower(): return GitLabErrorKind.DUPLICATE, message, None return GitLabErrorKind.FORBIDDEN, message, None if status_code == 404: return GitLabErrorKind.NOT_FOUND, message, None if status_code == 400 or status_code == 422: return GitLabErrorKind.VALIDATION, message, None if status_code and status_code >= 500: return GitLabErrorKind.RETRYABLE, message, None if status_code is None: return GitLabErrorKind.NETWORK, message, None return GitLabErrorKind.FATAL, message, None def is_retryable(kind: GitLabErrorKind) -> bool: return kind in ( GitLabErrorKind.RETRYABLE, GitLabErrorKind.RATE_LIMITED, GitLabErrorKind.NETWORK, ) MAX_RETRIES = 3