Skip to content

Commit 18d05a6

Browse files
noambloomNoam Bloomjd
authored
Support datetime.timedelta as a valid wait unit type (#342)
* Support `datetime.timedelta` as a valid wait unit type Signed-off-by: Noam Bloom <[email protected]> * Add datetime.timedelta support tests Signed-off-by: Noam Bloom <[email protected]> Co-authored-by: Noam Bloom <[email protected]> Co-authored-by: Julien Danjou <[email protected]>
1 parent f6465c0 commit 18d05a6

File tree

3 files changed

+65
-47
lines changed

3 files changed

+65
-47
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
features:
3+
- Add ``datetime.timedelta`` as accepted wait unit type.

tenacity/wait.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@
1717
import abc
1818
import random
1919
import typing
20+
from datetime import timedelta
2021

2122
from tenacity import _utils
2223

2324
if typing.TYPE_CHECKING:
2425
from tenacity import RetryCallState
2526

27+
wait_unit_type = typing.Union[int, float, timedelta]
28+
29+
30+
def to_seconds(wait_unit: wait_unit_type) -> float:
31+
return float(wait_unit.total_seconds() if isinstance(wait_unit, timedelta) else wait_unit)
32+
2633

2734
class wait_base(abc.ABC):
2835
"""Abstract base class for wait strategies."""
@@ -44,8 +51,8 @@ def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_bas
4451
class wait_fixed(wait_base):
4552
"""Wait strategy that waits a fixed amount of time between each retry."""
4653

47-
def __init__(self, wait: float) -> None:
48-
self.wait_fixed = wait
54+
def __init__(self, wait: wait_unit_type) -> None:
55+
self.wait_fixed = to_seconds(wait)
4956

5057
def __call__(self, retry_state: "RetryCallState") -> float:
5158
return self.wait_fixed
@@ -61,9 +68,9 @@ def __init__(self) -> None:
6168
class wait_random(wait_base):
6269
"""Wait strategy that waits a random amount of time between min/max."""
6370

64-
def __init__(self, min: typing.Union[int, float] = 0, max: typing.Union[int, float] = 1) -> None: # noqa
65-
self.wait_random_min = min
66-
self.wait_random_max = max
71+
def __init__(self, min: wait_unit_type = 0, max: wait_unit_type = 1) -> None: # noqa
72+
self.wait_random_min = to_seconds(min)
73+
self.wait_random_max = to_seconds(max)
6774

6875
def __call__(self, retry_state: "RetryCallState") -> float:
6976
return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min))
@@ -113,13 +120,13 @@ class wait_incrementing(wait_base):
113120

114121
def __init__(
115122
self,
116-
start: typing.Union[int, float] = 0,
117-
increment: typing.Union[int, float] = 100,
118-
max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa
123+
start: wait_unit_type = 0,
124+
increment: wait_unit_type = 100,
125+
max: wait_unit_type = _utils.MAX_WAIT, # noqa
119126
) -> None:
120-
self.start = start
121-
self.increment = increment
122-
self.max = max
127+
self.start = to_seconds(start)
128+
self.increment = to_seconds(increment)
129+
self.max = to_seconds(max)
123130

124131
def __call__(self, retry_state: "RetryCallState") -> float:
125132
result = self.start + (self.increment * (retry_state.attempt_number - 1))
@@ -142,13 +149,13 @@ class wait_exponential(wait_base):
142149
def __init__(
143150
self,
144151
multiplier: typing.Union[int, float] = 1,
145-
max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa
152+
max: wait_unit_type = _utils.MAX_WAIT, # noqa
146153
exp_base: typing.Union[int, float] = 2,
147-
min: typing.Union[int, float] = 0, # noqa
154+
min: wait_unit_type = 0, # noqa
148155
) -> None:
149156
self.multiplier = multiplier
150-
self.min = min
151-
self.max = max
157+
self.min = to_seconds(min)
158+
self.max = to_seconds(max)
152159
self.exp_base = exp_base
153160

154161
def __call__(self, retry_state: "RetryCallState") -> float:

tests/test_tenacity.py

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
16+
import datetime
1617
import logging
1718
import re
1819
import sys
@@ -29,7 +30,6 @@
2930
import tenacity
3031
from tenacity import RetryCallState, RetryError, Retrying, retry
3132

32-
3333
_unset = object()
3434

3535

@@ -180,28 +180,34 @@ def test_no_sleep(self):
180180
self.assertEqual(0, r.wait(make_retry_state(18, 9879)))
181181

182182
def test_fixed_sleep(self):
183-
r = Retrying(wait=tenacity.wait_fixed(1))
184-
self.assertEqual(1, r.wait(make_retry_state(12, 6546)))
183+
for wait in (1, datetime.timedelta(seconds=1)):
184+
with self.subTest():
185+
r = Retrying(wait=tenacity.wait_fixed(wait))
186+
self.assertEqual(1, r.wait(make_retry_state(12, 6546)))
185187

