Tenacity Retry Decorator Typing And CI Contracts


Tenacity is a Python retry library that often sits around network calls, database operations, and other failure prone steps in data pipelines. Its seven recent commits focus on static contracts and repeatable CI, with no claimed change to retry behavior at runtime.

The main correction is small but precise. The _RetryDecorated protocol now declares __get__ overloads, so a type checker can model the retry wrapper as a descriptor. Before this change, an instance method wrapped with @retry still looked like an unbound class method during static analysis. Mypy and Pylance could demand an explicit self argument or reduce the result to Any or Unknown.

The descriptor typing fix does not alter the wrapper object. Tenacity already returns a real function, and Python already binds that function when it is read through an instance. The new overloads make the declared type match that existing behavior.

That distinction matters in pipeline code. Retry decorators are often placed on client, loader, and checkpoint methods. A false error on loader.write(batch) pushes teams toward ignores or broad Any annotations exactly where failure handling should remain explicit. The added case in tests/test_tenacity.py checks a decorated double method and assigns its result to int. The instance overload uses an open parameter list while preserving the return type, so it fixes the binding error without claiming more parameter precision than the protocol can prove.

Another typing test change removes Typeguard from this path. The old test passed a wrapped function to check_type with a Callable shape. At runtime, that could establish that the object was callable, but it could not verify the argument and return signature of the function object.

The replacement lets strict Mypy inspect assignments in the test suite. Both forms of the decorator must fit Callable[[int], str]. An intentionally incompatible assignment carries a narrow ignore. If the decorator ever degrades to Any, that ignore becomes unused and warn_unused_ignores turns the regression into a CI failure.

This commit also fixes a copy error in the test. The result intended to cover @retry(...) was obtained by calling the raw decorator form again. The corrected test calls both wrappers. Typeguard then disappears from pyproject.toml, removing a dependency that had already caused test breakage during its version 3 series. The result is less test machinery and a stronger contract.

The inline annotation cleanup adds None return annotations to two constructors and declares the compiled expression field in tenacity/retry.py as re.Pattern[str] | None. These are modest changes. They help strict analysis understand initialized state without changing execution.

The commit history is careful about scope. A broader draft would have replaced existing Any values in RetryCallState with more restrictive container types. The merged change preserves those public annotations and removes declarations that were already obvious from literals or typed parameters. That is the right boundary for a maintenance pass: clarify internal facts, but do not imply a tighter public API unless runtime behavior supports it.

CI now pins Mypy at 2.3.0 and Ruff at 0.16.1. This removes a common source of unexplained CI failures: a new checker or newly stable lint rule arriving without a source change. The Ruff update also applies 14 reorderings for RUF036. Those reorder union members so None appears last, with no semantic effect.

Pins can turn into stale infrastructure, so the companion Dependabot configuration matters. The new uv update entry groups Python dependency changes into one monthly pull request and uses the increase strategy. Since this repository does not commit uv.lock, automation updates constraints in pyproject.toml rather than regenerating a lock file. CI becomes reproducible between planned upgrades, while the pins still have a defined path forward.

  • Check the next release notes for the descriptor typing fix before removing local ignores from pipeline clients.

  • Watch the first monthly Python dependency update. It should preserve exact checker pins and prove that the uv configuration behaves as intended without a committed lock file.

  • Keep an eye on bound method parameter inference. The new protocol preserves the result type and removes the false self requirement, but future typing work may be able to retain a more exact instance signature.