Module slack_sdk.http_retry

Expand source code
from typing import List

from .handler import RetryHandler
from .builtin_handlers import (
    ConnectionErrorRetryHandler,
    RateLimitErrorRetryHandler,
)
from .interval_calculator import RetryIntervalCalculator
from .builtin_interval_calculators import (
    FixedValueRetryIntervalCalculator,
    BackoffRetryIntervalCalculator,
)
from .jitter import Jitter
from .request import HttpRequest
from .response import HttpResponse
from .state import RetryState

connect_error_retry_handler = ConnectionErrorRetryHandler()
rate_limit_error_retry_handler = RateLimitErrorRetryHandler()


def default_retry_handlers() -> List[RetryHandler]:
    return [connect_error_retry_handler]


def all_builtin_retry_handlers() -> List[RetryHandler]:
    return [
        connect_error_retry_handler,
        rate_limit_error_retry_handler,
    ]


__all__ = [
    "RetryHandler",
    "ConnectionErrorRetryHandler",
    "RateLimitErrorRetryHandler",
    "RetryIntervalCalculator",
    "FixedValueRetryIntervalCalculator",
    "BackoffRetryIntervalCalculator",
    "Jitter",
    "HttpRequest",
    "HttpResponse",
    "RetryState",
    "connect_error_retry_handler",
    "rate_limit_error_retry_handler",
    "default_retry_handlers",
    "all_builtin_retry_handlers",
]

Sub-modules

slack_sdk.http_retry.async_handler

asyncio compatible RetryHandler interface. You can pass an array of handlers to customize retry logics in supported API clients.

slack_sdk.http_retry.builtin_async_handlers
slack_sdk.http_retry.builtin_handlers
slack_sdk.http_retry.builtin_interval_calculators
slack_sdk.http_retry.handler

RetryHandler interface. You can pass an array of handlers to customize retry logics in supported API clients.

slack_sdk.http_retry.interval_calculator
slack_sdk.http_retry.jitter
slack_sdk.http_retry.request
slack_sdk.http_retry.response
slack_sdk.http_retry.state

Functions

def all_builtin_retry_handlers() ‑> List[RetryHandler]
Expand source code
def all_builtin_retry_handlers() -> List[RetryHandler]:
    return [
        connect_error_retry_handler,
        rate_limit_error_retry_handler,
    ]
def default_retry_handlers() ‑> List[RetryHandler]
Expand source code
def default_retry_handlers() -> List[RetryHandler]:
    return [connect_error_retry_handler]

Classes

class BackoffRetryIntervalCalculator (backoff_factor: float = 0.5, jitter: Optional[Jitter] = None)

Retry interval calculator that calculates in the manner of Exponential Backoff And Jitter see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

Retry interval calculator that calculates in the manner of Exponential Backoff And Jitter

Args

backoff_factor
The factor for the backoff interval calculation
jitter
The jitter logic implementation
Expand source code
class BackoffRetryIntervalCalculator(RetryIntervalCalculator):
    """Retry interval calculator that calculates in the manner of Exponential Backoff And Jitter
    see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
    """

    backoff_factor: float
    jitter: Jitter

    def __init__(self, backoff_factor: float = 0.5, jitter: Optional[Jitter] = None):
        """Retry interval calculator that calculates in the manner of Exponential Backoff And Jitter

        Args:
            backoff_factor: The factor for the backoff interval calculation
            jitter: The jitter logic implementation
        """
        self.backoff_factor = backoff_factor
        self.jitter = jitter if jitter is not None else RandomJitter()

    def calculate_sleep_duration(self, current_attempt: int) -> float:
        interval = self.backoff_factor * (2 ** (current_attempt))
        sleep_duration = self.jitter.recalculate(interval)
        return sleep_duration

Ancestors

Class variables

var backoff_factor : float
var jitterJitter

Inherited members

