Source code for google_drive_ocr.errors

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HTTP Errors
===========

List of HTTP errors that can be fixed in most cases by trying again.

Provides a :code:`@retry` decorator, which applies exponential backoff
to a function.
"""

import math
import time
import random
import logging
import functools
from typing import Any, Callable

from googleapiclient.errors import HttpError

###############################################################################

logger = logging.getLogger(__name__)

###############################################################################
# https://developers.google.com/drive/api/v3/handle-errors

RETRY_ERRORS = {
    # 400: ["Bad request", "Invalid sharing request"],
    # 401: ["Invalid credentials"],
    403: ["Usage limit exceeded", "Daily limit exceeded",
          "Number of items in folder", "User rate limit exceeded",
          "Rate limit exceeded", "Sharing rate limit exceeded",
          "The user has not granted the app access to the file",
          "The user does not have sufficient permissions for the file",
          "App cannot be used within the authenticated user's domain"],
    404: ["File not found"],
    429: ["Too many requests"],
    500: ["Backend error"],
    502: ["Bad Gateway"],
    503: ["Service Unavailable"],
    504: ["Gateway Timeout"]
}

###############################################################################


[docs]def retry( attempts: int = 4, delay: int = 1, backoff: int = 2, hook: Callable[[int, Exception, int], Any] = None ) -> Callable: """ Decorator to Retry with Exponential Backoff (on Exception) A function that raises an exception on failure, when decorated with this decorator, will retry till it returns True or number of attempts runs out. The decorator will call the function up to :code:`attempts` times if it raises an exception. By default it catches instances of the Exception class and subclasses. This will recover after all but the most fatal errors. You may specify a custom tuple of exception classes with the :code:`exceptions` argument; the function will only be retried if it raises one of the specified exceptions. Additionally you may specify a hook function which will be called prior to retrying with the number of remaining tries and the exception instance; This is primarily intended to give the opportunity to log the failure. Hook is not called after failure if no retries remain. Parameters ---------- attempts : int, optional Number of attempts in case of failure. The default is 4. delay : int, optional Intinitial delay in seconds The default is 1. backoff : int, optional Backoff multiplication factor The default is 2. hook : Callable[[int, Exception, int], Any], optional Function with the parameters `(tries_remaining, exception, delay)` The default is None. Returns ------- Callable Decorator function Raises ------ ValueError If the :code:`backoff` multiplication factor is less than 1. ValueError If the number of :code:`attempts` is less than 0. ValueError If the initial :code:`delay` is less than or equal to 0. """ if backoff <= 1: raise ValueError("Backoff must be greater than 1") attempts = math.floor(attempts) if attempts < 0: raise ValueError("Attempts must be 0 or greater") if delay <= 0: raise ValueError("Delay must be greater than 0") def decorator(func): # ------------------------------------------------------------------- # @functools.wraps(func) def wrapper(*args, **kwargs): _delay = delay for tries_remaining in range(attempts, -1, -1): try: return func(*args, **kwargs) except HttpError as error: if error.resp.status in RETRY_ERRORS: if tries_remaining > 0: if hook is not None: hook(tries_remaining, error, _delay) logger.warning( f"{error.resp.status}: {error.resp.reason}" ) logger.info(f"Retrying in {_delay} seconds ..") time.sleep(_delay + random.random()) _delay *= backoff else: logger.error("Failed number of attempts exceeded.") else: logger.error( f"{error.resp.status}: {error.resp.reason}" ) raise else: break # ------------------------------------------------------------------- # return wrapper return decorator
###############################################################################