Mypy Error with Decorator Handling Both Sync and Async Functions #1930
Answered
by
Daverball
FelixSiegel
asked this question in
Q&A
|
Hi everyone, I have a somewhat complex decorator setup, and I'm running into a Mypy type-checking issue when using it on async functions. What the Decorator Does
Here’s the implementation: from typing import Callable, ParamSpec, TypeVar, Awaitable, overload
import asyncio
import functools
Param = ParamSpec("Param")
RetType = TypeVar("RetType")
class Logger:
@overload
@classmethod
def catch_errors(
cls, func: Callable[Param, RetType]
) -> Callable[Param, RetType | None]: ...
@overload
@classmethod
def catch_errors(
cls, func: Callable[Param, Awaitable[RetType]]
) -> Callable[Param, Awaitable[RetType | None]]: ...
@classmethod
def catch_errors(cls, func):
if asyncio.iscoroutinefunction(func):
@functools.wraps(func)
async def async_wrapper(*args: Param.args, **kwargs: Param.kwargs) -> RetType | None:
try:
return await func(*args, **kwargs)
except Exception as err:
# do some error logging
return None
return async_wrapper
else:
@functools.wraps(func)
def wrapper(*args: Param.args, **kwargs: Param.kwargs) -> RetType | None:
try:
return func(*args, **kwargs)
except Exception as err:
# do some error logging
return None
return wrapperThe Problem When I decorate an async function like this: @Logger.catch_errors
async def foo() -> int:
return 1And then call it like this: var = await foo()Mypy gives me the following error: What I Tried
Question How can I fix this type issue so that Mypy correctly understands the return type when decorating async functions? Any insights would be greatly appreciated! |
Answered by
Daverball
Feb 22, 2025
Replies: 1 comment 1 reply
|
You need to swap the two overloads, the non-async overload is broader than the async one and will match all async functions as well. So you always end up with |
1 reply
Answer selected by
FelixSiegel
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You need to swap the two overloads, the non-async overload is broader than the async one and will match all async functions as well. So you always end up with
RetType | Noneas the return type, which in case of your async function will beCoroutine[Any, Any, int] | None.