backon 3.2.0__tar.gz → 3.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {backon-3.2.0 → backon-3.3.0}/PKG-INFO +1 -1
  2. {backon-3.2.0 → backon-3.3.0}/backon/_common.py +10 -15
  3. {backon-3.2.0 → backon-3.3.0}/backon/_decorator.py +5 -5
  4. {backon-3.2.0 → backon-3.3.0}/backon/_retry.py +200 -64
  5. {backon-3.2.0 → backon-3.3.0}/pyproject.toml +1 -1
  6. {backon-3.2.0 → backon-3.3.0}/LICENSE +0 -0
  7. {backon-3.2.0 → backon-3.3.0}/README.md +0 -0
  8. {backon-3.2.0 → backon-3.3.0}/backon/__init__.py +0 -0
  9. {backon-3.2.0 → backon-3.3.0}/backon/_async.py +0 -0
  10. {backon-3.2.0 → backon-3.3.0}/backon/_conditions.py +0 -0
  11. {backon-3.2.0 → backon-3.3.0}/backon/_jitter.py +0 -0
  12. {backon-3.2.0 → backon-3.3.0}/backon/_state.py +0 -0
  13. {backon-3.2.0 → backon-3.3.0}/backon/_sync.py +0 -0
  14. {backon-3.2.0 → backon-3.3.0}/backon/_typing.py +0 -0
  15. {backon-3.2.0 → backon-3.3.0}/backon/_wait_gen.py +0 -0
  16. {backon-3.2.0 → backon-3.3.0}/backon/py.typed +0 -0
  17. {backon-3.2.0 → backon-3.3.0}/backon/types.py +0 -0
  18. {backon-3.2.0 → backon-3.3.0}/tests/__init__.py +0 -0
  19. {backon-3.2.0 → backon-3.3.0}/tests/test_advanced_features.py +0 -0
  20. {backon-3.2.0 → backon-3.3.0}/tests/test_backon.py +0 -0
  21. {backon-3.2.0 → backon-3.3.0}/tests/test_backon_async.py +0 -0
  22. {backon-3.2.0 → backon-3.3.0}/tests/test_backon_predicate.py +0 -0
  23. {backon-3.2.0 → backon-3.3.0}/tests/test_backon_sync.py +0 -0
  24. {backon-3.2.0 → backon-3.3.0}/tests/test_features.py +0 -0
  25. {backon-3.2.0 → backon-3.3.0}/tests/test_jitter.py +0 -0
  26. {backon-3.2.0 → backon-3.3.0}/tests/test_retry.py +0 -0
  27. {backon-3.2.0 → backon-3.3.0}/tests/test_types.py +0 -0
  28. {backon-3.2.0 → backon-3.3.0}/tests/test_typing.py +0 -0
  29. {backon-3.2.0 → backon-3.3.0}/tests/test_wait_gen.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: backon
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Function decoration for backoff and retry
5
5
  Keywords: retry,backoff,decorators
6
6
  Author-Email: Llucs <c307lucas@gmail.com>
@@ -1,8 +1,6 @@
1
1
  import functools
2
2
  import logging
3
- import sys
4
3
  import time as time_module
5
- import traceback
6
4
 
7
5
  _logger = logging.getLogger("backon")
8
6
  _logger.addHandler(logging.NullHandler())
@@ -63,6 +61,8 @@ def _prepare_logger(logger):
63
61
  def _config_handlers(
64
62
  user_handlers, *, default_handler=None, logger=None, log_level=None
65
63
  ):
64
+ if isinstance(user_handlers, list) and logger is None:
65
+ return user_handlers
66
66
  handlers = []
67
67
  if logger is not None:
68
68
  assert log_level is not None
