0
# Async Support
1
2
Tenacity provides comprehensive support for asynchronous programming with native async/await syntax, asyncio integration, trio support, and Tornado web framework compatibility. The library automatically detects async functions and applies appropriate async retry controllers.
3
4
## Core Async Classes
5
6
### AsyncRetrying
7
8
```python { .api }
9
from tenacity import AsyncRetrying
10
11
class AsyncRetrying(BaseRetrying):
12
"""
13
Asynchronous retry controller for coroutines.
14
15
Handles retry logic for async/await functions and coroutines.
16
Auto-detects trio vs asyncio for appropriate sleep function.
17
Provides async iterator support for manual retry loops.
18
"""
19
20
def __init__(
21
self,
22
sleep: Callable[[float], Awaitable[None]] = None, # Auto-detected
23
stop: StopBaseT = stop_never,
24
wait: WaitBaseT = wait_none(),
25
retry: Union[RetryBaseT, AsyncRetryBaseT] = retry_if_exception_type(),
26
before: Callable[[RetryCallState], Awaitable[None]] = None,
27
after: Callable[[RetryCallState], Awaitable[None]] = None,
28
before_sleep: Optional[Callable[[RetryCallState], Awaitable[None]]] = None,
29
reraise: bool = False,
30
retry_error_cls: type = RetryError,
31
retry_error_callback: Optional[Callable[[RetryCallState], Awaitable[Any]]] = None
32
):
33
"""
34
Initialize async retry controller.
35
36
Parameters are same as BaseRetrying, but callbacks can be async functions.
37
If sleep is None, auto-detects asyncio.sleep or trio.sleep.
38
"""
39
40
async def __call__(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
41
"""
42
Execute coroutine with asynchronous retry logic.
43
44
Parameters:
45
- fn: Async function/coroutine to execute with retries
46
- *args: Positional arguments to pass to fn
47
- **kwargs: Keyword arguments to pass to fn
48
49
Returns:
50
Result of successful coroutine execution.
51
52
Raises:
53
RetryError: When all retry attempts are exhausted
54
"""
55
56
def __aiter__(self) -> AsyncIterator[AttemptManager]:
57
"""Async iterator interface for attempt managers."""
58
59
async def __anext__(self) -> AttemptManager:
60
"""Async iteration support for manual retry loops."""
61
```
62
63
## Automatic Async Detection
64
65
The main `@retry` decorator automatically detects async functions and applies `AsyncRetrying`:
66
67
```python { .api }
68
from tenacity import retry, stop_after_attempt, wait_exponential
69
70
# Automatically uses AsyncRetrying for async functions
71
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1))
72
async def async_api_call():
73
"""Async function automatically gets async retry behavior."""
74
async with httpx.AsyncClient() as client:
75
response = await client.get("https://api.example.com/data")
76
return response.json()
77
78
# Usage
79
async def main():
80
result = await async_api_call()
81
```
82
83
## Async Retry Strategies
84
85
Tenacity provides async-specific retry strategies that support async predicates:
86
87
### async_retry_base
88
89
```python { .api }
90
from tenacity.asyncio.retry import async_retry_base
91
92
class async_retry_base(retry_base):
93
"""
94
Base class for async retry strategies.
95
96
Extends retry_base to support async predicate functions.
97
"""
98
99
@abstractmethod
100
async def __call__(self, retry_state: RetryCallState) -> bool:
101
"""
102
Async method to determine whether to retry.
103
104
Parameters:
105
- retry_state: Complete state of current retry session
106
107
Returns:
108
True if another attempt should be made, False to stop retrying
109
"""
110
```
111
112
### Async Exception Strategies
113
114
```python { .api }
115
from tenacity.asyncio import retry_if_exception
116
117
class retry_if_exception(async_retry_base):
118
"""
119
Async version of retry_if_exception with async predicate support.
120
121
Allows using async functions to evaluate whether exceptions
122
should trigger retries.
123
"""
124
125
def __init__(self, predicate: Callable[[BaseException], Awaitable[bool]]):
126
"""
127
Initialize with async exception predicate.
128
129
Parameters:
130
- predicate: Async function that takes exception and returns bool
131
"""
132
133
# Usage example
134
async def is_retryable_async(exc):
135
# Could perform async operations like database lookups
136
if isinstance(exc, CustomAPIError):
137
# Check if error code indicates retryable condition
138
return await check_error_retryable(exc.error_code)
139
return isinstance(exc, (ConnectionError, TimeoutError))
140
141
@retry(retry=retry_if_exception(is_retryable_async))
142
async def complex_async_operation():
143
pass
144
```
145
146
### Async Result Strategies
147
148
```python { .api }
149
from tenacity.asyncio import retry_if_result
150
151
class retry_if_result(async_retry_base):
152
"""
153
Async version of retry_if_result with async predicate support.
154
155
Allows using async functions to evaluate whether successful
156
results should trigger retries.
157
"""
158
159
def __init__(self, predicate: Callable[[Any], Awaitable[bool]]):
160
"""
161
Initialize with async result predicate.
162
163
Parameters:
164
- predicate: Async function that takes result and returns bool
165
"""
166
167
# Usage example
168
async def is_result_incomplete(result):
169
if isinstance(result, dict) and 'job_id' in result:
170
# Async check of job status
171
status = await check_job_status(result['job_id'])
172
return status in ['PENDING', 'RUNNING']
173
return False
174
175
@retry(retry=retry_if_result(is_result_incomplete))
176
async def submit_and_wait_for_job():
177
pass
178
```
179
180
### Async Logical Combinations
181
182
```python { .api }
183
from tenacity.asyncio import retry_any, retry_all
184
185
class retry_any(async_retry_base):
186
"""
187
Async version of retry_any supporting mixed sync/async conditions.
188
189
Combines multiple retry strategies (sync or async) with OR logic.
190
"""
191
192
def __init__(self, *retries: Union[retry_base, async_retry_base]):
193
"""
194
Initialize with retry strategies to combine.
195
196
Parameters:
197
- *retries: Mix of sync and async retry strategies
198
"""
199
200
class retry_all(async_retry_base):
201
"""
202
Async version of retry_all supporting mixed sync/async conditions.
203
204
Combines multiple retry strategies (sync or async) with AND logic.
205
"""
206
207
def __init__(self, *retries: Union[retry_base, async_retry_base]):
208
"""
209
Initialize with retry strategies to combine.
210
211
Parameters:
212
- *retries: Mix of sync and async retry strategies
213
"""
214
```
215
216
## Async Framework Integration
217
218
### AsyncIO Integration
219
220
Tenacity automatically integrates with asyncio by detecting the running event loop:
221
222
```python { .api }
223
import asyncio
224
from tenacity import retry, stop_after_attempt, wait_exponential
225
226
@retry(
227
stop=stop_after_attempt(5),
228
wait=wait_exponential(multiplier=1, min=4, max=10),
229
retry=retry_if_exception_type((asyncio.TimeoutError, ConnectionError))
230
)
231
async def asyncio_operation():
232
async with aiohttp.ClientSession() as session:
233
async with session.get('https://api.example.com') as response:
234
return await response.json()
235
236
# Run with asyncio
237
async def main():
238
result = await asyncio_operation()
239
240
asyncio.run(main())
241
```
242
243
### Trio Integration
244
245
Tenacity auto-detects trio and uses `trio.sleep` instead of `asyncio.sleep`:
246
247
```python { .api }
248
import trio
249
from tenacity import retry, stop_after_delay, wait_random_exponential
250
251
@retry(
252
stop=stop_after_delay(30),
253
wait=wait_random_exponential(multiplier=1, max=10)
254
)
255
async def trio_operation():
256
# Automatically uses trio.sleep for delays
257
await trio.sleep(0.1) # Your trio code here
258
return "success"
259
260
# Run with trio
261
async def main():
262
result = await trio_operation()
263
264
trio.run(main)
265
```
266
267
### Manual AsyncRetrying Usage
268
269
```python { .api }
270
# Direct usage of AsyncRetrying controller
271
async_retrying = AsyncRetrying(
272
stop=stop_after_attempt(3),
273
wait=wait_exponential(multiplier=1, min=1, max=10),
274
retry=retry_if_exception_type(ConnectionError)
275
)
276
277
# Apply to different async functions
278
result1 = await async_retrying(async_function1, arg1, arg2)
279
result2 = await async_retrying(async_function2, kwarg=value)
280
281
# Create variations
282
fast_async_retrying = async_retrying.copy(wait=wait_fixed(0.5))
283
```
284
285
## Async Iterator Support
286
287
AsyncRetrying provides async iterator support for manual retry loops:
288
289
```python { .api }
290
async def manual_async_retry():
291
async_retrying = AsyncRetrying(
292
stop=stop_after_attempt(3),
293
wait=wait_exponential(multiplier=1)
294
)
295
296
async for attempt in async_retrying:
297
with attempt:
298
# Your async code that might fail
299
result = await risky_async_operation()
300
break # Success - exit retry loop
301
302
return result
303
```
304
305
## Async Callbacks and Hooks
306
307
All callback parameters in async retry controllers can be async functions:
308
309
```python { .api }
310
import logging
311
312
async def async_before_callback(retry_state):
313
"""Async callback executed before each attempt."""
314
logger.info(f"Starting attempt {retry_state.attempt_number}")
315
await audit_attempt_start(retry_state)
316
317
async def async_after_callback(retry_state):
318
"""Async callback executed after each attempt."""
319
if retry_state.outcome.failed:
320
await log_failure_to_monitoring(retry_state)
321
else:
322
await record_success_metrics(retry_state)
323
324
async def async_before_sleep_callback(retry_state):
325
"""Async callback executed before sleeping between retries."""
326
await update_retry_status(
327
f"Retrying in {retry_state.upcoming_sleep} seconds"
328
)
329
330
@retry(
331
stop=stop_after_attempt(3),
332
wait=wait_exponential(multiplier=1),
333
before=async_before_callback,
334
after=async_after_callback,
335
before_sleep=async_before_sleep_callback
336
)
337
async def monitored_async_operation():
338
pass
339
```
340
341
## Tornado Integration
342
343
### TornadoRetrying
344
345
```python { .api }
346
from tenacity.tornadoweb import TornadoRetrying
347
from tornado import gen
348
349
class TornadoRetrying(BaseRetrying):
350
"""
351
Tornado web framework retry controller.
352
353
Specialized for Tornado's @gen.coroutine decorated functions.
354
Uses Tornado's IOLoop for asynchronous sleep operations.
355
"""
356
357
@gen.coroutine
358
def __call__(self, fn: Callable[..., Any], *args, **kwargs) -> Any:
359
"""
360
Execute Tornado coroutine with retry logic.
361
362
Uses Tornado's generator-based coroutine model and IOLoop.sleep.
363
364
Parameters:
365
- fn: Tornado coroutine to execute with retries
366
- *args: Positional arguments to pass to fn
367
- **kwargs: Keyword arguments to pass to fn
368
369
Returns:
370
tornado.concurrent.Future with result of successful execution.
371
"""
372
```
373
374
### Tornado Usage
375
376
```python { .api }
377
from tornado import gen, ioloop
378
from tenacity import retry, stop_after_attempt, wait_exponential
379
380
# Automatic Tornado detection
381
@retry(stop=stop_after_attempt(3), wait=wait_exponential())
382
@gen.coroutine
383
def tornado_operation():
384
"""Automatically uses TornadoRetrying for @gen.coroutine functions."""
385
response = yield tornado_http_client.fetch("http://api.example.com")
386
raise gen.Return(response.body)
387
388
# Manual TornadoRetrying usage
389
tornado_retrying = TornadoRetrying(
390
stop=stop_after_attempt(5),
391
wait=wait_exponential(multiplier=1, max=10)
392
)
393
394
@gen.coroutine
395
def manual_tornado_retry():
396
result = yield tornado_retrying(tornado_api_call, url="http://api.example.com")
397
raise gen.Return(result)
398
```
399
400
## Async Error Handling
401
402
### Async RetryError Handling
403
404
```python { .api }
405
from tenacity import RetryError
406
407
async def handle_async_retry_failure():
408
try:
409
result = await failing_async_operation()
410
except RetryError as retry_err:
411
# Access the last failed attempt
412
last_attempt = retry_err.last_attempt
413
414
# Get the original exception
415
if last_attempt.failed:
416
original_exc = last_attempt.result() # Raises original exception
417
418
# Or reraise the original exception directly
419
retry_err.reraise()
420
```
421
422
### Async Exception Chaining
423
424
```python { .api }
425
@retry(
426
retry=retry_if_exception_cause_type(ConnectionError),
427
stop=stop_after_attempt(3)
428
)
429
async def async_with_exception_chaining():
430
try:
431
await external_async_api()
432
except ConnectionError as e:
433
# Chain exceptions for cause analysis
434
raise ValueError("External API failed") from e
435
```
436
437
## Advanced Async Patterns
438
439
### Async Circuit Breaker
440
441
```python { .api }
442
import asyncio
443
from datetime import datetime, timedelta
444
445
class AsyncCircuitBreaker:
446
def __init__(self):
447
self.failure_count = 0
448
self.last_failure_time = None
449
self.circuit_open = False
450
451
async def should_retry(self, retry_state):
452
if self.circuit_open:
453
# Circuit is open, check if enough time has passed
454
if datetime.now() - self.last_failure_time > timedelta(minutes=5):
455
self.circuit_open = False
456
self.failure_count = 0
457
return True
458
return False
459
460
# Circuit is closed, normal retry logic
461
if retry_state.outcome and retry_state.outcome.failed:
462
self.failure_count += 1
463
if self.failure_count >= 5:
464
self.circuit_open = True
465
self.last_failure_time = datetime.now()
466
return False
467
468
return True
469
470
circuit_breaker = AsyncCircuitBreaker()
471
472
@retry(retry=circuit_breaker.should_retry)
473
async def circuit_protected_async_operation():
474
pass
475
```
476
477
### Async Resource Pool Retry
478
479
```python { .api }
480
import asyncio
481
482
async def async_resource_retry():
483
# Pool of async resources (connections, etc.)
484
resource_pool = asyncio.BoundedSemaphore(10)
485
486
@retry(
487
stop=stop_after_delay(60),
488
wait=wait_exponential(multiplier=1, max=5)
489
)
490
async def acquire_and_use_resource():
491
async with resource_pool:
492
# Use resource with retry protection
493
return await use_async_resource()
494
495
return await acquire_and_use_resource()
496
```
497
498
### Async Rate Limiting Integration
499
500
```python { .api }
501
import asyncio
502
from aiolimiter import AsyncLimiter
503
504
# Rate limiter integration
505
rate_limiter = AsyncLimiter(10, 60) # 10 requests per minute
506
507
@retry(
508
retry=retry_if_exception_type(RateLimitError),
509
wait=wait_exponential(multiplier=1, min=60, max=300), # Wait for rate limit reset
510
stop=stop_after_attempt(3)
511
)
512
async def rate_limited_async_call():
513
async with rate_limiter:
514
return await api_call()
515
```
516
517
### Async Retry with Context Managers
518
519
```python { .api }
520
import asyncio
521
from contextlib import asynccontextmanager
522
523
@asynccontextmanager
524
async def async_retry_context(retrying_config):
525
"""Async context manager for retry operations."""
526
async_retrying = AsyncRetrying(**retrying_config)
527
528
try:
529
yield async_retrying
530
finally:
531
# Cleanup code
532
await cleanup_async_resources()
533
534
# Usage
535
async def context_retry_example():
536
config = {
537
'stop': stop_after_attempt(3),
538
'wait': wait_exponential(multiplier=1)
539
}
540
541
async with async_retry_context(config) as retrying:
542
result = await retrying(async_operation, param=value)
543
return result
544
```
545
546
## Performance Considerations
547
548
### Async Overhead Minimization
549
550
```python { .api }
551
# For high-frequency async operations, minimize retry overhead
552
@retry(
553
wait=wait_none(), # No async sleep overhead
554
stop=stop_after_attempt(2), # Quick failure
555
retry=retry_if_exception_type(TransientAsyncError)
556
)
557
async def high_frequency_async_operation():
558
pass
559
```
560
561
### Memory Management in Long-Running Async Retries
562
563
```python { .api }
564
# For long-running async services, manage retry state memory
565
@retry(
566
stop=stop_never, # Infinite retry for services
567
wait=wait_exponential(multiplier=1, max=300), # Cap wait at 5 minutes
568
retry=retry_if_exception_type(RecoverableAsyncError)
569
)
570
async def persistent_async_service():
571
# Service code that should retry indefinitely
572
pass
573
```
574
575
This comprehensive async support enables robust retry behavior for modern asynchronous Python applications across asyncio, trio, and Tornado frameworks with full integration into Python's async/await ecosystem.