Skip to content

Commit

Permalink
Merge branch 'main' into wait-random-exponential-min
Browse files Browse the repository at this point in the history
  • Loading branch information
yxtay authored Feb 6, 2024
2 parents befa463 + 24b4a5c commit 09aefef
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 116 deletions.
8 changes: 3 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@ jobs:
tox: py310
- python: "3.11"
tox: py311
- python: "3.11"
- python: "3.12"
tox: py312
- python: "3.12"
tox: pep8
- python: "3.11"
tox: black-ci
- python: "3.11"
tox: mypy
- python: "3.12"
tox: py312
steps:
- name: Checkout 🛎️
uses: actions/[email protected]
Expand Down
4 changes: 1 addition & 3 deletions .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ queue_rules:
- "check-success=test (3.10, py310)"
- "check-success=test (3.11, py311)"
- "check-success=test (3.12, py312)"
- "check-success=test (3.11, black-ci)"
- "check-success=test (3.11, pep8)"
- "check-success=test (3.11, mypy)"
- "check-success=test (3.12, pep8)"

pull_request_rules:
- name: warn on no changelog
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ requires = [
]
build-backend="setuptools.build_meta"

[tool.black]
line-length = 120
safe = true
target-version = ["py38", "py39", "py310", "py311", "py312"]
[tool.ruff]
line-length = 88
indent-width = 4
target-version = "py38"

[tool.mypy]
strict = true
Expand Down
35 changes: 27 additions & 8 deletions tenacity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ class BaseAction:
NAME: t.Optional[str] = None

def __repr__(self) -> str:
state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS)
state_str = ", ".join(
f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
)
return f"{self.__class__.__name__}({state_str})"

def __str__(self) -> str:
Expand Down Expand Up @@ -221,10 +223,14 @@ def copy(
retry: t.Union[retry_base, object] = _unset,
before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset,
before_sleep: t.Union[
t.Optional[t.Callable[["RetryCallState"], None]], object
] = _unset,
reraise: t.Union[bool, object] = _unset,
retry_error_cls: t.Union[t.Type[RetryError], object] = _unset,
retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset,
retry_error_callback: t.Union[
t.Optional[t.Callable[["RetryCallState"], t.Any]], object
] = _unset,
) -> "BaseRetrying":
"""Copy this object with some parameters changed if needed."""
return self.__class__(
Expand All @@ -237,7 +243,9 @@ def copy(
before_sleep=_first_set(before_sleep, self.before_sleep),
reraise=_first_set(reraise, self.reraise),
retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),
retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback),
retry_error_callback=_first_set(
retry_error_callback, self.retry_error_callback
),
)

