or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdindex.mdinstrumentation.mdretry-callers.mdretry-core.md

retry-callers.mddocs/

0

# Retry Callers

1

2

Reusable retry caller classes that enable pre-configuring retry parameters and applying them to multiple functions. These classes are ideal when you need consistent retry behavior across different operations or want to separate retry configuration from business logic.

3

4

## Capabilities

5

6

### Synchronous Retry Caller

7

8

The `RetryingCaller` class provides a reusable interface for calling functions with consistent retry parameters.

9

10

```python { .api }

11

class RetryingCaller:

12

"""

13

Reusable caller for retrying functions with pre-configured parameters.

14

15

Instances can be reused as they create new retry contexts on each call.

16

"""

17

18

def __init__(

19

self,

20

attempts: int | None = 10,

21

timeout: float | datetime.timedelta | None = 45.0,

22

wait_initial: float | datetime.timedelta = 0.1,

23

wait_max: float | datetime.timedelta = 5.0,

24

wait_jitter: float | datetime.timedelta = 1.0,

25

wait_exp_base: float = 2.0,

26

):

27

"""

28

Initialize retry caller with default parameters.

29

30

Parameters: Same as retry() decorator

31

"""

32

33

def __call__(

34

self,

35

on: ExcOrPredicate,

36

callable_: Callable[P, T],

37

/,

38

*args: P.args,

39

**kwargs: P.kwargs,

40

) -> T:

41

"""

42

Call callable with retries if specified exceptions are raised.

43

44

Parameters:

45

- on: Exception(s) to retry on

46

- callable_: Function to call

47

- args: Positional arguments for the function

48

- kwargs: Keyword arguments for the function

49

50

Returns:

51

Return value of the callable

52

"""

53

54

def on(self, on: ExcOrPredicate, /) -> BoundRetryingCaller:

55

"""

56

Create bound caller pre-configured for specific exception types.

57

58

Returns:

59

BoundRetryingCaller instance bound to the exception type

60

"""

61

```

62

63

**Usage Examples:**

64

65

```python

66

import stamina

67

import httpx

68

69

# Create reusable caller with custom parameters

70

http_caller = stamina.RetryingCaller(

71

attempts=5,

72

timeout=30.0,

73

wait_initial=0.5,

74

wait_max=10.0

75

)

76

77

# Call different functions with same retry behavior

78

def fetch_user(user_id):

79

response = httpx.get(f"/users/{user_id}")

80

response.raise_for_status()

81

return response.json()

82

83

def fetch_posts(user_id):

84

response = httpx.get(f"/users/{user_id}/posts")

85

response.raise_for_status()

86

return response.json()

87

88

# Use the same caller for multiple operations

89

user = http_caller(httpx.HTTPError, fetch_user, 123)

90

posts = http_caller(httpx.HTTPError, fetch_posts, 123)

91

92

# Direct function calls

93

result = http_caller(

94

httpx.HTTPError,

95

lambda: httpx.get("https://api.example.com/data").json(),

96

)

97

98

# Call with additional arguments

99

result = http_caller(

100

ValueError,

101

process_data,

102

input_data,

103

format="json",

104

validate=True

105

)

106

```

107

108

### Bound Retry Caller

109

110

The `BoundRetryingCaller` class is created by calling `RetryingCaller.on()` and provides a simpler interface when you always retry on the same exception types.

111

112

```python { .api }

113

class BoundRetryingCaller:

114

"""

115

RetryingCaller pre-bound to specific exception types.

116

117

Created by RetryingCaller.on() - do not instantiate directly.

118

"""

119

120

def __call__(

121

self,

122

callable_: Callable[P, T],

123

/,

124

*args: P.args,

125

**kwargs: P.kwargs

126

) -> T:

127

"""

128

Call callable with bound exception handling.

129

130

Parameters:

131

- callable_: Function to call

132

- args: Positional arguments for the function

133

- kwargs: Keyword arguments for the function

134

135

Returns:

136

Return value of the callable

137

"""

138

```

139

140

**Usage Examples:**

141

142

```python

143

# Create caller bound to specific exception

144

http_caller = stamina.RetryingCaller(attempts=3, timeout=15.0)

145

bound_caller = http_caller.on(httpx.HTTPError)

146

147

# Cleaner syntax for repeated operations

148

user = bound_caller(fetch_user, 123)

149

posts = bound_caller(fetch_posts, 123)

150

profile = bound_caller(fetch_profile, 123)

151

152

# Method chaining

153

result = (stamina.RetryingCaller(attempts=5)

154

.on(ConnectionError)

155

(connect_to_service, host="localhost", port=8080))

156

```

157

158

### Asynchronous Retry Caller

159

160

The `AsyncRetryingCaller` provides the same interface as `RetryingCaller` but for asynchronous functions.

161

162

```python { .api }

163

class AsyncRetryingCaller:

164

"""

165

Async version of RetryingCaller for async functions.

166

"""

167

168

def __init__(

169

self,

170

attempts: int | None = 10,

171

timeout: float | datetime.timedelta | None = 45.0,

172

wait_initial: float | datetime.timedelta = 0.1,

173

wait_max: float | datetime.timedelta = 5.0,

174

wait_jitter: float | datetime.timedelta = 1.0,

175

wait_exp_base: float = 2.0,

176

):

177

"""Initialize async retry caller with default parameters."""

178

179

async def __call__(

180

self,

181

on: ExcOrPredicate,

182

callable_: Callable[P, Awaitable[T]],

183

/,

184

*args: P.args,

185

**kwargs: P.kwargs,

186

) -> T:

187

"""

188

Async call callable with retries.

189

190

Parameters: Same as RetryingCaller.__call__ but callable_ must be async

191

192

Returns:

193

Awaited return value of the callable

194

"""

195

196

def on(self, on: ExcOrPredicate, /) -> BoundAsyncRetryingCaller:

197

"""Create bound async caller for specific exception types."""

198

```

