0
# Utilities & Helpers
1
2
Tenacity provides various utility functions, helper classes, and supporting functionality that enable advanced retry scenarios, time conversion, sleep strategies, and integration with different environments.
3
4
## Exception Classes
5
6
### TryAgain
7
8
```python { .api }
9
from tenacity import TryAgain
10
11
class TryAgain(Exception):
12
"""
13
Exception to force immediate retry regardless of retry condition.
14
15
Raise this exception to bypass all retry strategy evaluation
16
and force another attempt (subject to stop conditions).
17
Useful for explicit retry control within application logic.
18
"""
19
pass
20
```
21
22
#### Usage Examples
23
24
```python { .api }
25
@retry(
26
stop=stop_after_attempt(5),
27
retry=retry_if_exception_type(ValueError) # Only retry ValueErrors normally
28
)
29
def conditional_retry_operation():
30
if some_temporary_condition():
31
# Force retry even though condition might not normally trigger it
32
raise TryAgain
33
34
# Normal operation logic
35
if error_condition():
36
raise ConnectionError("Network failed") # This would NOT normally retry
37
38
return "success"
39
```
40
41
### RetryError
42
43
```python { .api }
44
from tenacity import RetryError
45
46
class RetryError(Exception):
47
"""
48
Exception raised when all retry attempts are exhausted.
49
50
Contains information about the final failed attempt and provides
51
methods to access the original exception that caused the failure.
52
"""
53
54
def __init__(self, last_attempt: Future):
55
"""
56
Initialize with the final failed attempt.
57
58
Parameters:
59
- last_attempt: Future object representing the final attempt
60
"""
61
self.last_attempt = last_attempt
62
63
def reraise(self) -> None:
64
"""
65
Reraise the original exception from the last attempt.
66
67
Raises the actual exception that caused the final failure,
68
preserving the original traceback information.
69
"""
70
71
@property
72
def last_attempt(self) -> Future:
73
"""Access the final failed attempt Future object."""
74
```
75
76
#### RetryError Usage
77
78
```python { .api }
79
from tenacity import RetryError
80
81
def handle_retry_failure():
82
try:
83
result = failing_operation()
84
except RetryError as retry_err:
85
# Access the final attempt
86
final_attempt = retry_err.last_attempt
87
print(f"Failed after attempt {final_attempt.attempt_number}")
88
89
# Get the original exception
90
try:
91
original_result = final_attempt.result() # This will raise the original exception
92
except Exception as original_exc:
93
print(f"Original failure: {original_exc}")
94
95
# Or reraise the original exception directly
96
# retry_err.reraise() # Preserves original traceback
97
98
@retry(
99
stop=stop_after_attempt(3),
100
reraise=False # RetryError will be raised instead of original exception
101
)
102
def failing_operation():
103
raise ValueError("Something went wrong")
104
```
105
106
## Sleep Functions and Strategies
107
108
### Default Sleep Function
109
110
```python { .api }
111
from tenacity.nap import sleep
112
113
def sleep(seconds: float) -> None:
114
"""
115
Default sleep function using time.sleep().
116
117
Parameters:
118
- seconds: Number of seconds to sleep
119
120
Standard blocking sleep implementation used by default
121
in synchronous retry operations.
122
"""
123
```
124
125
### Event-Based Sleep
126
127
```python { .api }
128
from tenacity.nap import sleep_using_event
129
import threading
130
131
class sleep_using_event:
132
"""
133
Sleep strategy that waits on a threading event with timeout.
134
135
Allows external interruption of sleep periods through event signaling.
136
Useful for graceful shutdown scenarios or coordinated stopping.
137
"""
138
139
def __init__(self, event: threading.Event):
140
"""
141
Initialize with threading event for sleep control.
142
143
Parameters:
144
- event: threading.Event that can interrupt sleep when set
145
"""
146
147
def __call__(self, timeout: Optional[float]) -> None:
148
"""
149
Sleep for specified timeout or until event is set.
150
151
Parameters:
152
- timeout: Maximum time to sleep in seconds (None = indefinite)
153
154
Returns immediately if event is already set.
155
"""
156
```
157
158
#### Event Sleep Usage
159
160
```python { .api }
161
import threading
162
163
# Create event for coordinated sleep control
164
shutdown_event = threading.Event()
165
event_sleep = sleep_using_event(shutdown_event)
166
167
@retry(
168
sleep=event_sleep,
169
stop=stop_after_attempt(10),
170
wait=wait_exponential(multiplier=1, max=60)
171
)
172
def interruptible_operation():
173
# This operation's sleep periods can be interrupted
174
pass
175
176
# In another thread or signal handler
177
def shutdown_handler():
178
shutdown_event.set() # Interrupts any ongoing sleep periods
179
```
180
181
## Time Utilities
182
183
### Time Unit Conversion
184
185
```python { .api }
186
from tenacity._utils import time_unit_type, to_seconds
187
from datetime import timedelta
188
189
# Type alias for time specifications
190
time_unit_type = Union[int, float, timedelta]
191
192
def to_seconds(time_unit: time_unit_type) -> float:
193
"""
194
Convert various time units to seconds.
195
196
Parameters:
197
- time_unit: Time specification as int/float (seconds) or timedelta
198
199
Returns:
200
Time converted to seconds as float
201
"""
202
```
203
204
#### Time Conversion Examples
205
206
```python { .api }
207
from datetime import timedelta
208
209
# All equivalent - 30 seconds
210
seconds_int = to_seconds(30) # 30.0
211
seconds_float = to_seconds(30.5) # 30.5
212
seconds_timedelta = to_seconds(timedelta(seconds=30)) # 30.0
213
214
# Complex time specifications
215
minutes = to_seconds(timedelta(minutes=5)) # 300.0
216
mixed = to_seconds(timedelta(minutes=2, seconds=30)) # 150.0
217
hours = to_seconds(timedelta(hours=1)) # 3600.0
218
219
# Usage in retry configurations
220
@retry(
221
stop=stop_after_delay(to_seconds(timedelta(minutes=10))),
222
wait=wait_fixed(to_seconds(timedelta(seconds=30)))
223
)
224
def time_converted_operation():
225
pass
226
```
227
228
### Maximum Wait Constant
229
230
```python { .api }
231
from tenacity._utils import MAX_WAIT
232
import sys
233
234
# Maximum wait time constant
235
MAX_WAIT: float = sys.maxsize / 2
236
```
237
238
## Utility Functions
239
240
### Callback Name Resolution
241
242
```python { .api }
243
from tenacity._utils import get_callback_name
244
245
def get_callback_name(cb: Callable[..., Any]) -> str:
246
"""
247
Get fully qualified name of callback function.
248
249
Parameters:
250
- cb: Callback function to get name for
251
252
Returns:
253
Fully qualified function name as string
254
255
Useful for logging and debugging callback configurations.
256
"""
257
```
258
259
#### Callback Name Usage
260
261
```python { .api }
262
def my_custom_callback(retry_state):
263
pass
264
265
callback_name = get_callback_name(my_custom_callback)
266
print(callback_name) # "my_module.my_custom_callback"
267
268
# Usage in logging
269
def log_callback_config(retrying_instance):
270
before_name = get_callback_name(retrying_instance.before)
271
after_name = get_callback_name(retrying_instance.after)
272
print(f"Before callback: {before_name}")
273
print(f"After callback: {after_name}")
274
```
275
276
### Ordinal Number Conversion
277
278
```python { .api }
279
from tenacity._utils import to_ordinal
280
281
def to_ordinal(pos_num: int) -> str:
282
"""
283
Convert positive number to ordinal string representation.
284
285
Parameters:
286
- pos_num: Positive integer to convert
287
288
Returns:
289
Ordinal string (1st, 2nd, 3rd, 4th, etc.)
290
291
Useful for human-friendly logging and error messages.
292
"""
293
```
294
295
#### Ordinal Usage Examples
296
297
```python { .api }
298
# Convert numbers to ordinals
299
print(to_ordinal(1)) # "1st"
300
print(to_ordinal(2)) # "2nd"
301
print(to_ordinal(3)) # "3rd"
302
print(to_ordinal(4)) # "4th"
303
print(to_ordinal(21)) # "21st"
304
print(to_ordinal(22)) # "22nd"
305
306
# Usage in retry messages
307
def ordinal_logging_callback(retry_state):
308
attempt_ordinal = to_ordinal(retry_state.attempt_number)
309
print(f"Starting {attempt_ordinal} attempt at {retry_state.fn.__name__}")
310
311
@retry(before=ordinal_logging_callback)
312
def operation_with_ordinal_logging():
313
pass
314
```
315
316
## Async Utilities
317
318
### Coroutine Detection
319
320
```python { .api }
321
from tenacity._utils import is_coroutine_callable
322
323
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
324
"""
325
Check if callable is a coroutine function.
326
327
Parameters:
328
- call: Callable to check
329
330
Returns:
331
True if callable is async/coroutine function, False otherwise
332
333
Used internally to detect async functions for automatic
334
AsyncRetrying application.
335
"""
336
```
337
338
### Async Function Wrapping
339
340
```python { .api }
341
from tenacity._utils import wrap_to_async_func
342
343
def wrap_to_async_func(call: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
344
"""
345
Wrap synchronous function to async, or return as-is if already async.
346
347
Parameters:
348
- call: Function to wrap (sync or async)
349
350
Returns:
351
Async function that can be awaited
352
353
Enables using sync callbacks in async retry contexts.
354
"""
355
```
356
357
#### Async Utility Usage
358
359
```python { .api }
360
# Check if function is async
361
def sync_function():
362
return "sync result"
363
364
async def async_function():
365
return "async result"
366
367
print(is_coroutine_callable(sync_function)) # False
368
print(is_coroutine_callable(async_function)) # True
369
370
# Wrap sync function for async use
371
wrapped_sync = wrap_to_async_func(sync_function)
372
wrapped_async = wrap_to_async_func(async_function) # Returns as-is
373
374
# Both can now be awaited
375
async def demo():
376
result1 = await wrapped_sync() # Works
377
result2 = await wrapped_async() # Works
378
```
379
380
## Constants and Sentinels
381
382
### NO_RESULT Sentinel
383
384
```python { .api }
385
from tenacity import NO_RESULT
386
387
# Sentinel object for unset results
388
NO_RESULT: object
389
```
390
391
#### NO_RESULT Usage
392
393
```python { .api }
394
def check_result_status(retry_state):
395
if retry_state.outcome is NO_RESULT:
396
print("No result set yet")
397
elif retry_state.outcome.failed:
398
print("Attempt failed")
399
else:
400
print("Attempt succeeded")
401
```
402
403
## Advanced Utility Patterns
404
405
### Custom Sleep Implementations
406
407
```python { .api }
408
import asyncio
409
import time
410
411
class ProgressiveSleep:
412
"""Custom sleep with progress indication."""
413
414
def __init__(self, progress_callback=None):
415
self.progress_callback = progress_callback
416
417
def __call__(self, seconds):
418
if self.progress_callback:
419
# Show progress during long sleeps
420
for i in range(int(seconds)):
421
time.sleep(1)
422
self.progress_callback(i + 1, int(seconds))
423
# Handle fractional remainder
424
remainder = seconds - int(seconds)
425
if remainder > 0:
426
time.sleep(remainder)
427
else:
428
time.sleep(seconds)
429
430
# Usage with progress indication
431
def progress_indicator(current, total):
432
print(f"Sleeping... {current}/{total} seconds")
433
434
progressive_sleep = ProgressiveSleep(progress_indicator)
435
436
@retry(
437
sleep=progressive_sleep,
438
wait=wait_exponential(multiplier=1, min=5, max=30)
439
)
440
def operation_with_progress():
441
pass
442
```
443
444
### Retry State Analysis
445
446
```python { .api }
447
def analyze_retry_state(retry_state: RetryCallState) -> dict:
448
"""Comprehensive retry state analysis."""
449
return {
450
'function_name': retry_state.fn.__name__,
451
'attempt_number': retry_state.attempt_number,
452
'elapsed_seconds': retry_state.seconds_since_start,
453
'total_idle_time': retry_state.idle_for,
454
'active_time': retry_state.seconds_since_start - retry_state.idle_for,
455
'last_outcome': 'failed' if retry_state.outcome and retry_state.outcome.failed else 'success',
456
'upcoming_sleep': retry_state.upcoming_sleep if retry_state.upcoming_sleep else 0,
457
'efficiency': (retry_state.seconds_since_start - retry_state.idle_for) / retry_state.seconds_since_start if retry_state.seconds_since_start > 0 else 1.0
458
}
459
460
def detailed_analysis_callback(retry_state):
461
analysis = analyze_retry_state(retry_state)
462
print(f"Retry Analysis: {analysis}")
463
464
@retry(after=detailed_analysis_callback)
465
def analyzed_operation():
466
pass
467
```
468
469
### Time Zone Aware Utilities
470
471
```python { .api }
472
from datetime import datetime, timezone
473
import time
474
475
class TimeZoneAwareSleep:
476
"""Sleep implementation that considers time zones."""
477
478
def __init__(self, timezone_offset=0):
479
self.timezone_offset = timezone_offset
480
481
def __call__(self, seconds):
482
# Could adjust sleep based on time zone considerations
483
current_hour = datetime.now(timezone.utc).hour + self.timezone_offset
484
485
# Example: longer sleeps during business hours
486
if 9 <= current_hour <= 17:
487
adjusted_seconds = seconds * 1.5 # 50% longer during business hours
488
else:
489
adjusted_seconds = seconds
490
491
time.sleep(adjusted_seconds)
492
493
# Usage for timezone-aware retry behavior
494
tz_sleep = TimeZoneAwareSleep(timezone_offset=-8) # PST
495
496
@retry(
497
sleep=tz_sleep,
498
wait=wait_exponential(multiplier=1, max=60)
499
)
500
def timezone_aware_operation():
501
pass
502
```
503
504
### Performance Monitoring Utilities
505
506
```python { .api }
507
import time
508
import threading
509
from collections import defaultdict
510
511
class RetryPerformanceMonitor:
512
"""Monitor retry performance across the application."""
513
514
def __init__(self):
515
self.stats = defaultdict(list)
516
self.lock = threading.Lock()
517
518
def record_attempt(self, retry_state):
519
with self.lock:
520
function_name = retry_state.fn.__name__
521
self.stats[function_name].append({
522
'attempt': retry_state.attempt_number,
523
'timestamp': time.time(),
524
'elapsed': retry_state.seconds_since_start,
525
'success': not (retry_state.outcome and retry_state.outcome.failed)
526
})
527
528
def get_summary(self, function_name=None):
529
with self.lock:
530
if function_name:
531
return self._summarize_function(function_name, self.stats[function_name])
532
else:
533
return {fn: self._summarize_function(fn, attempts)
534
for fn, attempts in self.stats.items()}
535
536
def _summarize_function(self, function_name, attempts):
537
if not attempts:
538
return {'total_attempts': 0}
539
540
total_attempts = len(attempts)
541
successful_operations = len([a for a in attempts if a['success']])
542
avg_duration = sum(a['elapsed'] for a in attempts) / total_attempts
543
544
return {
545
'function_name': function_name,
546
'total_attempts': total_attempts,
547
'successful_operations': successful_operations,
548
'success_rate': successful_operations / total_attempts,
549
'avg_duration': avg_duration
550
}
551
552
# Global performance monitor
553
perf_monitor = RetryPerformanceMonitor()
554
555
@retry(after=perf_monitor.record_attempt)
556
def monitored_operation():
557
pass
558
559
# Get performance summary
560
summary = perf_monitor.get_summary()
561
print(summary)
562
```
563
564
### Resource Management Utilities
565
566
```python { .api }
567
import weakref
568
from contextlib import contextmanager
569
570
class RetryResourceManager:
571
"""Manage resources across retry attempts."""
572
573
def __init__(self):
574
self.active_retries = weakref.WeakSet()
575
576
def register_retry(self, retry_state):
577
self.active_retries.add(retry_state)
578
579
def get_active_count(self):
580
return len(self.active_retries)
581
582
@contextmanager
583
def resource_context(self, retry_state):
584
self.register_retry(retry_state)
585
try:
586
yield
587
finally:
588
# Cleanup happens automatically via WeakSet
589
pass
590
591
# Global resource manager
592
resource_manager = RetryResourceManager()
593
594
def resource_aware_callback(retry_state):
595
active_count = resource_manager.get_active_count()
596
if active_count > 10:
597
print(f"Warning: {active_count} active retry operations")
598
599
@retry(
600
before=resource_aware_callback,
601
before_sleep=lambda rs: resource_manager.register_retry(rs)
602
)
603
def resource_managed_operation():
604
pass
605
```
606
607
This comprehensive collection of utilities and helpers provides the building blocks for advanced retry scenarios, performance monitoring, resource management, and integration with various system components.