Skip to content

refactor: drop always-true truthiness checks and enable truthy-bool - #679

Merged
mergify[bot] merged 1 commit into
mainfrom
devs/jd/chore/mypy-strictness/drop-always-true-truthiness-checks-enable-truthy--cb9981f6
Aug 5, 2026
Merged

refactor: drop always-true truthiness checks and enable truthy-bool#679
mergify[bot] merged 1 commit into
mainfrom
devs/jd/chore/mypy-strictness/drop-always-true-truthiness-checks-enable-truthy--cb9981f6

Conversation

@jd

@jd jd commented Aug 5, 2026

Copy link
Copy Markdown
Owner

truthy-bool reports objects tested for truthiness that implement neither
__bool__ nor __len__, and so can only ever be true. Three sites:

BaseRetrying._run_wait and AsyncRetrying._run_wait both guarded the
wait call with if self.wait:. wait is typed WaitBaseT and defaults to
a wait_none() instance, so it is never falsy and the sleep = 0.0 branch
has been dead since 17aefd9 -- a leftover from when the surrounding code
still used if self.after is not None: style guards. Call self.wait
unconditionally.

if tornado: guarded the optional import in two places. mypy only ever
sees the try branch, so it resolves the name to the module and reads the
test as always-true. Compute _HAS_TORNADO once and branch on that
instead; this also keeps tornado.gen fully typed, which annotating the
name as ModuleType | None would have thrown away.

@jd

jd commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

This pull request is part of a Mergify stack:

# Pull Request Link
1 refactor: drop always-true truthiness checks and enable truthy-bool #679 👈
2 refactor: declare BaseAction's REPR_FIELDS and NAME as ClassVar #680
3 test: fix possibly-undefined and deprecated call sites #681
4 refactor: mark overridden methods with @OverRide #682

@jd
jd marked this pull request as ready for review August 5, 2026 15:28
@jd
jd force-pushed the devs/jd/chore/mypy-strictness/drop-always-true-truthiness-checks-enable-truthy--cb9981f6 branch from 05deb5e to 7229e61 Compare August 5, 2026 15:39
@jd
jd force-pushed the devs/jd/chore/mypy-strictness/enable-strictness-options-strict-leaves-off--9eee43a7 branch from 5a33f88 to 1897498 Compare August 5, 2026 15:39
@jd

jd commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Revision history

# Type Changes Reason Date
1 initial 05deb5e 2026-08-05 15:39 UTC
2 rebase 05deb5e → 7229e61 (rebase only) 2026-08-05 15:39 UTC
3 rebase 7229e61 → 220001e (rebase only) 2026-08-05 15:47 UTC

@mergify

mergify Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

This pull request spent 1 minute 55 seconds in the queue, including 29 seconds running CI.

Required conditions to merge

Reason

Pull request #679 has been dequeued

GitHub refused to merge the pull request. Pull Request has merge conflicts. This is usually enforced by a branch protection or ruleset rule.

Hint

You should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it.
If you do update this pull request, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

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

`truthy-bool` reports objects tested for truthiness that implement neither
`__bool__` nor `__len__`, and so can only ever be true. Three sites:

`BaseRetrying._run_wait` and `AsyncRetrying._run_wait` both guarded the
wait call with `if self.wait:`. `wait` is typed `WaitBaseT` and defaults to
a `wait_none()` instance, so it is never falsy and the `sleep = 0.0` branch
has been dead since 17aefd9 -- a leftover from when the surrounding code
still used `if self.after is not None:` style guards. Call `self.wait`
unconditionally.

`if tornado:` guarded the optional import in two places. mypy only ever
sees the `try` branch, so it resolves the name to the module and reads the
test as always-true. Compute `_HAS_TORNADO` once and branch on that
instead; this also keeps `tornado.gen` fully typed, which annotating the
name as `ModuleType | None` would have thrown away.

Change-Id: Icb9981f6797707e070dc2423015f2fbf6c94e4c4
@jd
jd force-pushed the devs/jd/chore/mypy-strictness/drop-always-true-truthiness-checks-enable-truthy--cb9981f6 branch from 7229e61 to 220001e Compare August 5, 2026 15:47
@mergify mergify Bot removed the dequeued label Aug 5, 2026
@mergify

mergify Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-08-05 15:48 UTC · Rule: default · triggered by rule autoqueue
  • Checks skipped · PR is already up-to-date
  • Merged2026-08-05 15:48 UTC · at 220001e35912cf438a14e5d811f560b63caa17bb · squash

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

Required conditions to merge

@mergify
mergify Bot merged commit e6ef84a into main Aug 5, 2026
9 checks passed
@mergify mergify Bot added the queued label Aug 5, 2026
@mergify
mergify Bot deleted the devs/jd/chore/mypy-strictness/drop-always-true-truthiness-checks-enable-truthy--cb9981f6 branch August 5, 2026 15:48
@mergify mergify Bot removed the queued label Aug 5, 2026
mergify Bot pushed a commit that referenced this pull request Aug 6, 2026
)

Three runtime regressions from the mypy-strictness stack (#679, #677), all
A/B verified against a2af454. None were caught by the test suite, and all
three only fire once a retry actually happens, so happy-path callers see
nothing until production.

1. `Retrying(wait=None)`, `wait=0` and `wait=sum([])` now raise
   `TypeError: 'NoneType' object is not callable` from inside `iter()`.
   #679 removed the `if self.wait:` guard on the grounds that `truthy-bool`
   proved it always true -- but that only holds for the *declared* type.
   Untyped callers pass `None`/`0` to mean "no wait", and `sum([])` over an
   empty strategy list returns the int 0. The `TypeError` is raised outside
   the attempt's try/except, so it escapes uncaught and destroys the caller's
   real exception. Restore the guard with a local `truthy-bool` suppression
   explaining why the "impossible" branch is reachable. `before`, `after`,
   `before_sleep` and `retry_error_callback` all kept their `is not None`
   guards; `wait` was the only one dropped.

2. `plain_callable + wait_strategy` now raises `TypeError`. `WaitBaseT`
   admits plain callables, and a function has no `__add__`, so this went
   through `wait_base.__radd__` -- which #677 narrowed to `int`. Widen it to
   `WaitBaseT | int` and build the `wait_combine` again. `5 + strategy` is
   still rejected, now via `NotImplemented` rather than a combination that
   fails later. The parameter stays `int` rather than `Literal[0]` because
   typeshed's `sum()` protocol requires `__radd__(x: int)`; narrowing it
   would make every `sum()` over strategies need a `type: ignore`.

3. `wait_combine.__call__` passed the state as `retry_state=`, which crashes
   on any `WaitBaseT` callable whose parameter has another name. Pass it
   positionally, as `BaseRetrying._run_wait` already does.

Adds regression tests for all three plus the async path -- the original
change shipped with no test covering any of them.

Change-Id: Ia40c32a22cdac09cbfba4280fb0a7f0c2cb7b7e0
mergify Bot pushed a commit that referenced this pull request Aug 6, 2026
…696)

#679 replaced `if tornado:` with a `_HAS_TORNADO` bool computed once at
import, to stop `truthy-bool` flagging the module object as always true.
But only one of the two call sites was converted: the `retry()` decorator
still dereferences the live `tornado` global right after testing the
snapshot. The two desync as soon as anything reassigns the global.

A/B verified against a2af454: `tenacity.tornado = None` followed by any
`@tenacity.retry` decoration returned normally before, and now raises
`AttributeError: 'NoneType' object has no attribute 'gen'`. Nulling the
module global is the standard way a downstream suite exercises the
non-tornado path without uninstalling tornado.

Replace the constant with a `_has_tornado()` function so both call sites
read the live global and cannot drift apart. As a bonus this needs no
`redundant-expr` suppression: the check reads as a plain return rather than
the left operand of an `and`.

Change-Id: I6f0fbb9cad3d4e46599b0cae1b5b60378742fd1f
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.

1 participant