Repository navigation
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).