or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-support.mdcallbacks-hooks.mdcore-decorator.mdindex.mdretry-strategies.mdstop-conditions.mdutilities.mdwait-strategies.md

async-support.mddocs/

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.