Source code for globus_sdk.transport.retry

from __future__ import annotations

import enum
import typing as t

import requests

if t.TYPE_CHECKING:
    from .caller_info import RequestCallerInfo

C = t.TypeVar("C", bound=t.Callable[..., t.Any])


# alias useful for declaring retry-related types
RetryCheck = t.Callable[["RetryContext"], "RetryCheckResult"]


[docs] class RetryContext: """ The RetryContext is an object passed to retry checks in order to determine whether or not a request should be retried. The context is constructed after each request, regardless of success or failure. If an exception was raised, the context will contain that exception object. Otherwise, the context will contain a response object. Exactly one of ``response`` or ``exception`` will be present. :param attempt: The request attempt number, starting at 0. :param caller_info: Contextual information about the caller, including authorizer :param response: The response on a successful request :param exception: The error raised when trying to send the request """ def __init__( self, attempt: int, *, caller_info: RequestCallerInfo, response: requests.Response | None = None, exception: Exception | None = None, ) -> None: # retry attempt number self.attempt = attempt # caller info provides contextual information about the request self.caller_info = caller_info # the response or exception from a request # we expect exactly one of these to be non-null self.response = response self.exception = exception # the retry delay or "backoff" before retrying self.backoff: float | None = None
[docs] class RetryCheckResult(enum.Enum): #: yes, retry the request do_retry = enum.auto() #: no, do not retry the request do_not_retry = enum.auto() #: "I don't know", ask other checks for an answer no_decision = enum.auto()
[docs] class RetryCheckFlags(enum.Flag): #: no flags (default) NONE = enum.auto() #: only run this check once per request RUN_ONCE = enum.auto()
# stub for mypy class _RetryCheckFunc: _retry_check_flags: RetryCheckFlags
[docs] def set_retry_check_flags(flag: RetryCheckFlags) -> t.Callable[[C], C]: """ A decorator for setting retry check flags on a retry check function. Usage: >>> @set_retry_check_flags(RetryCheckFlags.RUN_ONCE) >>> def foo(ctx): ... :param flag: The flag to set on the check """ def decorator(func: C) -> C: as_check = t.cast(_RetryCheckFunc, func) as_check._retry_check_flags = flag return func return decorator
class RetryCheckCollection: """ A RetryCheckCollection is an ordered collection of retry checks which are used to determine whether or not a request should be retried. Checks are stored in registration order. Notably, the collection does not decide - how many times a request should retry - how or how long the call should wait between attempts (except via the backoff which may be set) - what kinds of request parameters (e.g., timeouts) are used It *only* contains ``RetryCheck`` functions which can look at a response or error and decide whether or not to retry. """ def __init__(self) -> None: self._data: list[RetryCheck] = [] def register_check(self, func: RetryCheck) -> RetryCheck: """ Register a retry check with this policy. A retry checker is a callable responsible for implementing `check(RetryContext) -> RetryCheckResult` `check` should *not* perform any sleeps or delays. Multiple checks should be chainable and callable in any order. :param func: The function or other callable to register as a retry check """ self._data.append(func) return func def register_many_checks(self, funcs: t.Iterable[RetryCheck]) -> None: """ Register all checks in a collection of checks. :param funcs: An iterable collection of retry check callables """ for f in funcs: self.register_check(f) def __iter__(self) -> t.Iterator[RetryCheck]: yield from self._data def __len__(self) -> int: return len(self._data)