Help with "ParamSpec is unbound" mypy error in decorator for sync + async functions #1970
-
Hi everyone, I'm currently writing a decorator that should catch errors for both async and non-async functions. While it works as expected, Here's my 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, Awaitable[RetType]]
) -> Callable[Param, Awaitable[RetType | None]]: ...
@overload
@classmethod
def catch_errors(
cls, func: Callable[Param, RetType]
) -> Callable[Param, 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: # ← mypy error here
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: # ← mypy error here
try:
return func(*args, **kwargs)
except Exception as err:
# do some error logging
return None
return wrapper The error from
Does anyone know how to fix this or why Thanks in advance! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
I'm not able to repro the error. Here's your code in the mypy playground. Perhaps you're using an outdated version of mypy? |
Beta Was this translation helpful? Give feedback.
-
You need to add annotations to your implementation signature as well, since mypy will use those annotations to check the body. Note the use of from typing import Any, 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, Awaitable[RetType]]
) -> Callable[Param, Awaitable[RetType | None]]: ...
@overload
@classmethod
def catch_errors(
cls, func: Callable[Param, RetType]
) -> Callable[Param, RetType | None]: ...
@classmethod
def catch_errors(
cls, func: Callable[Param, Any]
) -> Callable[Param, Any]:
if asyncio.iscoroutinefunction(func):
@functools.wraps(func)
async def async_wrapper(*args: Param.args, **kwargs: Param.kwargs) -> RetType | None: # ← mypy error here
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: # ← mypy error here
try:
return func(*args, **kwargs)
except Exception as err:
# do some error logging
return None
return wrapper |
Beta Was this translation helpful? Give feedback.
You need to add annotations to your implementation signature as well, since mypy will use those annotations to check the body.
Note the use of
Any
for the implementation for theCallable
return type, this reduces type safety slightly in the implementation, but if you try to use a union of the two overloads instead, you will end up with errors in other places, since mypy can't figure out the relationship between the two overloads.