199

200

**Usage Examples:**

201

202

```python

203

import asyncio

204

import stamina

205

import httpx

206

207

# Create async retry caller

208

async_caller = stamina.AsyncRetryingCaller(

209

attempts=3,

210

timeout=20.0,

211

wait_max=5.0

212

)

213

214

async def fetch_async(url):

215

async with httpx.AsyncClient() as client:

216

response = await client.get(url)

217

response.raise_for_status()

218

return response.json()

219

220

async def process_async(data):

221

# Simulate async processing

222

await asyncio.sleep(0.1)

223

if not data:

224

raise ValueError("Invalid data")

225

return {"processed": data}

226

227

# Use async caller

228

async def main():

229

# Call different async functions

230

data = await async_caller(httpx.HTTPError, fetch_async, "https://api.example.com/data")

231

result = await async_caller(ValueError, process_async, data)

232

233

# With bound caller

234

bound_caller = async_caller.on(httpx.HTTPError)

235

user = await bound_caller(fetch_async, "https://api.example.com/user/123")

236

237

return result

238

239

# Run async code

240

result = asyncio.run(main())

241

```

242

243

### Bound Async Retry Caller

244

245

The `BoundAsyncRetryingCaller` is the async equivalent of `BoundRetryingCaller`.

246

247

```python { .api }

248

class BoundAsyncRetryingCaller:

249

"""

250

AsyncRetryingCaller pre-bound to specific exception types.

251

252

Created by AsyncRetryingCaller.on() - do not instantiate directly.

253

"""

254

255

async def __call__(

256

self,

257

callable_: Callable[P, Awaitable[T]],

258

/,

259

*args: P.args,

260

**kwargs: P.kwargs,

261

) -> T:

262

"""Async call callable with bound exception handling."""

263

```

264

265

**Usage Examples:**

266

267

```python

268

# Bound async caller usage

269

async_caller = stamina.AsyncRetryingCaller(attempts=5)

270

http_bound = async_caller.on(httpx.HTTPError)

271

272

async def fetch_multiple_endpoints():

273

# Use same bound caller for multiple async operations

274

users = await http_bound(fetch_async, "https://api.example.com/users")

275

posts = await http_bound(fetch_async, "https://api.example.com/posts")

276

comments = await http_bound(fetch_async, "https://api.example.com/comments")

277

278

return {"users": users, "posts": posts, "comments": comments}

279

```

280

281

## Advanced Usage Patterns

282

283

### Configuration Factories

284

285

Create caller factories for different retry strategies:

286

287

```python

288

def create_http_caller(timeout=30.0):

289

"""Factory for HTTP-specific retry callers."""

290

return stamina.RetryingCaller(

291

attempts=3,

292

timeout=timeout,

293

wait_initial=0.5,

294

wait_max=5.0

295

).on(httpx.HTTPError)

296

297

def create_db_caller():

298

"""Factory for database-specific retry callers."""

299

return stamina.RetryingCaller(

300

attempts=5,

301

timeout=60.0,

302

wait_initial=1.0,

303

wait_max=30.0

304

).on(DatabaseError)

305

306

# Use factories

307

http_caller = create_http_caller(timeout=15.0)

308

db_caller = create_db_caller()

309

310

user = http_caller(fetch_user, 123)

311

records = db_caller(query_database, "SELECT * FROM users")

312

```

313

314

### Caller Composition

315

316

Combine callers for complex scenarios:

317

318

```python

319

# Different retry strategies for different operations

320

fast_caller = stamina.RetryingCaller(attempts=3, wait_max=2.0)

321

slow_caller = stamina.RetryingCaller(attempts=10, wait_max=30.0)

322

323

def robust_data_processing(data):

324

# Quick retries for validation

325

validated = fast_caller(ValueError, validate_data, data)

326

327

# Longer retries for expensive processing

328

processed = slow_caller(ProcessingError, process_data, validated)

329

330

# Quick retries for saving

331

result = fast_caller(IOError, save_result, processed)

332

333

return result

334

```

335

336

### Context-Aware Callers

337

338

Use caller state for contextual retry decisions:

339

340

```python

341

class ContextualRetryingCaller:

342

def __init__(self, base_caller):

343

self.base_caller = base_caller

344

self.attempt_counts = {}

345

346

def __call__(self, operation_id, on, callable_, *args, **kwargs):

347

# Track attempts per operation

348

self.attempt_counts[operation_id] = self.attempt_counts.get(operation_id, 0) + 1

349

350

try:

351

return self.base_caller(on, callable_, *args, **kwargs)

352

except Exception:

353

print(f"Operation {operation_id} failed after {self.attempt_counts[operation_id]} attempts")

354

raise

355

356

# Usage with context

357

contextual_caller = ContextualRetryingCaller(

358

stamina.RetryingCaller(attempts=3)

359

)

360

361

result = contextual_caller(

362

"fetch_user_123",

363

httpx.HTTPError,

364

fetch_user,

365

123

366

)

367

```