Skip to content

fix: respect enabled=False in iterator protocols (__iter__ / __aiter__)#643

Merged
mergify[bot] merged 1 commit into
jd:mainfrom
gaoflow:fix/enabled-false-iter
Jun 25, 2026
Merged

fix: respect enabled=False in iterator protocols (__iter__ / __aiter__)#643
mergify[bot] merged 1 commit into
jd:mainfrom
gaoflow:fix/enabled-false-iter

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

When Retrying(enabled=False) is used with the context-manager iterator pattern — for attempt in retrying: or async for attempt in retrying: — the enabled flag is silently ignored and the full retry machinery runs (multiple attempts, stop/wait evaluated). This is inconsistent with the wraps()-based path, which already handles enabled=False correctly by running the function exactly once and propagating any exception directly.

Reproducer:

import tenacity

call_count = 0
retrying = tenacity.Retrying(enabled=False, stop=tenacity.stop_after_attempt(5))
try:
    for attempt in retrying:
        with attempt:
            call_count += 1
            raise ValueError("fail")
except tenacity.RetryError:
    # WRONG: call_count == 5, and a RetryError is raised instead of ValueError
    print(f"call_count={call_count}")  # prints 5, should be 1

Fix

  • BaseRetrying.__iter__: when not self.enabled, yield one AttemptManager, then re-raise the stored exception if the attempt failed — mirroring wraps().
  • AsyncRetrying.__aiter__ / __anext__: same semantics via a one-shot done-flag.

Four regression tests are added (sync failure, sync success, async failure, async success).

This pull request was prepared with the assistance of AI, under my direction and review.

When Retrying(enabled=False) was used with the context-manager iterator
pattern (`for attempt in retrying` / `async for attempt in retrying`),
the `enabled` flag was silently ignored and the full retry machinery ran
(multiple attempts, stop/wait evaluated) instead of executing the body
exactly once and propagating any exception directly.

The `wraps()`-based path already handled `enabled=False` correctly, so
this patch brings the iterator API in line with it:
- BaseRetrying.__iter__: if not enabled, yield one AttemptManager and
  re-raise the stored exception if the attempt failed.
- AsyncRetrying.__aiter__ / __anext__: same semantics via a one-shot
  flag that stops after the first yielded AttemptManager.

Four regression tests are added (two sync, two async) covering the
failure and success cases for each protocol.
@mergify

mergify Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Queued — the merge queue status continues in this comment ↓.

@mergify

mergify Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-06-25 07:39 UTC · Rule: default · triggered by rule autoqueue
  • Checks skipped · PR is already up-to-date
  • Merged2026-06-25 07:39 UTC · at d9da50047ea9df4a62da614f84cf033f46f31dd1 · squash

This pull request spent 8 seconds in the queue, including 1 second running CI.

Required conditions to merge

@mergify mergify Bot merged commit 868c60b into jd:main Jun 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants