@@ -127,7 +127,6 @@ def __exit__(
127
127
128
128
129
129
class RetryKWs (TypedDict ):
130
- on : type [Exception ] | tuple [type [Exception ], ...]
131
130
attempts : int | None
132
131
timeout : float | dt .timedelta | None
133
132
wait_initial : float | dt .timedelta
@@ -148,7 +147,6 @@ class BaseRetryingCaller:
148
147
149
148
def __init__ (
150
149
self ,
151
- on : type [Exception ] | tuple [type [Exception ], ...],
152
150
attempts : int | None = 10 ,
153
151
timeout : float | dt .timedelta | None = 45.0 ,
154
152
wait_initial : float | dt .timedelta = 0.1 ,
@@ -157,7 +155,6 @@ def __init__(
157
155
wait_exp_base : float = 2.0 ,
158
156
):
159
157
self ._context_kws = {
160
- "on" : on ,
161
158
"attempts" : attempts ,
162
159
"timeout" : timeout ,
163
160
"wait_initial" : wait_initial ,
@@ -167,34 +164,105 @@ def __init__(
167
164
}
168
165
169
166
def __repr__ (self ) -> str :
170
- on = guess_name (self ._context_kws ["on" ])
171
167
kws = ", " .join (
172
168
f"{ k } ={ self ._context_kws [k ]!r} " # type: ignore[literal-required]
173
169
for k in sorted (self ._context_kws )
174
170
if k != "on"
175
171
)
176
- return f"<{ self .__class__ .__name__ } (on= { on } , { kws } )>"
172
+ return f"<{ self .__class__ .__name__ } ({ kws } )>"
177
173
178
174
179
175
class RetryingCaller (BaseRetryingCaller ):
180
176
"""
181
177
Call your callables with retries.
182
178
179
+ Arguments have the same meaning as for :func:`stamina.retry`.
180
+
183
181
Tip:
184
- Instances of ``RetryingCaller`` may be reused because they create a new
185
- :func:`retry_context` iterator on each call.
182
+ Instances of ``RetryingCaller`` may be reused because they internally
183
+ create a new :func:`retry_context` iterator on each call.
186
184
187
185
.. versionadded:: 24.2.0
188
186
"""
189
187
190
188
def __call__ (
191
- self , func : Callable [P , T ], / , * args : P .args , ** kw : P .kwargs
189
+ self ,
190
+ on : type [Exception ] | tuple [type [Exception ], ...],
191
+ callable_ : Callable [P , T ],
192
+ / ,
193
+ * args : P .args ,
194
+ ** kwargs : P .kwargs ,
192
195
) -> T :
193
- for attempt in retry_context (** self ._context_kws ):
196
+ r"""
197
+ Call ``callable_(*args, **kw)`` with retries if *on* is raised.
198
+
199
+ Args:
200
+ on: Exception(s) to retry on.
201
+
202
+ callable\_: Callable to call.
203
+
204
+ args: Positional arguments to pass to *callable_*.
205
+
206
+ kw: Keyword arguments to pass to *callable_*.
207
+ """
208
+ for attempt in retry_context (on , ** self ._context_kws ):
194
209
with attempt :
195
- return func (* args , ** kw )
210
+ return callable_ (* args , ** kwargs )
211
+
212
+ raise SystemError ("unreachable" ) # noqa: EM101
213
+
214
+ def on (
215
+ self , on : type [Exception ] | tuple [type [Exception ], ...], /
216
+ ) -> BoundRetryingCaller :
217
+ """
218
+ Create a new instance of :class:`BoundRetryingCaller` with the same
219
+ parameters, but bound to a specific exception type.
196
220
197
- raise SystemError ("unreachable" ) # pragma: no cover # noqa: EM101
221
+ .. versionadded:: 24.2.0
222
+ """
223
+ # This should be a `functools.partial`, but unfortunately it's
224
+ # impossible to provide a nicely typed API with it, so we use a
225
+ # separate class.
226
+ return BoundRetryingCaller (self , on )
227
+
228
+
229
+ class BoundRetryingCaller :
230
+ """
231
+ Same as :class:`RetryingCaller`, but pre-bound to a specific exception
232
+ type.
233
+
234
+ Caution:
235
+ Returned by :meth:`RetryingCaller.on` -- do not instantiate directly.
236
+
237
+ .. versionadded:: 24.2.0
238
+ """
239
+
240
+ __slots__ = ("_caller" , "_on" )
241
+
242
+ _caller : RetryingCaller
243
+ _on : type [Exception ] | tuple [type [Exception ], ...]
244
+
245
+ def __init__ (
246
+ self ,
247
+ caller : RetryingCaller ,
248
+ on : type [Exception ] | tuple [type [Exception ], ...],
249
+ ):
250
+ self ._caller = caller
251
+ self ._on = on
252
+
253
+ def __repr__ (self ) -> str :
254
+ return (
255
+ f"<BoundRetryingCaller({ guess_name (self ._on )} , { self ._caller !r} )>"
256
+ )
257
+
258
+ def __call__ (
259
+ self , callable_ : Callable [P , T ], / , * args : P .args , ** kwargs : P .kwargs
260
+ ) -> T :
261
+ """
262
+ Same as :func:`RetryingCaller.__call__`, except retry on the exception
263
+ that is bound to this instance.
264
+ """
265
+ return self ._caller (self ._on , callable_ , * args , ** kwargs )
198
266
199
267
200
268
class AsyncRetryingCaller (BaseRetryingCaller ):
@@ -205,13 +273,73 @@ class AsyncRetryingCaller(BaseRetryingCaller):
205
273
"""
206
274
207
275
async def __call__ (
208
- self , func : Callable [P , Awaitable [T ]], / , * args : P .args , ** kw : P .kwargs
276
+ self ,
277
+ on : type [Exception ] | tuple [type [Exception ], ...],
278
+ callable_ : Callable [P , Awaitable [T ]],
279
+ / ,
280
+ * args : P .args ,
281
+ ** kwargs : P .kwargs ,
209
282
) -> T :
210
- async for attempt in retry_context (** self ._context_kws ):
283
+ """
284
+ Same as :meth:`RetryingCaller.__call__`, but *callable_* is awaited.
285
+ """
286
+ async for attempt in retry_context (on , ** self ._context_kws ):
211
287
with attempt :
212
- return await func (* args , ** kw )
288
+ return await callable_ (* args , ** kwargs )
213
289
214
- raise SystemError ("unreachable" ) # pragma: no cover # noqa: EM101
290
+ raise SystemError ("unreachable" ) # noqa: EM101
291
+
292
+ def on (
293
+ self , on : type [Exception ] | tuple [type [Exception ], ...], /
294
+ ) -> BoundAsyncRetryingCaller :
295
+ """
296
+ Create a new instance of :class:`BoundAsyncRetryingCaller` with the
297
+ same parameters, but bound to a specific exception type.
298
+
299
+ .. versionadded:: 24.2.0
300
+ """
301
+ return BoundAsyncRetryingCaller (self , on )
302
+
303
+
304
+ class BoundAsyncRetryingCaller :
305
+ """
306
+ Same as :class:`BoundRetryingCaller`, but for async callables.
307
+
308
+ Caution:
309
+ Returned by :meth:`AsyncRetryingCaller.on` -- do not instantiate
310
+ directly.
311
+
312
+ .. versionadded:: 24.2.0
313
+ """
314
+
315
+ __slots__ = ("_caller" , "_on" )
316
+
317
+ _caller : AsyncRetryingCaller
318
+ _on : type [Exception ] | tuple [type [Exception ], ...]
319
+
320
+ def __init__ (
321
+ self ,
322
+ caller : AsyncRetryingCaller ,
323
+ on : type [Exception ] | tuple [type [Exception ], ...],
324
+ ):
325
+ self ._caller = caller
326
+ self ._on = on
327
+
328
+ def __repr__ (self ) -> str :
329
+ return f"<BoundAsyncRetryingCaller({ guess_name (self ._on )} , { self ._caller !r} )>"
330
+
331
+ async def __call__ (
332
+ self ,
333
+ callable_ : Callable [P , Awaitable [T ]],
334
+ / ,
335
+ * args : P .args ,
336
+ ** kwargs : P .kwargs ,
337
+ ) -> T :
338
+ """
339
+ Same as :func:`AsyncRetryingCaller.__call__`, except retry on the
340
+ exception that is bound to this instance.
341
+ """
342
+ return await self ._caller (self ._on , callable_ , * args , ** kwargs )
215
343
216
344
217
345
_STOP_NO_RETRY = _t .stop_after_attempt (1 )
0 commit comments