class ConnectionErrorRetryHandler (max_retry_count: int = 1, interval_calculator: RetryIntervalCalculator = <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>, error_types: List[Type[Exception]] = [<class 'urllib.error.URLError'>, <class 'ConnectionResetError'>, <class 'http.client.RemoteDisconnected'>])

RetryHandler that does retries for connectivity issues.

RetryHandler interface.

Args

max_retry_count
The maximum times to do retries
interval_calculator
Pass an interval calculator for customizing the logic
Expand source code
class ConnectionErrorRetryHandler(RetryHandler):
    """RetryHandler that does retries for connectivity issues."""

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
        error_types: List[Type[Exception]] = [
            # To cover URLError: <urlopen error [Errno 104] Connection reset by peer>
            URLError,
            ConnectionResetError,
            RemoteDisconnected,
        ],
    ):
        super().__init__(max_retry_count, interval_calculator)
        self.error_types_to_do_retries = error_types

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        if error is None:
            return False

        if isinstance(error, URLError):
            if response is not None:
                return False  # status 40x

        for error_type in self.error_types_to_do_retries:
            if isinstance(error, error_type):
                return True
        return False

Ancestors

Class variables

var interval_calculatorRetryIntervalCalculator
var max_retry_count : int
class FixedValueRetryIntervalCalculator (fixed_internal: float = 0.5)

Retry interval calculator that uses a fixed value.

Retry interval calculator that uses a fixed value.

Args

fixed_internal
The fixed interval seconds
Expand source code
class FixedValueRetryIntervalCalculator(RetryIntervalCalculator):
    """Retry interval calculator that uses a fixed value."""

    fixed_interval: float

    def __init__(self, fixed_internal: float = 0.5):
        """Retry interval calculator that uses a fixed value.

        Args:
            fixed_internal: The fixed interval seconds
        """
        self.fixed_interval = fixed_internal

    def calculate_sleep_duration(self, current_attempt: int) -> float:
        return self.fixed_interval

Ancestors

Class variables

var fixed_interval : float

Inherited members

class HttpRequest (*, method: str, url: str, headers: Dict[str, Union[str, List[str]]], body_params: Optional[Dict[str, Any]] = None, data: Optional[bytes] = None)

HTTP request representation

Expand source code
class HttpRequest:
    """HTTP request representation"""

    method: str
    url: str
    headers: Dict[str, Union[str, List[str]]]
    body_params: Optional[Dict[str, Any]]
    data: Optional[bytes]

    def __init__(
        self,
        *,
        method: str,
        url: str,
        headers: Dict[str, Union[str, List[str]]],
        body_params: Optional[Dict[str, Any]] = None,
        data: Optional[bytes] = None,
    ):
        self.method = method
        self.url = url
        self.headers = {k: v if isinstance(v, list) else [v] for k, v in headers.items()}
        self.body_params = body_params
        self.data = data

    @classmethod
    def from_urllib_http_request(cls, req: Request) -> "HttpRequest":
        return HttpRequest(
            method=req.method,
            url=req.full_url,
            headers={k: v if isinstance(v, list) else [v] for k, v in req.headers.items()},
            data=req.data,
        )

Class variables

var body_params : Optional[Dict[str, Any]]
var data : Optional[bytes]
var headers : Dict[str, Union[str, List[str]]]
var method : str
var url : str

Static methods

def from_urllib_http_request(req: urllib.request.Request) ‑> HttpRequest
Expand source code
@classmethod
def from_urllib_http_request(cls, req: Request) -> "HttpRequest":
    return HttpRequest(
        method=req.method,
        url=req.full_url,
        headers={k: v if isinstance(v, list) else [v] for k, v in req.headers.items()},
        data=req.data,
    )
class HttpResponse (*, status_code: Union[str, int], headers: Dict[str, Union[str, List[str]]], body: Optional[Dict[str, Any]] = None, data: Optional[bytes] = None)

HTTP response representation

