Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies = [
"httpx",
"pydantic",
"requests",
"types-requests",
"tenacity"
]

Expand All @@ -51,6 +52,7 @@ httpx = [
]
requests = [
"requests",
"types-requests",
"tenacity",
]
dev = [
Expand All @@ -59,6 +61,7 @@ dev = [
"respx",
"ruff",
"nox",
"types-requests",
]
docs = [
"mkdocs",
Expand Down
32 changes: 29 additions & 3 deletions retryhttp/_retry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional, Sequence, Tuple, Type, Union
from typing import Any, Callable, Optional, Sequence, Tuple, Type, Union, overload

from tenacity import (
RetryCallState,
Expand All @@ -24,6 +24,32 @@
from ._wait import wait_context_aware, wait_retry_after


@overload
def retry(func: F) -> F: ...


@overload
def retry(
func: None = None,
*,
max_attempt_number: int = 3,
retry_server_errors: bool = True,
retry_network_errors: bool = True,
retry_timeouts: bool = True,
retry_rate_limited: bool = True,
wait_server_errors: wait_base = wait_random_exponential(),
wait_network_errors: wait_base = wait_exponential(),
wait_timeouts: wait_base = wait_random_exponential(),
wait_rate_limited: wait_base = wait_retry_after(),
server_error_codes: Union[Sequence[int], int] = (500, 502, 503, 504),
network_errors: Union[
Type[BaseException], Tuple[Type[BaseException], ...], None
] = None,
timeouts: Union[Type[BaseException], Tuple[Type[BaseException], ...], None] = None,
**kwargs: Any,
) -> Callable[[F], F]: ...


def retry(
func: Optional[F] = None,
*,
Expand All @@ -42,7 +68,7 @@ def retry(
] = None,
timeouts: Union[Type[BaseException], Tuple[Type[BaseException], ...], None] = None,
**kwargs: Any,
) -> F:
) -> Union[F, Callable[[F], F]]:
"""Retry potentially transient HTTP errors with sensible default behavior.

By default, retries the following errors, for a total of 3 attempts, with
Expand Down Expand Up @@ -105,7 +131,7 @@ def retry(
if timeouts is None:
timeouts = get_default_timeouts()

retry_strategies = []
retry_strategies: list[retry_base] = []
if retry_server_errors:
retry_strategies.append(
retry_if_server_error(server_error_codes=server_error_codes)
Expand Down
30 changes: 15 additions & 15 deletions retryhttp/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
pass


def get_default_network_errors() -> Tuple[
Union[Type[httpx.NetworkError], Type[requests.ConnectionError]], ...
]:
def get_default_network_errors() -> Tuple[Type[BaseException], ...]:
"""Get all network errors to use by default.

Args:
Expand All @@ -37,7 +35,7 @@ def get_default_network_errors() -> Tuple[
N/A

"""
exceptions = []
exceptions: list[type[BaseException]] = []
if _HTTPX_INSTALLED:
exceptions.extend(
[
Expand All @@ -56,41 +54,37 @@ def get_default_network_errors() -> Tuple[
return tuple(exceptions)


def get_default_timeouts() -> Tuple[
Type[Union[httpx.TimeoutException, requests.Timeout]], ...
]:
def get_default_timeouts() -> Tuple[Type[BaseException], ...]:
"""Get all timeout exceptions to use by default.

Returns:
tuple: Timeout exceptions.

"""
exceptions = []
exceptions: list[Type[BaseException]] = []
if _HTTPX_INSTALLED:
exceptions.append(httpx.TimeoutException)
if _REQUESTS_INSTALLED:
exceptions.append(requests.Timeout)
return tuple(exceptions)


def get_default_http_status_exceptions() -> Tuple[
Union[Type[httpx.HTTPStatusError], Type[requests.HTTPError]], ...
]:
def get_default_http_status_exceptions() -> Tuple[Type[BaseException], ...]:
"""Get default HTTP status 4xx or 5xx exceptions.

Returns:
tuple: HTTP status exceptions.

"""
exceptions = []
exceptions: list[Type[BaseException]] = []
if _HTTPX_INSTALLED:
exceptions.append(httpx.HTTPStatusError)
if _REQUESTS_INSTALLED:
exceptions.append(requests.HTTPError)
return tuple(exceptions)


def is_rate_limited(exc: Union[BaseException, None]) -> bool:
def is_rate_limited(exc: Optional[BaseException]) -> bool:
"""Whether a given exception indicates the user has been rate limited.

Args:
Expand All @@ -100,7 +94,10 @@ def is_rate_limited(exc: Union[BaseException, None]) -> bool:
bool: Whether exc indicates rate limiting.

"""
if isinstance(exc, get_default_http_status_exceptions()):
if exc is None:
return False
exceptions = get_default_http_status_exceptions()
if isinstance(exc, exceptions) and hasattr(exc, "response"):
return exc.response.status_code == 429
return False

Expand All @@ -120,9 +117,12 @@ def is_server_error(
bool: whether exc indicates an error included in status_codes.

"""
if exc is None:
return False
if isinstance(status_codes, int):
status_codes = [status_codes]
if isinstance(exc, get_default_http_status_exceptions()):
exceptions = get_default_http_status_exceptions()
if isinstance(exc, exceptions) and hasattr(exc, "response"):
return exc.response.status_code in status_codes
return False

Expand Down
6 changes: 5 additions & 1 deletion retryhttp/_wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ def _get_wait_value(self, retry_state: RetryCallState) -> float:
"""
if retry_state.outcome:
exc = retry_state.outcome.exception()
if isinstance(exc, get_default_http_status_exceptions()):
if exc is None:
return 0
if isinstance(exc, get_default_http_status_exceptions()) and hasattr(
exc, "response"
):
value = exc.response.headers.get(self.header)
if value is None:
raise ValueError(f"Header not present: {self.header}")
Expand Down