Skip to content

Commit 75a8fe5

Browse files
author
Martin Kulhavy
committed
Log "Retrying block" instead "Retrying None" etc. when there is no called function (#333)
This happens when `Retrying` is used as a context manager.
1 parent 95ecee8 commit 75a8fe5

File tree

7 files changed

+84
-9
lines changed

7 files changed

+84
-9
lines changed

tenacity/after.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ def after_log(
3636
"""After call strategy that logs to some logger the finished attempt."""
3737

3838
def log_it(retry_state: "RetryCallState") -> None:
39+
target = "block" if retry_state.fn is None else f"call to '{_utils.get_callback_name(retry_state.fn)}'"
40+
verb = "running" if retry_state.fn is None else "calling"
41+
3942
logger.log(
4043
log_level,
41-
f"Finished call to '{_utils.get_callback_name(retry_state.fn)}' "
42-
f"after {sec_format % retry_state.seconds_since_start}(s), "
43-
f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
44+
f"Finished {target} after {sec_format % retry_state.seconds_since_start}(s), "
45+
f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time {verb} it.",
4446
)
4547

4648
return log_it

tenacity/before.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["Re
3232
"""Before call strategy that logs to some logger the attempt."""
3333

3434
def log_it(retry_state: "RetryCallState") -> None:
35+
target = "block" if retry_state.fn is None else f"call to '{_utils.get_callback_name(retry_state.fn)}'"
36+
verb = "running" if retry_state.fn is None else "calling"
3537
logger.log(
3638
log_level,
37-
f"Starting call to '{_utils.get_callback_name(retry_state.fn)}', "
38-
f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
39+
f"Starting {target}, this is the {_utils.to_ordinal(retry_state.attempt_number)} time {verb} it.",
3940
)
4041

4142
return log_it

tenacity/before_sleep.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ def log_it(retry_state: "RetryCallState") -> None:
4848
verb, value = "returned", retry_state.outcome.result()
4949
local_exc_info = False # exc_info does not apply when no exception
5050

51+
target = "block" if retry_state.fn is None else _utils.get_callback_name(retry_state.fn)
5152
logger.log(
5253
log_level,
53-
f"Retrying {_utils.get_callback_name(retry_state.fn)} "
54-
f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
54+
f"Retrying {target} in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
5555
exc_info=local_exc_info,
5656
)
5757

tests/test_after.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_01_default(self):
3232
)
3333

3434
def test_02_custom_sec_format(self):
35-
"""Test log formatting with custom int format.."""
35+
"""Test log formatting with custom int format."""
3636
log = unittest.mock.MagicMock(spec="logging.Logger.log")
3737
logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)
3838

@@ -48,3 +48,25 @@ def test_02_custom_sec_format(self):
4848
f"after {sec_format % retry_state.seconds_since_start}(s), "
4949
f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
5050
)
51+
52+
def test_03_no_call_function(self):
53+
"""Test log formatting when there is no called function.
54+
55+
E.g. when using as context manager.
56+
"""
57+
log = unittest.mock.MagicMock(spec="logging.Logger.log")
58+
logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)
59+
60+
sec_format = "%0.3f"
61+
delay_since_first_attempt = 0.1
62+
63+
retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, delay_since_first_attempt)
64+
retry_state.fn = None
65+
fun = after_log(logger=logger, log_level=self.log_level) # use default sec_format
66+
fun(retry_state)
67+
log.assert_called_once_with(
68+
self.log_level,
69+
f"Finished block "
70+
f"after {sec_format % retry_state.seconds_since_start}(s), "
71+
f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time running it.",
72+
)

tests/test_before.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,22 @@ def test_01_default(self):
2828
f"Starting call to '{_utils.get_callback_name(retry_state.fn)}', "
2929
f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
3030
)
31+
32+
def test_02_no_call_function(self):
33+
"""Test log formatting when there is no called function.
34+
35+
E.g. when using as context manager.
36+
"""
37+
log = unittest.mock.MagicMock(spec="logging.Logger.log")
38+
logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)
39+
40+
delay_since_first_attempt = 0.1
41+
42+
retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, delay_since_first_attempt)
43+
retry_state.fn = None
44+
fun = before_log(logger=logger, log_level=self.log_level)
45+
fun(retry_state)
46+
log.assert_called_once_with(
47+
self.log_level,
48+
f"Starting block, this is the {_utils.to_ordinal(retry_state.attempt_number)} time running it.",
49+
)

tests/test_before_sleep.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,30 @@ def test_02_wrong_return(self):
6161
f"in {sleep} seconds as it returned {retry_state.outcome.result()}.",
6262
exc_info=False,
6363
)
64+
65+
def test_03_wrong_return_no_call_function(self):
66+
"""Test log formatting when there is no called function.
67+
68+
E.g. when using as context manager.
69+
"""
70+
log = unittest.mock.MagicMock(spec="logging.Logger.log")
71+
logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)
72+
73+
delay_since_first_attempt = 0.1
74+
sleep = 2
75+
76+
retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, delay_since_first_attempt)
77+
retry_state.fn = None
78+
retry_state.next_action = unittest.mock.MagicMock(spec="tenacity.RetryAction", sleep=sleep)
79+
retry_state.outcome = unittest.mock.MagicMock(
80+
spec="tenacity.Future",
81+
failed=False,
82+
result=unittest.mock.Mock(return_value="infinity"),
83+
)
84+
fun = before_sleep_log(logger=logger, log_level=self.log_level)
85+
fun(retry_state)
86+
log.assert_called_once_with(
87+
self.log_level,
88+
f"Retrying block in {sleep} seconds as it returned {retry_state.outcome.result()}.",
89+
exc_info=False,
90+
)

tests/test_tenacity.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def _set_delay_since_start(retry_state, delay):
5050
assert retry_state.seconds_since_start == delay
5151

5252

53+
def flaky_function():
54+
"""Just an empty function to use as the called function."""
55+
56+
5357
def make_retry_state(previous_attempt_number, delay_since_first_attempt, last_result=None):
5458
"""Construct RetryCallState for given attempt number & delay.
5559
@@ -63,7 +67,7 @@ def make_retry_state(previous_attempt_number, delay_since_first_attempt, last_re
6367
delay_since_first_attempt=delay_since_first_attempt,
6468
)
6569

66-
retry_state = RetryCallState(None, None, (), {})
70+
retry_state = RetryCallState(None, flaky_function, (), {})
6771
retry_state.attempt_number = previous_attempt_number
6872
if last_result is not None:
6973
retry_state.outcome = last_result

0 commit comments

Comments
 (0)