Expand source code
class HttpResponse:
    """HTTP response representation"""

    status_code: int
    headers: Dict[str, List[str]]
    body: Optional[Dict[str, Any]]
    data: Optional[bytes]

    def __init__(
        self,
        *,
        status_code: Union[int, str],
        headers: Dict[str, Union[str, List[str]]],
        body: Optional[Dict[str, Any]] = None,
        data: Optional[bytes] = None,
    ):
        self.status_code = int(status_code)
        self.headers = {k: v if isinstance(v, list) else [v] for k, v in headers.items()}
        self.body = body
        self.data = data

Class variables

var body : Optional[Dict[str, Any]]
var data : Optional[bytes]
var headers : Dict[str, List[str]]
var status_code : int
class Jitter

Jitter interface

Expand source code
class Jitter:
    """Jitter interface"""

    def recalculate(self, duration: float) -> float:
        """Recalculate the given duration.
        see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

        Args:
            duration: the duration in seconds

        Returns:
            A new duration that the jitter amount is added
        """
        raise NotImplementedError()

Subclasses

Methods

def recalculate(self, duration: float) ‑> float

Recalculate the given duration. see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

Args

duration
the duration in seconds

Returns

A new duration that the jitter amount is added

Expand source code
def recalculate(self, duration: float) -> float:
    """Recalculate the given duration.
    see also: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

    Args:
        duration: the duration in seconds

    Returns:
        A new duration that the jitter amount is added
    """
    raise NotImplementedError()
class RateLimitErrorRetryHandler (max_retry_count: int = 1, interval_calculator: RetryIntervalCalculator = <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>)

RetryHandler that does retries for rate limited errors.

RetryHandler interface.

Args

max_retry_count
The maximum times to do retries
interval_calculator
Pass an interval calculator for customizing the logic
Expand source code
class RateLimitErrorRetryHandler(RetryHandler):
    """RetryHandler that does retries for rate limited errors."""

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        return response is not None and response.status_code == 429

    def prepare_for_next_attempt(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> None:
        if response is None:
            raise error

        state.next_attempt_requested = True
        retry_after_header_name: Optional[str] = None
        for k in response.headers.keys():
            if k.lower() == "retry-after":
                retry_after_header_name = k
                break
        duration = 1
        if retry_after_header_name is None:
            # This situation usually does not arise. Just in case.
            duration += random.random()
        else:
            duration = int(response.headers.get(retry_after_header_name)[0]) + random.random()
        time.sleep(duration)
        state.increment_current_attempt()

Ancestors

Class variables

var interval_calculatorRetryIntervalCalculator
var max_retry_count : int

Methods

def prepare_for_next_attempt(self, *, state: RetryState, request: HttpRequest, response: Optional[HttpResponse] = None, error: Optional[Exception] = None) ‑> None
Expand source code
def prepare_for_next_attempt(
    self,
    *,
    state: RetryState,
    request: HttpRequest,
    response: Optional[HttpResponse] = None,
    error: Optional[Exception] = None,
) -> None:
    if response is None:
        raise error

    state.next_attempt_requested = True
    retry_after_header_name: Optional[str] = None
    for k in response.headers.keys():
        if k.lower() == "retry-after":
            retry_after_header_name = k
            break
    duration = 1
    if retry_after_header_name is None:
        # This situation usually does not arise. Just in case.
        duration += random.random()
    else:
        duration = int(response.headers.get(retry_after_header_name)[0]) + random.random()
    time.sleep(duration)
    state.increment_current_attempt()
class RetryHandler (max_retry_count: int = 1, interval_calculator: RetryIntervalCalculator = <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>)

RetryHandler interface. You can pass an array of handlers to customize retry logics in supported API clients.

RetryHandler interface.

Args

max_retry_count
The maximum times to do retries
interval_calculator
Pass an interval calculator for customizing the logic
Expand source code
class RetryHandler:
    """RetryHandler interface.
    You can pass an array of handlers to customize retry logics in supported API clients.
    """

    max_retry_count: int
    interval_calculator: RetryIntervalCalculator

    def __init__(
        self,
        max_retry_count: int = 1,
        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
    ):
        """RetryHandler interface.

        Args:
            max_retry_count: The maximum times to do retries
            interval_calculator: Pass an interval calculator for customizing the logic
        """
        self.max_retry_count = max_retry_count
        self.interval_calculator = interval_calculator

    def can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        if state.current_attempt >= self.max_retry_count:
            return False
        return self._can_retry(
            state=state,
            request=request,
            response=response,
            error=error,
        )

    def _can_retry(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> bool:
        raise NotImplementedError()

    def prepare_for_next_attempt(
        self,
        *,
        state: RetryState,
        request: HttpRequest,
        response: Optional[HttpResponse] = None,
        error: Optional[Exception] = None,
    ) -> None:
        state.next_attempt_requested = True
        duration = self.interval_calculator.calculate_sleep_duration(state.current_attempt)
        time.sleep(duration)
        state.increment_current_attempt()

Subclasses

Class variables

var interval_calculatorRetryIntervalCalculator
var max_retry_count : int

Methods

def can_retry(self, *, state: RetryState, request: HttpRequest, response: Optional[HttpResponse] = None, error: Optional[Exception] = None) ‑> bool
Expand source code
def can_retry(
    self,
    *,
    state: RetryState,
    request: HttpRequest,
    response: Optional[HttpResponse] = None,
    error: Optional[Exception] = None,
) -> bool:
    if state.current_attempt >= self.max_retry_count:
        return False
    return self._can_retry(
        state=state,
        request=request,
        response=response,
        error=error,
    )
def prepare_for_next_attempt(self, *, state: RetryState, request: HttpRequest, response: Optional[HttpResponse] = None, error: Optional[Exception] = None) ‑> None
Expand source code
def prepare_for_next_attempt(
    self,
    *,
    state: RetryState,
    request: HttpRequest,
    response: Optional[HttpResponse] = None,
    error: Optional[Exception] = None,
) -> None:
    state.next_attempt_requested = True
    duration = self.interval_calculator.calculate_sleep_duration(state.current_attempt)
    time.sleep(duration)
    state.increment_current_attempt()
class RetryIntervalCalculator

Retry interval calculator interface.

Expand source code
class RetryIntervalCalculator:
    """Retry interval calculator interface."""

    def calculate_sleep_duration(self, current_attempt: int) -> float:
        """Calculates an interval duration in seconds.

        Args:
            current_attempt: the number of the current attempt (zero-origin; 0 means no retries are done so far)
        Returns:
            calculated interval duration in seconds
        """
        raise NotImplementedError()

Subclasses

Methods

def calculate_sleep_duration(self, current_attempt: int) ‑> float

Calculates an interval duration in seconds.

Args

current_attempt
the number of the current attempt (zero-origin; 0 means no retries are done so far)

Returns

calculated interval duration in seconds

Expand source code
def calculate_sleep_duration(self, current_attempt: int) -> float:
    """Calculates an interval duration in seconds.

    Args:
        current_attempt: the number of the current attempt (zero-origin; 0 means no retries are done so far)
    Returns:
        calculated interval duration in seconds
    """
    raise NotImplementedError()
class RetryState (*, current_attempt: int = 0, custom_values: Optional[Dict[str, Any]] = None)
Expand source code
class RetryState:
    next_attempt_requested: bool
    current_attempt: int  # zero-origin
    custom_values: Optional[Dict[str, Any]]

    def __init__(
        self,
        *,
        current_attempt: int = 0,
        custom_values: Optional[Dict[str, Any]] = None,
    ):
        self.next_attempt_requested = False
        self.current_attempt = current_attempt
        self.custom_values = custom_values

    def increment_current_attempt(self) -> int:
        self.current_attempt += 1
        return self.current_attempt

Class variables

var current_attempt : int
var custom_values : Optional[Dict[str, Any]]
var next_attempt_requested : bool

Methods

def increment_current_attempt(self) ‑> int
Expand source code
def increment_current_attempt(self) -> int:
    self.current_attempt += 1
    return self.current_attempt