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
pyright regression: combining @classmethod and @retry throws error. #756
The latest commit that does not have this pyright warning is 930958f . The latest working release of tenacity is 9.1.4.
Steps to Reproduce
Make a test.py file:
fromtenacityimportretry@retryasyncdefsome_func(a: int) ->None:
passclassFoo:
@staticmethod@retryasyncdefstatic_func(a: int) ->None: ...
@classmethod@retryasyncdefclass_func(cls, a: int) ->None: ...
@retryasyncdefmethod_func(self, a: int) ->None: ...
@classmethod@retrydefsync_class_func(cls, a: int) ->None: ...
@retrydefsync_func(self, a: int) ->None: ...
asyncdeftest_async() ->None:
c=Foo()
# These all work fine.awaitsome_func(1)
awaitFoo.static_func(1)
awaitc.method_func(1)
c.sync_func(1)
# pyright fails with `error: Argument missing for parameter "a" (reportCallIssue)`awaitFoo.class_func(1)
# But if we use an instance instead of the class, it works fine.awaitc.class_func(1)
# Same error with a sync function as with async.Foo.sync_class_func(1)
Run pyright test.py
Observe errors:
test.py:36:11 - error: Argument missing for parameter "a" (reportCallIssue)
test.py:42:5 - error: Argument missing for parameter "a" (reportCallIssue)
2 errors, 0 warnings, 0 informations
Thanks for the clean bisect to f4a31e1, it pointed straight at the _RetryDecorated return type.
I reproduced it on main with mypy (the strict CI config) and pyright, and a standalone callable protocol fails the same way, so neither checker applies classmethod binding to a protocol object.
Both checkers do still call the protocol's __get__, so my suggestion is to give retry an extra overload for callables with a first positional parameter that returns a sibling protocol, _RetryDecoratedMethod[S, P, R].
Its __get__ has a self-typed overload that drops the first parameter when it is type[T], which makes Foo.method(1) and Foo().method(1) resolve for classmethods, sync and async.
Instance methods also bind self precisely now, .retry, .statistics and .retry_with stay typed, and runtime behaviour is unchanged.
The trade-off is that a plain function whose first parameter is annotated type[X] and is stored on a class would type-check as if bound, which looks rare to me.
I have this locally with assert_type regression tests for classmethods, staticmethods, instance methods and plain functions, so I'm happy to open a PR if this direction works for you 🙂
neither checker applies classmethod binding to a protocol object.
Interesting that someone is already working on a fix for pyright for this. I wonder if it's more appropriate to fix this upstream on pyright and mypy than to add a workaround to tenacity? I didn't realize it was an issue with the type checkers when I filed this issue, as I'm not well-versed in how they work 😅
I'm happy to open a PR if this direction works for you
I don't have any objection to it, but I'm not a maintainer for tenacity, so I can't officially answer whether your proposed workaround would be accepted. Your proposal is a bit beyond my technical comprehension 🙂
Good question! An upstream fix would be the cleanest long-term answer, but even if pyright and mypy change how they bind classmethod over protocol objects, it'll take a while to ship and for users to pick up those versions.
Meanwhile tenacity users after 9.1.4 hit this today, and older checker versions would keep failing either way.
The overload is small, purely typing-level with no runtime change, and can be dropped once the checkers handle it.
Totally fair on the maintainer point, and thanks for the quick repro 🙂
I'll open a PR with the regression tests so @jd can weigh in on the approach there, and I'll link any upstream pyright/mypy issue in the PR description.
Activity
devtechedge commentedon Oct 14, 2026
Thanks for the clean bisect to f4a31e1, it pointed straight at the
_RetryDecoratedreturn type.I reproduced it on main with mypy (the strict CI config) and pyright, and a standalone callable protocol fails the same way, so neither checker applies
classmethodbinding to a protocol object.Both checkers do still call the protocol's
__get__, so my suggestion is to giveretryan extra overload for callables with a first positional parameter that returns a sibling protocol,_RetryDecoratedMethod[S, P, R].Its
__get__has a self-typed overload that drops the first parameter when it istype[T], which makesFoo.method(1)andFoo().method(1)resolve for classmethods, sync and async.Instance methods also bind
selfprecisely now,.retry,.statisticsand.retry_withstay typed, and runtime behaviour is unchanged.The trade-off is that a plain function whose first parameter is annotated
type[X]and is stored on a class would type-check as if bound, which looks rare to me.I have this locally with assert_type regression tests for classmethods, staticmethods, instance methods and plain functions, so I'm happy to open a PR if this direction works for you 🙂
mnakama commentedon Oct 15, 2026
Interesting that someone is already working on a fix for pyright for this. I wonder if it's more appropriate to fix this upstream on pyright and mypy than to add a workaround to tenacity? I didn't realize it was an issue with the type checkers when I filed this issue, as I'm not well-versed in how they work 😅
I don't have any objection to it, but I'm not a maintainer for tenacity, so I can't officially answer whether your proposed workaround would be accepted. Your proposal is a bit beyond my technical comprehension 🙂
devtechedge commentedon Oct 15, 2026
Good question! An upstream fix would be the cleanest long-term answer, but even if pyright and mypy change how they bind
classmethodover protocol objects, it'll take a while to ship and for users to pick up those versions.Meanwhile tenacity users after 9.1.4 hit this today, and older checker versions would keep failing either way.
The overload is small, purely typing-level with no runtime change, and can be dropped once the checkers handle it.
Totally fair on the maintainer point, and thanks for the quick repro 🙂
I'll open a PR with the regression tests so @jd can weigh in on the approach there, and I'll link any upstream pyright/mypy issue in the PR description.