Repository navigation
Releases: Llucs/backon
Release list
v4.4.5
4.4.5 - 2026-08-03
- Fix
ThreadPoolExecutorleak on the exception path whenattempt_timeoutis set (#36). The executor created per attempt was only shut down on the timeout path, leaking worker threads whenever the target raised an exception._retry_loop_syncnow wraps the executor intry/finallyso_executor.shutdown(wait=False)runs on every code path (success, timeout, and exception). - Fix handler
detailsdict always containing emptyargsandkwargs(#73). The real call arguments were never threaded intoRetryState/RetryCallState, so handlers readingdetails["args"]/details["kwargs"](e.g.details["kwargs"]["query_id"]) got()/{}and raisedKeyError.args/kwargsare now plumbed from every entry point (on_exception/on_predicatesync+async, sync/async generators,Retrying.call/async_call,RetryingCaller/AsyncRetryingCaller,hedge) into the retry loop, andRetryState/RetryCallStateare constructed with them so every handler (on_attempt,on_backoff,on_giveup,on_success,before,after,before_sleep) receives the actual call arguments. - Add regression tests for both issues: executor thread leak (decorator, functional API,
RetryingCaller, success path) and handlerdetailsargs/kwargs (sync+async, generators,Retrying/RetryingCaller/AsyncRetryingCaller,on_backoff/on_giveup/on_success/on_attempt/before/after/before_sleep), including theKeyErrorreproduction from the issue.
v4.4.4
4.4.4 - 2026-07-25
- Fix
hedge()functional API not accepting arguments for the target function (#43). Addedargsandkwkeyword-only parameters tohedge(),_hedge_sync(), and_hedge_async(), forwarding them to the target via_make_hedge_target. - Fix fast path silently discarding float return values from
condition/giveup(#38). Added_is_custom_waitdetection in_retry_fast_syncand_retry_fast_async, so custom wait times are used instead of calling_next_wait(). - Add tests for both bugs.
v4.4.3
4.4.3 - 2026-07-24
- Remove dead code: 5 unreachable
if state.outcome is not Noneguards in_decide.py, 2return Truedead-code statements in_decorator.pyand_helpers.py, 1 always-trueif user_handlers is not Noneguard in_common.py. - Add
# pragma: no coverto theParamSpecstub in_typing.py(unreachable on Python 3.13+). - Achieve 100% branch coverage across all 20 source files.
- Remove duplicated tests from
test_coverage_gaps.py.
v4.4.2
4.4.2 - 2026-07-22
- Documentation: remove stale Metrics references from README. The metrics instrumentation (
MetricsCollector,PrometheusMetrics,OTelMetrics,StructlogMetrics,get_metrics_collector,set_metrics_collector) was removed as dead code in 4.3.0, but the README still listed a[Metrics](#metrics)ToC entry (broken link), advertised "Prometheus / OpenTelemetry / structlog metrics" in the Features list, and included a "Metrics | Prometheus / OTel" row in the migration table. All three references are now gone. - Documentation: fix
AGENTS.mdRelease Process step 1, which instructed to bump__version__inbackon/__init__.py. That value is read dynamically fromimportlib.metadata; onlyversioninpyproject.tomlneeds bumping. Now consistent with the Versioning section.
Note: mypy python_version stays at 3.10 even though 4.4.0 restored Python 3.9 runtime support. mypy 2.x dropped python_version = "3.9" (minimum supported target is 3.10); 3.9 compatibility is enforced via Union/Optional in module-level aliases, from __future__ import annotations in _decorator.py, and the Python 3.9 entry in the CI matrix.
Validation: All checks pass — ruff, mypy, and 563 tests.
v4.4.1
4.4.1 - 2026-07-21
- Fix
raise_on_giveup=Falsesilently swallowingKeyboardInterrupt,SystemExit, andGeneratorExit(#31). All retry loops (fast and slow, sync and async) now useexcept Exceptioninstead ofexcept BaseException, letting control-flow exceptions propagate naturally. - Fix
BreakerRetrying.call()andBreakerRetrying.async_call()ignoringhalf_open_max_callswhen the circuit breaker is inHALF_OPENstate (#26). Added the same HALF_OPEN guard thatCircuitBreaker.call()uses, preventing unlimited concurrent calls during recovery. - Add tests for KeyboardInterrupt/SystemExit/GeneratorExit propagation through retry loops.
- Add tests for BreakerRetrying HALF_OPEN state enforcement.
Validation: All checks pass — ruff, mypy, and 563 tests.
Release v4.4.0 — Python 3.9 support restored
4.4.0 - 2026-07-21
- Python 3.9 support restored. Minimum Python is now 3.9 (was 3.10).
- Replace
X | Yin module-level type aliases withUnion[X, Y]/Optional[X]. - Add
from __future__ import annotationsto_decorator.pysoX | Yin function signatures is not evaluated at runtime. - Add conditional
ParamSpecimport with a runtime stub for Python 3.9. - Unwrap
staticmethodobjects inon_predicateandon_exception(Python 3.9 doesn't allow callingstaticmethodobjects directly, fixed in 3.10+). - Relax
warn_unused_ignores = falsein mypy config to support multiple mypy versions safely. - Regenerate
pdm.lockwith target>=3.9. - Update CI matrix to include Python 3.9.
- Update ruff
target-version = "py39".
Validation: All checks pass on Python 3.9, 3.10, 3.11, 3.12, 3.13, 3.14 — ruff, mypy, and 551 tests.
v4.3.0
- Breaking: Remove dead metrics instrumentation (
MetricsCollector,PrometheusMetrics,OTelMetrics,StructlogMetrics,get_metrics_collector,set_metrics_collector). Never called by retry loops — entirely dead code. - Breaking: Remove dead ReadTheDocs config and
docs/directory. RTD was unused and referencing a non-existentdocs/conf.py. - Fix trio
AttemptTimeoutErrornever caught — unifiedexcept trio.TooSlowError/except AttemptTimeoutErrorinto single handler. - Fix mutable wait generator singletons (
expo,constant,fibo,decay, etc.) sharing internal state across uses. Wrapped in_WaitFactory. - Refactor
_decorator.pyfrom 843 to 563 lines (‑33%). Extracted_prepare_handlers(),_build_retry_kw(),_make_retry_with(). - Enable C4 (comprehensions) and RUF (ruff-specific) lint rules.
- Rewrite
CONTRIBUTING.mdto referenceAGENTS.mdas single source of truth for technical rules. - Add
ruff format --checkto the pre-commit checklist inAGENTS.md.
v4.2.5
4.2.5 - 2026-07-17
- Fix
_RetryAttempt.__exit__swallowingKeyboardInterrupt,SystemExit, andGeneratorExit(#39).__exit__now only suppressesExceptionsubclasses, lettingBaseExceptionsubclasses propagate naturally. (codevoyager-ai-dev)
What's Changed
- [codevoyager] fix: _RetryAttempt.exit no longer suppresses KeyboardInterrupt, SystemExit, GeneratorExit by @codevoyager-ai-dev in #56
New Contributors
- @codevoyager-ai-dev made their first contribution in #56
Full Changelog: v4.2.4...v4.2.5
v4.2.4
4.2.4 - 2026-07-15
- Fix
_maybe_callswallowing legitimateTypeErrorexceptions from called functions (#48). Previously allTypeErrorexceptions were caught and silenced; now only argument-mismatch errors cause the fallback to return the callable, while real bugs propagate. (fazalpsinfo-cmyk) - Fix
_Wait.__call__not accepting positional arguments (#49). Calling e.g.constant(0.05)orexpo(10)no longer raisesTypeError. Pre-configured kwargs (e.g.expo(max_value=10)) are preserved when chained with positional args. (fazalpsinfo-cmyk)
v4.2.3
Release v4.2.3 consolidates the working-tree progress since v4.1.1: [#19] fast-path contextvars, [#16] hedge crash, [#17] raise_on_giveup ignored in fast path, [#18] "stop triggered on success", [#15] composed wait generators losing kwargs, [#22] assert helpers ignoring their fn, [#20] RetryingCaller/AsyncRetryingCaller dropping most options, [#21] README private imports, [#23] stop_before_delay never stopping the loop + README claims, [#24] AGENTS.md Architecture section outdated.
All 567 tests pass; ruff / mypy clean; coverage 96.11%.
4.2.3 - 2026-07-10
- Documentation: rewrite the
AGENTS.mdArchitecturesection to match the actual repository layout (#24). The previous text pointed AI agents and contributors tobackon/_sync.py,backon/_async.py, and a non-existentbackon/_retry.pyfor retry logic; those are now deprecation stubs / non-existent files. The new section documents the_retry/subpackage (_api.py,_classes.py,_decide.py,_fast.py,_helpers.py,_inner.py,_loops.py) alongside_circuit_breaker.py,_conditions.py,_context.py,_hedging.py,_instrumentation.py,_rate_limiter.py,_state.py,_testing.py,_trio.py, and thepy.typedPEP 561 marker, and explicitly calls out_sync.py/_async.pyasDeprecationWarningstubs. Notes thattests/uses feature-oriented naming rather than a one-test_-per-source mirror.
4.2.2 - 2026-07-10
- Fix
stop_before_delay(max_delay)actually stopping the retry loop (#23)._decide_outcomenow writes the upcoming wait intostate.outcome.waitbefore invoking theStoppredicate, soelapsed + wait >= max_delayevaluates against the real upcoming wait rather than0. Verified with a wait-incrementing loop wherestop_before_delay(0.3)stops at the 3rd attempt (sum of sleeps < 0.31s). The README description is updated to: "Stop if the upcoming wait would makeelapsed + wait >= max_delay". - Documentation (#23): the README "Context manager" section now clarifies that
Retrying.__exit__/__aexit__(and theBreakerRetrying/HedgingRetryingtwins) are a scoping device and do not re-raise the final exception — user.statisticsafter the call. The Rate Limiter section now clarifies thatRateLimitErroris raised only by directRateLimiterusage; inside a retry loop exceeding the limit just throttles by sleeping. The migration section adds a note thatbackon.__version__is read fromimportlib.metadata(apip install backonis required; running from a source checkout without installing returns"0.0.0"). - The contextvars claim ("thread-safe and async-safe") is now backed by every retry configuration (the fast-path fix landed in #19 / 4.1.4 already covers it), and the operator-composition example is now backed by code (the kwarg-preservation fix landed in #15 / 4.1.5).
- Add
test_stop_before_delay_actually_stops_retry_loopandtest_stop_before_delay_predicate_based_actually_stops(tests/test_edge_cases.py) covering the exception-based and predicate-based retry paths.
4.2.1 - 2026-07-10
- Documentation: replace private-module imports in README examples with the public API (#21). The Circuit Breaker, Hedging, Metrics, Testing Utilities, and auto-detection snippets now use
from backon import ...for symbols already in__all__. The_auto_detect_collectorprivate helper was replaced withbackon.get_metrics_collector(). The Trio section and migration table now explicitly documentbackon._trioas the deliberately private, optional-dependency surface (trio helpers are intentionally not re-exported).
4.2.0 - 2026-07-10
RetryingCallerandAsyncRetryingCallernow accept and forward the full set of retry options (#20). Previously they silently droppedgiveup,predicate,condition,stop, theon_*handlers,retry_error_callback,raise_on_giveup,logger,before/after, andname. The Dynamic Backoff feature (giveup predicate / float return) and the Handlers API are now reachable through the callable caller surface, consistent withRetrying,retry(), and the decorators.- Add
TestCallerForwardsFullOptions(12 tests) totests/test_retrying_caller.pycovering giveup, predicate-based retry, on_attempt / on_giveup handlers, condition / stop composition objects, retry_error_callback, raise_on_giveup, copy preservation, and the async twins.
4.1.5 - 2026-07-10
- Fix
assert_retried()/assert_not_retried()ignoring theirfnargument (#22). Both helpers now wrap the user-suppliedfnin a counting spy, drive it throughon_exception, and assert the spy was invoked exactlyexpected_tries(resp. exactly 1) times.assert_retriedfailing whenfndoes not actually raise is now detectable instead of silently passing. - Add
tests/test_assertions.py(7 tests) covering fn invocation, the non-raising-fn failure path, non-ValueErrorexceptions, and the raising-fn path ofassert_not_retried. - Fix wait generators losing pre-configured kwargs when composed with
+,wait_combine(), orwait_chain()(#15)._Waitnow stores its original constructor args/kwargs and__call__merges them with any caller-supplied kwargs, so pre-configured instances such asbackon.expo(base=3)andbackon.constant(interval=0.2)preserve their configuration through composition and re-instantiation inside the retry loop. - Add
TestPreconfiguredKwargsPreserved(7 tests) totests/test_wait_combine.pycoveringwait_combine, the+operator,wait_chain, decorator integration, and call-time kwarg override.
4.1.4 - 2026-07-10
- Fix
is_retrying()/get_attempt_number()returningNoneinside a retry when the fast path was active (#19). The fast paths_retry_fast_syncand_retry_fast_asyncnow enter_retry_context_manager(state.tries)around thetarget()call, matching the non-fast path. The README's "contextvars — thread-safe and async-safe" guarantee now holds for every retry configuration, not only those that force the slow path. - Add
TestFastPathRetryContext(5 tests) totests/test_fast_path.pycovering sync, async, functional API, and the outside-retry default.
4.1.3 - 2026-07-10
- Fix
raise_on_giveup=Falsebeing silently ignored in the fast path (#17):_retry_fast_syncand_retry_fast_asyncnow thread theraise_on_giveupflag and returnNoneon giveup when it isFalse, matching the non-fast path (_retry_loop_*). - Fix the fast path raising
RuntimeError("stop triggered on success")when thestoppredicate fires on the same iteration where the target call succeeds but the condition still asks for a retry (#18): the fast path now returns the successfulretin that case, matching_decide_outcome/_retry_loop_*behaviour. - Add
tests/test_fast_path.pycovering both regressions (sync + async, direct_retry_fast_*calls and via decorators).
4.1.2 - 2026-07-10
- Fix
hedge()raisingTypeError: 'NoneType' object is not callablewhenever the target function raised (issue #16). The hedge helpers now build a defaultRetryConditionvia_make_default_condition(exception, giveup, predicate)before fanning out, mirroring_retry_sync/_retry_async. Hedge futures also honour the final exception by passingraise_on_giveup=Trueso that a fully-failed hedge surfaces the real error instead ofNone. - Add
tests/test_hedging.pywith sync/async/decorator/context-manager coverage forhedge(),on_hedge(), andHedgingRetrying(18 tests, 1 skipped pending #18).