@@ -84,28 +84,23 @@ def _config_handlers(
84
84
 
85
85
  def _log_backoff(details, logger, log_level):
86
86
  msg = "Backing off %s(...) for %.1fs (%s)"
87
- log_args = [details["target"].__name__, details["wait"]]
88
-
89
- exc_typ, exc, _ = sys.exc_info()
87
+ exc = details.get("exception")
90
88
  if exc is not None:
91
- exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
92
- log_args.append(exc_fmt.rstrip("\n"))
89
+ exc_fmt = f"{type(exc).__name__}: {exc}"
93
90
  else:
94
- log_args.append(details["value"])
91
+ exc_fmt = details.get("value", "unknown")
92
+ log_args = [details["target"].__name__, details["wait"], exc_fmt]
95
93
  logger.log(log_level, msg, *log_args)
96
94
 
97
95
 
98
96
  def _log_giveup(details, logger, log_level):
99
97
  msg = "Giving up %s(...) after %d tries (%s)"
100
- log_args = [details["target"].__name__, details["tries"]]
101
-
102
- exc_typ, exc, _ = sys.exc_info()
98
+ exc = details.get("exception")
103
99
  if exc is not None:
104
- exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
105
- log_args.append(exc_fmt.rstrip("\n"))
100
+ exc_fmt = f"{type(exc).__name__}: {exc}"
106
101
  else:
107
- log_args.append(details["value"])
108
-
102
+ exc_fmt = details.get("value", "unknown")
103
+ log_args = [details["target"].__name__, details["tries"], exc_fmt]
109
104
  logger.log(log_level, msg, *log_args)
110
105
 
111
106
 
@@ -21,7 +21,7 @@ from backon._conditions import (
21
21
  retry_if_result,
22
22
  )
23
23
  from backon._jitter import full_jitter
24
- from backon._retry import _retry_async, _retry_sync
24
+ from backon._retry import _retry_async_inner, _retry_sync_inner
25
25
  from backon._state import RetryState
26
26
  from backon._typing import (
27
27
  P,
@@ -91,7 +91,7 @@ def on_predicate(
91
91
 
92
92
  return cast(
93
93
  R,
94
- await _retry_async(
94
+ await _retry_async_inner(
95
95
  wrapped,
96
96
  wait_gen,
97
97
  condition=condition,
@@ -121,7 +121,7 @@ def on_predicate(
121
121
 
122
122
  return cast(
123
123
  R,
124
- _retry_sync(
124
+ _retry_sync_inner(
125
125
  lambda: target(*args, **kwargs),
126
126
  wait_gen,
127
127
  condition=condition,
@@ -217,7 +217,7 @@ def on_exception(
217
217
 
218
218
  return cast(
219
219
  R,
220
- await _retry_async(
220
+ await _retry_async_inner(
221
221
  wrapped,
222
222
  wait_gen,
223
223
  condition=condition,
@@ -247,7 +247,7 @@ def on_exception(
247
247
 
248
248
  return cast(
249
249
  R,
250
- _retry_sync(
250
+ _retry_sync_inner(
251
251
  lambda: target(*args, **kwargs),
252
252
  wait_gen,
253
253
  condition=condition,
@@ -16,7 +16,6 @@ from backon._common import (
16
16
  _log_backoff,
17
17
  _log_giveup,
18
18
  _maybe_call,
19
- _next_wait,
20
19
  _now,
21
20
  _prepare_logger,
22
21
  is_enabled,
@@ -44,21 +43,19 @@ from backon._typing import (
44
43
  from backon._wait_gen import expo
45
44
 
46
45
 
47
- def _sync_call_handlers(hdlrs, details):
48
- if not hdlrs:
49
- return
50
- for hdlr in hdlrs:
51
- hdlr(details)
46
+ def _call_hdlrs(hdlrs, details):
47
+ if hdlrs:
48
+ for h in hdlrs:
49
+ h(details)
52
50
 
53
51
 
54
- async def _async_call_handlers(handlers, details):
55
- if not handlers:
56
- return
57
- for handler in handlers:
58
- if inspect.iscoroutinefunction(handler):
59
- await handler(details)
60
- else:
61
- handler(details)
52
+ async def _call_hdlrs_async(handlers, details):
53
+ if handlers:
54
+ for handler in handlers:
55
+ if inspect.iscoroutinefunction(handler):
56
+ await handler(details)
57
+ else:
58
+ handler(details)
62
59
 
63
60
 
64
61
  def _to_seconds(value: float | int | timedelta) -> float:
@@ -128,15 +125,16 @@ def _retry_loop_sync(
128
125
  wait_gen_kwargs,
129
126
  ):
130
127
  state = RetryState(target=target)
131
- state.start_time = _now()
128
+ start_time = _now()
129
+ state.start_time = start_time
132
130
  wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
133
131
 
134
132
  while True:
135
133
  state.tries += 1
136
- state.elapsed = _elapsed(state.start_time)
134
+ state.elapsed = _now() - start_time
137
135
  outcome = Attempt(tries=state.tries, elapsed=state.elapsed)
138
136
 
139
- _sync_call_handlers(on_attempt, state.to_details())
137
+ _call_hdlrs(on_attempt, state.to_details())
140
138
 
141
139
  try:
142
140
  ret = target()
@@ -145,16 +143,23 @@ def _retry_loop_sync(
145
143
  outcome.value = None
146
144
  state.outcome = outcome
147
145
  try:
148
- seconds = _next_wait(wait, None, jitter, state.elapsed, max_time)
146
+ value = wait.send(None)
147
+ if jitter is not None:
148
+ seconds = jitter(value)
149
+ else:
150
+ seconds = value
151
+ if max_time is not None:
152
+ seconds = min(seconds, max_time - state.elapsed)
149
153
  except StopIteration:
150
154
  break
151
155
  if stop(state):
152
156
  break
153
157
  details = state.to_details()
154
158
  details["wait"] = seconds
155
- _sync_call_handlers(before_sleep, details)
156
- _sync_call_handlers(on_backoff, details)
157
- sleep(seconds)
159
+ _call_hdlrs(before_sleep, details)
160
+ _call_hdlrs(on_backoff, details)
161
+ if seconds > 0:
162
+ sleep(seconds)
158
163
  continue
159
164
  except BaseException as exc:
160
165
  outcome.exception = exc
@@ -165,7 +170,7 @@ def _retry_loop_sync(
165
170
  if not condition(state):
166
171
  details = state.to_details()
167
172
  details["exception"] = exc
168
- _sync_call_handlers(on_giveup, details)
173
+ _call_hdlrs(on_giveup, details)
169
174
  if retry_error_callback is not None:
170
175
  return retry_error_callback(details)
171
176
  if raise_on_giveup:
@@ -175,7 +180,7 @@ def _retry_loop_sync(
175
180
  if stop(state):
176
181
  details = state.to_details()
177
182
  details["exception"] = exc
178
- _sync_call_handlers(on_giveup, details)
183
+ _call_hdlrs(on_giveup, details)
179
184
  if retry_error_callback is not None:
180
185
  return retry_error_callback(details)
181
186
  if raise_on_giveup:
@@ -183,11 +188,17 @@ def _retry_loop_sync(
183
188
  return None
184
189
 
185
190
  try:
186
- seconds = _next_wait(wait, exc, jitter, state.elapsed, max_time)
191
+ value = wait.send(exc)
192
+ if jitter is not None:
193
+ seconds = jitter(value)
194
+ else:
195
+ seconds = value
196
+ if max_time is not None:
197
+ seconds = min(seconds, max_time - state.elapsed)
187
198
  except StopIteration:
188
199
  details = state.to_details()
189
200
  details["exception"] = exc
190
- _sync_call_handlers(on_giveup, details)
201
+ _call_hdlrs(on_giveup, details)
191
202
  if raise_on_giveup:
192
203
  raise exc from None
193
204
  return None
@@ -195,9 +206,10 @@ def _retry_loop_sync(
195
206
  details = state.to_details()
196
207
  details["wait"] = seconds
197
208
  details["exception"] = exc
198
- _sync_call_handlers(before_sleep, details)
199
- _sync_call_handlers(on_backoff, details)
200
- sleep(seconds)
209
+ _call_hdlrs(before_sleep, details)
210
+ _call_hdlrs(on_backoff, details)
211
+ if seconds > 0:
212
+ sleep(seconds)
201
213
  else:
202
214
  outcome.value = ret
203
215
  outcome.exception = None
@@ -207,27 +219,34 @@ def _retry_loop_sync(
207
219
  if stop(state):
208
220
  details = state.to_details()
209
221
  details["value"] = ret
210
- _sync_call_handlers(on_giveup, details)
222
+ _call_hdlrs(on_giveup, details)
211
223
  return ret
212
224
 
213
225
  try:
214
- seconds = _next_wait(wait, ret, jitter, state.elapsed, max_time)
226
+ value = wait.send(ret)
227
+ if jitter is not None:
228
+ seconds = jitter(value)
229
+ else:
230
+ seconds = value
231
+ if max_time is not None:
232
+ seconds = min(seconds, max_time - state.elapsed)
215
233
  except StopIteration:
216
234
  details = state.to_details()
217
235
  details["value"] = ret
218
- _sync_call_handlers(on_giveup, details)
236
+ _call_hdlrs(on_giveup, details)
219
237
  return ret
220
238
 
221
239
  details = state.to_details()
222
240
  details["wait"] = seconds
223
241
  details["value"] = ret
224
- _sync_call_handlers(before_sleep, details)
225
- _sync_call_handlers(on_backoff, details)
226
- sleep(seconds)
242
+ _call_hdlrs(before_sleep, details)
243
+ _call_hdlrs(on_backoff, details)
244
+ if seconds > 0:
245
+ sleep(seconds)
227
246
  else:
228
247
  details = state.to_details()
229
248
  details["value"] = ret
230
- _sync_call_handlers(on_success, details)
249
+ _call_hdlrs(on_success, details)
231
250
  return ret
232
251
 
233
252
 
@@ -249,15 +268,16 @@ async def _retry_loop_async(
249
268
  wait_gen_kwargs,
250
269
  ):
251
270
  state = RetryState(target=target)
252
- state.start_time = _now()
271
+ start_time = _now()
272
+ state.start_time = start_time
253
273
  wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
254
274
 
255
275
  while True:
256
276
  state.tries += 1
257
- state.elapsed = _elapsed(state.start_time)
277
+ state.elapsed = _now() - start_time
258
278
  outcome = Attempt(tries=state.tries, elapsed=state.elapsed)
259
279
 
260
- await _async_call_handlers(on_attempt, state.to_details())
280
+ await _call_hdlrs_async(on_attempt, state.to_details())
261
281
 
262
282
  try:
263
283
  ret = await target()
@@ -266,16 +286,23 @@ async def _retry_loop_async(
266
286
  outcome.value = None
267
287
  state.outcome = outcome
268
288
  try:
269
- seconds = _next_wait(wait, None, jitter, state.elapsed, max_time)
289
+ value = wait.send(None)
290
+ if jitter is not None:
291
+ seconds = jitter(value)
292
+ else:
293
+ seconds = value
294
+ if max_time is not None:
295
+ seconds = min(seconds, max_time - state.elapsed)
270
296
  except StopIteration:
271
297
  break
272
298
  if stop(state):
273
299
  break
274
300
  details = state.to_details()
275
301
  details["wait"] = seconds
276
- await _async_call_handlers(before_sleep, details)
277
- await _async_call_handlers(on_backoff, details)
278
- await sleep(seconds)
302
+ await _call_hdlrs_async(before_sleep, details)
303
+ await _call_hdlrs_async(on_backoff, details)
304
+ if seconds > 0:
305
+ await sleep(seconds)
279
306
  continue
280
307
  except BaseException as exc:
281
308
  outcome.exception = exc
@@ -286,7 +313,7 @@ async def _retry_loop_async(
286
313
  if not condition(state):
287
314
  details = state.to_details()
288
315
  details["exception"] = exc
289
- await _async_call_handlers(on_giveup, details)
316
+ await _call_hdlrs_async(on_giveup, details)
290
317
  if retry_error_callback is not None:
291
318
  return retry_error_callback(details)
292
319
  if raise_on_giveup:
@@ -296,7 +323,7 @@ async def _retry_loop_async(
296
323
  if stop(state):
297
324
  details = state.to_details()
298
325
  details["exception"] = exc
299
- await _async_call_handlers(on_giveup, details)
326
+ await _call_hdlrs_async(on_giveup, details)
300
327
  if retry_error_callback is not None:
301
328
  return retry_error_callback(details)
302
329
  if raise_on_giveup:
@@ -304,11 +331,17 @@ async def _retry_loop_async(
304
331
  return None
305
332
 
306
333
  try:
307
- seconds = _next_wait(wait, exc, jitter, state.elapsed, max_time)
334
+ value = wait.send(exc)
335
+ if jitter is not None:
336
+ seconds = jitter(value)
337
+ else:
338
+ seconds = value
339
+ if max_time is not None:
340
+ seconds = min(seconds, max_time - state.elapsed)
308
341
  except StopIteration:
309
342
  details = state.to_details()
310
343
  details["exception"] = exc
311
- await _async_call_handlers(on_giveup, details)
344
+ await _call_hdlrs_async(on_giveup, details)
312
345
  if raise_on_giveup:
313
346
  raise exc from None
314
347
  return None
@@ -316,9 +349,10 @@ async def _retry_loop_async(
316
349
  details = state.to_details()
317
350
  details["wait"] = seconds
318
351
  details["exception"] = exc
319
- await _async_call_handlers(before_sleep, details)
320
- await _async_call_handlers(on_backoff, details)
321
- await sleep(seconds)
352
+ await _call_hdlrs_async(before_sleep, details)
353
+ await _call_hdlrs_async(on_backoff, details)
354
+ if seconds > 0:
355
+ await sleep(seconds)
322
356
  else:
323
357
  outcome.value = ret
324
358
  outcome.exception = None
@@ -328,30 +362,131 @@ async def _retry_loop_async(
328
362
  if stop(state):
329
363
  details = state.to_details()
330
364
  details["value"] = ret
331
- await _async_call_handlers(on_giveup, details)
365
+ await _call_hdlrs_async(on_giveup, details)
332
366
  return ret
333
367
 
334
368
  try:
335
- seconds = _next_wait(wait, ret, jitter, state.elapsed, max_time)
369
+ value = wait.send(ret)
370
+ if jitter is not None:
371
+ seconds = jitter(value)
372
+ else:
373
+ seconds = value
374
+ if max_time is not None:
375
+ seconds = min(seconds, max_time - state.elapsed)
336
376
  except StopIteration:
337
377
  details = state.to_details()
338
378
  details["value"] = ret
339
- await _async_call_handlers(on_giveup, details)
379
+ await _call_hdlrs_async(on_giveup, details)
340
380
  return ret
341
381
 
342
382
  details = state.to_details()
343
383
  details["wait"] = seconds
344
384
  details["value"] = ret
345
- await _async_call_handlers(before_sleep, details)
346
- await _async_call_handlers(on_backoff, details)
347
- await sleep(seconds)
385
+ await _call_hdlrs_async(before_sleep, details)
386
+ await _call_hdlrs_async(on_backoff, details)
387
+ if seconds > 0:
388
+ await sleep(seconds)
348
389
  else:
349
390
  details = state.to_details()
350
391
  details["value"] = ret
351
- await _async_call_handlers(on_success, details)
392
+ await _call_hdlrs_async(on_success, details)
352
393
  return ret
353
394
 
354
395
 
396
+ def _retry_sync_inner(
397
+ target,
398
+ wait_gen,
399
+ *,
400
+ condition=None,
401
+ stop=None,
402
+ jitter=None,
403
+ on_success=None,
404
+ on_backoff=None,
405
+ on_giveup=None,
406
+ on_attempt=None,
407
+ before_sleep=None,
408
+ sleep=None,
409
+ retry_error_callback=None,
410
+ raise_on_giveup=True,
411
+ max_time=None,
412
+ wait_gen_kwargs=None,
413
+ max_tries=None,
414
+ ):
415
+ if not is_enabled():
416
+ return target()
417
+ if wait_gen_kwargs is None:
418
+ wait_gen_kwargs = {}
419
+ if stop is None:
420
+ stop = _make_default_stop(max_tries, max_time)
421
+
422
+ _sleep = sleep or time_module.sleep
423
+
424
+ return _retry_loop_sync(
425
+ target,
426
+ wait_gen,
427
+ condition,
428
+ stop,
429
+ jitter,
430
+ on_success,
431
+ on_backoff,
432
+ on_giveup,
433
+ on_attempt,
434
+ before_sleep,
435
+ _sleep,
436
+ retry_error_callback,
437
+ raise_on_giveup,
438
+ max_time,
439
+ wait_gen_kwargs,
440
+ )
441
+
442
+
443
+ async def _retry_async_inner(
444
+ target,
445
+ wait_gen,
446
+ *,
447
+ condition=None,
448
+ stop=None,
449
+ jitter=None,
450
+ on_success=None,
451
+ on_backoff=None,
452
+ on_giveup=None,
453
+ on_attempt=None,
454
+ before_sleep=None,
455
+ sleep=None,
456
+ retry_error_callback=None,
457
+ raise_on_giveup=True,
458
+ max_time=None,
459
+ wait_gen_kwargs=None,
460
+ max_tries=None,
461
+ ):
462
+ if not is_enabled():
463
+ return await target()
464
+ if wait_gen_kwargs is None:
465
+ wait_gen_kwargs = {}
466
+ if stop is None:
467
+ stop = _make_default_stop(max_tries, max_time)
468
+
469
+ _sleep = sleep or asyncio.sleep
470
+
471
+ return await _retry_loop_async(
472
+ target,
473
+ wait_gen,
474
+ condition,
475
+ stop,
476
+ jitter,
477
+ on_success,
478
+ on_backoff,
479
+ on_giveup,
480
+ on_attempt,
481
+ before_sleep,
482
+ _sleep,
483
+ retry_error_callback,
484
+ raise_on_giveup,
485
+ max_time,
486
+ wait_gen_kwargs,
487
+ )
488
+
489
+
355
490
  def _retry_sync(
356
491
  target: Callable[..., Any],
357
492
  wait_gen: _WaitGenerator,
@@ -721,20 +856,21 @@ class Retrying:
721
856
 
722
857
  max_time = _maybe_call(self._max_time)
723
858
  try:
724
- seconds = _next_wait(
725
- self._iter_wait,
726
- exc,
727
- self._jitter,
728
- self._iter_state.elapsed,
729
- max_time,
730
- )
859
+ value = self._iter_wait.send(exc)
860
+ if self._jitter is not None:
861
+ seconds = self._jitter(value)
862
+ else:
863
+ seconds = value
864
+ if max_time is not None:
865
+ seconds = min(seconds, max_time - self._iter_state.elapsed)
731
866
  except StopIteration:
732
867
  if self._raise_on_giveup:
733
868
  raise exc from None
734
869
  raise StopIteration from None
735
870
 
736
871
  sleep_fn = self._sleep or time_module.sleep
737
- sleep_fn(seconds)
872
+ if seconds > 0:
873
+ sleep_fn(seconds)
738
874
 
739
875
  self._iter_state.tries += 1
740
876
  self._iter_state.elapsed = _elapsed(self._iter_state.start_time)
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "backon"
9
- version = "3.2.0"
9
+ version = "3.3.0"
10
10
  description = "Function decoration for backoff and retry"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes