refactor: drop always-true truthiness checks and enable truthy-bool - #679
Conversation
|
This pull request is part of a Mergify stack:
|
05deb5e to
7229e61
Compare
5a33f88 to
1897498
Compare
Revision history
|
Merge Queue Status
This pull request spent 1 minute 55 seconds in the queue, including 29 seconds running CI. Required conditions to merge
ReasonPull 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. HintYou 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. 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
7229e61 to
220001e
Compare
Merge Queue Status
This pull request spent 10 seconds in the queue, including 1 second running CI. Required conditions to merge
|
) 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
…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
truthy-boolreports objects tested for truthiness that implement neither__bool__nor__len__, and so can only ever be true. Three sites:BaseRetrying._run_waitandAsyncRetrying._run_waitboth guarded thewait call with
if self.wait:.waitis typedWaitBaseTand defaults toa
wait_none()instance, so it is never falsy and thesleep = 0.0branchhas been dead since 17aefd9 -- a leftover from when the surrounding code
still used
if self.after is not None:style guards. Callself.waitunconditionally.
if tornado:guarded the optional import in two places. mypy only eversees the
trybranch, so it resolves the name to the module and reads thetest as always-true. Compute
_HAS_TORNADOonce and branch on thatinstead; this also keeps
tornado.genfully typed, which annotating thename as
ModuleType | Nonewould have thrown away.