@@ -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,35 +164,101 @@ 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
183
179
Tip:
184
- Instances of ``RetryingCaller`` may be reused because they create a new
185
- :func:`retry_context` iterator on each call.
180
+ Instances of ``RetryingCaller`` may be reused because they internally
181
+ create a new :func:`retry_context` iterator on each call.
186
182
187
183
.. versionadded:: 24.2.0
188
184
"""
189
185
190
186
def __call__ (
191
- self , func : Callable [P , T ], / , * args : P .args , ** kw : P .kwargs
187
+ self ,
188
+ on : type [Exception ] | tuple [type [Exception ], ...],
189
+ callable_ : Callable [P , T ],
190
+ / ,
191
+ * args : P .args ,
192
+ ** kw : P .kwargs ,
192
193
) -> T :
193
- for attempt in retry_context (** self ._context_kws ):
194
+ r"""
195
+ Call ``callable_(*args, **kw)`` with retries if *on* is raised.
196
+
197
+ Args:
198
+ on: Exception(s) to retry on.
199
+
200
+ callable\_: Callable to call.
201
+
202
+ args: Positional arguments to pass to *callable_*.
203
+
204
+ kw: Keyword arguments to pass to *callable_*.
205
+ """
206
+ for attempt in retry_context (on , ** self ._context_kws ):
194
207
with attempt :
195
- return func (* args , ** kw )
208
+ return callable_ (* args , ** kw )
196
209
197
210
raise SystemError ("unreachable" ) # pragma: no cover # noqa: EM101
198
211
212
+ def on (
213
+ self , on : type [Exception ] | tuple [type [Exception ], ...], /
214
+ ) -> BoundRetryingCaller :
215
+ """
216
+ Create a new instance of :class:`BoundRetryingCaller` with the same
217
+ parameters, but bound to a specific exception type.
218
+
219
+ .. versionadded:: 24.2.0
220
+ """
221
+ return BoundRetryingCaller (self , on )
222
+
223
+
224
+ class BoundRetryingCaller :
225
+ """
226
+ Same as :class:`RetryingCaller`, but pre-bound to a specific exception
227
+ type.
228
+
229
+ Caution:
230
+ Returned by :meth:`RetryingCaller.on` -- do not instantiate directly.
231
+
232
+ .. versionadded:: 24.2.0
233
+ """
234
+
235
+ __slots__ = ("_caller" , "_on" )
236
+
237
+ _caller : RetryingCaller
238
+ _on : type [Exception ] | tuple [type [Exception ], ...]
239
+
240
+ def __init__ (
241
+ self ,
242
+ caller : RetryingCaller ,
243
+ on : type [Exception ] | tuple [type [Exception ], ...],
244
+ ):
245
+ self ._caller = caller
246
+ self ._on = on
247
+
248
+ def __repr__ (self ) -> str :
249
+ return (
250
+ f"<BoundRetryingCaller({ guess_name (self ._on )} , { self ._caller !r} )>"
251
+ )
252
+
253
+ def __call__ (
254
+ self , callable_ : Callable [P , T ], / , * args : P .args , ** kw : P .kwargs
255
+ ) -> T :
256
+ """
257
+ Same as :func:`RetryingCaller.__call__`, except retry on the exception
258
+ that is bound to this instance.
259
+ """
260
+ return self ._caller (self ._on , callable_ , * args , ** kw )
261
+
199
262
200
263
class AsyncRetryingCaller (BaseRetryingCaller ):
201
264
"""
@@ -205,14 +268,74 @@ class AsyncRetryingCaller(BaseRetryingCaller):
205
268
"""
206
269
207
270
async def __call__ (
208
- self , func : Callable [P , Awaitable [T ]], / , * args : P .args , ** kw : P .kwargs
271
+ self ,
272
+ on : type [Exception ] | tuple [type [Exception ], ...],
273
+ callable_ : Callable [P , Awaitable [T ]],
274
+ / ,
275
+ * args : P .args ,
276
+ ** kw : P .kwargs ,
209
277
) -> T :
210
- async for attempt in retry_context (** self ._context_kws ):
278
+ """
279
+ Same as :meth:`RetryingCaller.__call__`, but *callable_* is awaited.
280
+ """
281
+ async for attempt in retry_context (on , ** self ._context_kws ):
211
282
with attempt :
212
- return await func (* args , ** kw )
283
+ return await callable_ (* args , ** kw )
213
284
214
285
raise SystemError ("unreachable" ) # pragma: no cover # noqa: EM101
215
286
287
+ def on (
288
+ self , on : type [Exception ] | tuple [type [Exception ], ...], /
289
+ ) -> BoundAsyncRetryingCaller :
290
+ """
291
+ Create a new instance of :class:`BoundAsyncRetryingCaller` with the
292
+ same parameters, but bound to a specific exception type.
293
+
294
+ .. versionadded:: 24.2.0
295
+ """
296
+ return BoundAsyncRetryingCaller (self , on )
297
+
298
+
299
+ class BoundAsyncRetryingCaller :
300
+ """
301
+ Same as :class:`BoundRetryingCaller`, but for async callables.
302
+
303
+ Caution:
304
+ Returned by :meth:`AsyncRetryingCaller.on` -- do not instantiate
305
+ directly.
306
+
307
+ .. versionadded:: 24.2.0
308
+ """
309
+
310
+ __slots__ = ("_caller" , "_on" )
311
+
312
+ _caller : AsyncRetryingCaller
313
+ _on : type [Exception ] | tuple [type [Exception ], ...]
314
+
315
+ def __init__ (
316
+ self ,
317
+ caller : AsyncRetryingCaller ,
318
+ on : type [Exception ] | tuple [type [Exception ], ...],
319
+ ):
320
+ self ._caller = caller
321
+ self ._on = on
322
+
323
+ def __repr__ (self ) -> str :
324
+ return f"<BoundAsyncRetryingCaller({ guess_name (self ._on )} , { self ._caller !r} )>"
325
+
326
+ async def __call__ (
327
+ self ,
328
+ callable_ : Callable [P , Awaitable [T ]],
329
+ / ,
330
+ * args : P .args ,
331
+ ** kw : P .kwargs ,
332
+ ) -> T :
333
+ """
334
+ Same as :func:`AsyncRetryingCaller.__call__`, except retry on the
335
+ exception that is bound to this instance.
336
+ """
337
+ return await self ._caller (self ._on , callable_ , * args , ** kw )
338
+
216
339
217
340
_STOP_NO_RETRY = _t .stop_after_attempt (1 )
218
341
0 commit comments