186188
def test_incrementing_sleep(self):
187-
r = Retrying(wait=tenacity.wait_incrementing(start=500, increment=100))
188-
self.assertEqual(500, r.wait(make_retry_state(1, 6546)))
189-
self.assertEqual(600, r.wait(make_retry_state(2, 6546)))
190-
self.assertEqual(700, r.wait(make_retry_state(3, 6546)))
189+
for start, increment in ((500, 100), (datetime.timedelta(seconds=500), datetime.timedelta(seconds=100))):
190+
with self.subTest():
191+
r = Retrying(wait=tenacity.wait_incrementing(start=start, increment=increment))
192+
self.assertEqual(500, r.wait(make_retry_state(1, 6546)))
193+
self.assertEqual(600, r.wait(make_retry_state(2, 6546)))
194+
self.assertEqual(700, r.wait(make_retry_state(3, 6546)))
191195

192196
def test_random_sleep(self):
193-
r = Retrying(wait=tenacity.wait_random(min=1, max=20))
194-
times = set()
195-
for x in range(1000):
196-
times.add(r.wait(make_retry_state(1, 6546)))
197-
198-
# this is kind of non-deterministic...
199-
self.assertTrue(len(times) > 1)
200-
for t in times:
201-
self.assertTrue(t >= 1)
202-
self.assertTrue(t < 20)
203-
204-
def test_random_sleep_without_min(self):
197+
for min_, max_ in ((1, 20), (datetime.timedelta(seconds=1), datetime.timedelta(seconds=20))):
198+
with self.subTest():
199+
r = Retrying(wait=tenacity.wait_random(min=min_, max=max_))
200+
times = set()
201+
for _ in range(1000):
202+
times.add(r.wait(make_retry_state(1, 6546)))
203+
204+
# this is kind of non-deterministic...
205+
self.assertTrue(len(times) > 1)
206+
for t in times:
207+
self.assertTrue(t >= 1)
208+
self.assertTrue(t < 20)
209+
210+
def test_random_sleep_withoutmin_(self):
205211
r = Retrying(wait=tenacity.wait_random(max=2))
206212
times = set()
207213
times.add(r.wait(make_retry_state(1, 6546)))
@@ -274,18 +280,20 @@ def test_exponential_with_min_wait_and_multiplier(self):
274280
self.assertEqual(r.wait(make_retry_state(8, 0)), 256)
275281
self.assertEqual(r.wait(make_retry_state(20, 0)), 1048576)
276282

277-
def test_exponential_with_min_wait_and_max_wait(self):
278-
r = Retrying(wait=tenacity.wait_exponential(min=10, max=100))
279-
self.assertEqual(r.wait(make_retry_state(1, 0)), 10)
280-
self.assertEqual(r.wait(make_retry_state(2, 0)), 10)
281-
self.assertEqual(r.wait(make_retry_state(3, 0)), 10)
282-
self.assertEqual(r.wait(make_retry_state(4, 0)), 10)
283-
self.assertEqual(r.wait(make_retry_state(5, 0)), 16)
284-
self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
285-
self.assertEqual(r.wait(make_retry_state(7, 0)), 64)
286-
self.assertEqual(r.wait(make_retry_state(8, 0)), 100)
287-
self.assertEqual(r.wait(make_retry_state(9, 0)), 100)
288-
self.assertEqual(r.wait(make_retry_state(20, 0)), 100)
283+
def test_exponential_with_min_wait_andmax__wait(self):
284+
for min_, max_ in ((10, 100), (datetime.timedelta(seconds=10), datetime.timedelta(seconds=100))):
285+
with self.subTest():
286+
r = Retrying(wait=tenacity.wait_exponential(min=min_, max=max_))
287+
self.assertEqual(r.wait(make_retry_state(1, 0)), 10)
288+
self.assertEqual(r.wait(make_retry_state(2, 0)), 10)
289+
self.assertEqual(r.wait(make_retry_state(3, 0)), 10)
290+
self.assertEqual(r.wait(make_retry_state(4, 0)), 10)
291+
self.assertEqual(r.wait(make_retry_state(5, 0)), 16)
292+
self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
293+
self.assertEqual(r.wait(make_retry_state(7, 0)), 64)
294+
self.assertEqual(r.wait(make_retry_state(8, 0)), 100)
295+
self.assertEqual(r.wait(make_retry_state(9, 0)), 100)
296+
self.assertEqual(r.wait(make_retry_state(20, 0)), 100)
289297

290298
def test_legacy_explicit_wait_type(self):
291299
Retrying(wait="exponential_sleep")
@@ -335,7 +343,7 @@ def test_wait_arbitrary_sum(self):
335343
)
336344
)
337345
# Test it a few time since it's random
338-
for i in range(1000):
346+
for _ in range(1000):
339347
w = r.wait(make_retry_state(1, 5))
340348
self.assertLess(w, 9)
341349
self.assertGreaterEqual(w, 6)

0 commit comments

Comments
 (0)