You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE REQUEST] warn when retried code is not idempotent #504
in advance: I've looked at the past issues and PRs in this repo, looking for terms like "stateful" and "idempotent" and did not see anything related to my issue. Sorry if I missed it. Also I've had a quick glance at the doc but don't know it inside and out.
The below request might be out of scope for tenacity, because, as will be seen below, the issue is due to a bad pattern in the code I was looking at.
But I'll still share what happened to me today, and maybe that can give rise to a nice feature for tenacity.
I'll start with the result: tedious debugging because the traceback was not showing the actual, relevant issue.
The reason is that the retried code was not idempotent, specifically the decorated method deleted a class attribute, which can work without error at most once, while the nb of retries was 3.
Below is a MRE:
# python==3.12.7, tenacity==9.0.0fromtenacityimportstop_after_attempt, retryclassC:
def__init__(self):
self.a=""@retry(stop=stop_after_attempt(3))deff(self):
delself.a# represents a non idempotent action1/0# represents the actual prod errorC().f() # AttributeError: 'C' object has no attribute 'a'
In a Machine Learning context, del is quite common to clean up memory and so this is a real use case.
One way to avoid this is to decorate the minimal necessary code and avoid the above pattern, or use features like retry_if_exception_type but I'm wondering if it would be possible for tenacity to issue a warning log whenever there are > 1 error types encountered across all the retries
I tested this on Python 3.12.14 with both Tenacity 9.0.0 and current main (32ac68f). It logs the first ZeroDivisionError with its traceback, then the intermediate AttributeError. The final RetryError still represents the last AttributeError; adding reraise=True would raise that last exception directly, not recover the first one.
One limitation: before_sleep only runs when another retry is scheduled, so it does not log the final failed attempt. If you need to examine every failure accepted by the retry predicate, including the exhausted attempt, a custom after callback can inspect retry_state.outcome.exception().
This gives a workaround for the debugging problem, rather than automatically warning only when exception types change. A change of exception type alone does not establish that an operation is non-idempotent, and repeated exceptions of the same type do not establish that it is safe to retry. Keeping side-effecting cleanup outside the retried section and retrying only the intended transient failures remains the safer fix.
Activity
Master-Norna commentedon Oct 13, 2026
The first exception can be preserved in the logs using the existing
before_sleep_logcallback, without changing the retry policy:I tested this on Python 3.12.14 with both Tenacity 9.0.0 and current
main(32ac68f). It logs the firstZeroDivisionErrorwith its traceback, then the intermediateAttributeError. The finalRetryErrorstill represents the lastAttributeError; addingreraise=Truewould raise that last exception directly, not recover the first one.One limitation:
before_sleeponly runs when another retry is scheduled, so it does not log the final failed attempt. If you need to examine every failure accepted by the retry predicate, including the exhausted attempt, a customaftercallback can inspectretry_state.outcome.exception().This gives a workaround for the debugging problem, rather than automatically warning only when exception types change. A change of exception type alone does not establish that an operation is non-idempotent, and repeated exceptions of the same type do not establish that it is safe to retry. Keeping side-effecting cleanup outside the retried section and retrying only the intended transient failures remains the safer fix.