def __repr__(self) -> str:
Expand Down Expand Up @@ -285,7 +293,9 @@ def wraps(self, f: WrappedFn) -> WrappedFn:
:param f: A function to wraps for retrying.
"""

@functools.wraps(f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__"))
@functools.wraps(
f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
)
def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
return self(f, *args, **kw)

Expand Down Expand Up @@ -414,7 +424,9 @@ def failed(self) -> bool:
return self.exception() is not None

@classmethod
def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future":
def construct(
cls, attempt_number: int, value: t.Any, has_exception: bool
) -> "Future":
"""Construct a new Future object."""
fut = cls(attempt_number)
if has_exception:
Expand Down Expand Up @@ -477,7 +489,10 @@ def set_result(self, val: t.Any) -> None:
self.outcome, self.outcome_timestamp = fut, ts

def set_exception(
self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType| None"]
self,
exc_info: t.Tuple[
t.Type[BaseException], BaseException, "types.TracebackType| None"
],
) -> None:
ts = time.monotonic()
fut = Future(self.attempt_number)
Expand Down Expand Up @@ -539,7 +554,11 @@ def wrap(f: WrappedFn) -> WrappedFn:
r: "BaseRetrying"
if iscoroutinefunction(f):
r = AsyncRetrying(*dargs, **dkw)
elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f):
elif (
tornado
and hasattr(tornado.gen, "is_coroutine_function")
and tornado.gen.is_coroutine_function(f)
):
r = TornadoRetrying(*dargs, **dkw)
else:
r = Retrying(*dargs, **dkw)
Expand Down
8 changes: 6 additions & 2 deletions tenacity/_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
class AsyncRetrying(BaseRetrying):
sleep: t.Callable[[float], t.Awaitable[t.Any]]

def __init__(self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any) -> None:
def __init__(
self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any
) -> None:
super().__init__(**kwargs)
self.sleep = sleep

Expand Down Expand Up @@ -83,7 +85,9 @@ def wraps(self, fn: WrappedFn) -> WrappedFn:
fn = super().wraps(fn)
# Ensure wrapper is recognized as a coroutine function.

@functools.wraps(fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__"))
@functools.wraps(
fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
)
async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
return await fn(*args, **kwargs)

Expand Down
4 changes: 3 additions & 1 deletion tenacity/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,6 @@ def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:


def to_seconds(time_unit: time_unit_type) -> float:
return float(time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit)
return float(
time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit
)
4 changes: 3 additions & 1 deletion tenacity/before.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def before_nothing(retry_state: "RetryCallState") -> None:
"""Before call strategy that does nothing."""


def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["RetryCallState"], None]:
def before_log(
logger: "logging.Logger", log_level: int
) -> typing.Callable[["RetryCallState"], None]:
"""Before call strategy that logs to some logger the attempt."""

def log_it(retry_state: "RetryCallState") -> None:
Expand Down
3 changes: 2 additions & 1 deletion tenacity/before_sleep.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ def log_it(retry_state: "RetryCallState") -> None:

logger.log(
log_level,
f"Retrying {fn_name} " f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
f"Retrying {fn_name} "
f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
exc_info=local_exc_info,
)

Expand Down
8 changes: 6 additions & 2 deletions tenacity/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ def __init__(
match: typing.Optional[str] = None,
) -> None:
if message and match:
raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both")
raise TypeError(
f"{self.__class__.__name__}() takes either 'message' or 'match', not both"
)

# set predicate
if message:
Expand All @@ -221,7 +223,9 @@ def match_fnc(exception: BaseException) -> bool:

predicate = match_fnc
else:
raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'")
raise TypeError(
f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'"
)

super().__init__(predicate)

Expand Down
5 changes: 4 additions & 1 deletion tenacity/stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,7 @@ def __init__(self, max_delay: _utils.time_unit_type) -> None:
def __call__(self, retry_state: "RetryCallState") -> bool:
if retry_state.seconds_since_start is None:
raise RuntimeError("__call__() called but seconds_since_start is not set")
return retry_state.seconds_since_start + retry_state.upcoming_sleep >= self.max_delay
return (
retry_state.seconds_since_start + retry_state.upcoming_sleep
>= self.max_delay
)
6 changes: 5 additions & 1 deletion tenacity/tornadoweb.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@


class TornadoRetrying(BaseRetrying):
def __init__(self, sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, **kwargs: typing.Any) -> None:
def __init__(
self,
sleep: "typing.Callable[[float], Future[None]]" = gen.sleep,
**kwargs: typing.Any,
) -> None:
super().__init__(**kwargs)
self.sleep = sleep

Expand Down
12 changes: 9 additions & 3 deletions tenacity/wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_bas
return self.__add__(other)


WaitBaseT = typing.Union[wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]]
WaitBaseT = typing.Union[
wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]
]


class wait_fixed(wait_base):
Expand All @@ -64,12 +66,16 @@ def __init__(self) -> None:
class wait_random(wait_base):
"""Wait strategy that waits a random amount of time between min/max."""

def __init__(self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1) -> None: # noqa
def __init__(
self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1
) -> None: # noqa
self.wait_random_min = _utils.to_seconds(min)
self.wait_random_max = _utils.to_seconds(max)

def __call__(self, retry_state: "RetryCallState") -> float:
return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min))
return self.wait_random_min + (
random.random() * (self.wait_random_max - self.wait_random_min)
)


class wait_combine(wait_base):
Expand Down
34 changes: 28 additions & 6 deletions tests/test_after.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@

class TestAfterLogFormat(unittest.TestCase):
def setUp(self) -> None:
self.log_level = random.choice((logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL))
self.log_level = random.choice(
(
logging.DEBUG,
logging.INFO,
logging.WARNING,
logging.ERROR,
logging.CRITICAL,
)
)
self.previous_attempt_number = random.randint(1, 512)

def test_01_default(self):
Expand All @@ -22,10 +30,18 @@ def test_01_default(self):
sec_format = "%0.3f"
delay_since_first_attempt = 0.1

retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, delay_since_first_attempt)
fun = after_log(logger=logger, log_level=self.log_level) # use default sec_format
retry_state = test_tenacity.make_retry_state(
self.previous_attempt_number, delay_since_first_attempt
)
fun = after_log(
logger=logger, log_level=self.log_level
) # use default sec_format
fun(retry_state)
fn_name = "<unknown>" if retry_state.fn is None else _utils.get_callback_name(retry_state.fn)
fn_name = (
"<unknown>"
if retry_state.fn is None
else _utils.get_callback_name(retry_state.fn)
)
log.assert_called_once_with(
self.log_level,
f"Finished call to '{fn_name}' "
Expand All @@ -41,10 +57,16 @@ def test_02_custom_sec_format(self):
sec_format = "%.1f"
delay_since_first_attempt = 0.1

retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, delay_since_first_attempt)
retry_state = test_tenacity.make_retry_state(
self.previous_attempt_number, delay_since_first_attempt
)
fun = after_log(logger=logger, log_level=self.log_level, sec_format=sec_format)
fun(retry_state)
fn_name = "<unknown>" if retry_state.fn is None else _utils.get_callback_name(retry_state.fn)
fn_name = (
"<unknown>"
if retry_state.fn is None
else _utils.get_callback_name(retry_state.fn)
)
log.assert_called_once_with(
self.log_level,
f"Finished call to '{fn_name}' "
Expand Down
21 changes: 16 additions & 5 deletions tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,19 @@ async def function_with_defaults(a=1):
async def function_with_kwdefaults(*, a=1):
return a

retrying = AsyncRetrying(wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3))
retrying = AsyncRetrying(
wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3)
)
wrapped_defaults_function = retrying.wraps(function_with_defaults)
wrapped_kwdefaults_function = retrying.wraps(function_with_kwdefaults)

self.assertEqual(function_with_defaults.__defaults__, wrapped_defaults_function.__defaults__)
self.assertEqual(function_with_kwdefaults.__kwdefaults__, wrapped_kwdefaults_function.__kwdefaults__)
self.assertEqual(
function_with_defaults.__defaults__, wrapped_defaults_function.__defaults__
)
self.assertEqual(
function_with_kwdefaults.__kwdefaults__,
wrapped_kwdefaults_function.__kwdefaults__,
)

@asynctest
async def test_attempt_number_is_correct_for_interleaved_coroutines(self):
Expand Down Expand Up @@ -152,7 +159,9 @@ class CustomError(Exception):
pass

try:
async for attempt in tasyncio.AsyncRetrying(stop=stop_after_attempt(1), reraise=True):
async for attempt in tasyncio.AsyncRetrying(
stop=stop_after_attempt(1), reraise=True
):
with attempt:
raise CustomError()
except CustomError:
Expand All @@ -164,7 +173,9 @@ class CustomError(Exception):
async def test_sleeps(self):
start = current_time_ms()
try:
async for attempt in tasyncio.AsyncRetrying(stop=stop_after_attempt(1), wait=wait_fixed(1)):
async for attempt in tasyncio.AsyncRetrying(
stop=stop_after_attempt(1), wait=wait_fixed(1)
):
with attempt:
raise Exception()
except RetryError:
Expand Down
Loading

0 comments on commit 09aefef

Please sign in to comment.