How to type a decorator which also injects an attribute? #1509
-
Hello, I have a decorator which injects an attribute onto a function: from collections.abc import Callable
from functools import wraps
from typing import ParamSpec, TypeVar
_R = TypeVar("_R")
_P = ParamSpec("_P")
def mark_exempt(func: Callable[_P, _R]) -> Callable[_P, _R]:
@wraps(func)
def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _R:
return func(*args, **kwargs)
setattr(wrapped, "exempt", True)
return wrapped The signature of I would like the type system to track the type change so that my IDE autocompletes the attribute as well as the type system being able to error if the decorator is missing. My current attempt to solve this is as follows: from collections.abc import Callable
from functools import wraps
from typing import Literal, ParamSpec, Protocol, TypeVar, reveal_type
_R = TypeVar("_R", covariant=True)
_P = ParamSpec("_P")
class CallableExemptForCSRF(Protocol[_P, _R, _P, _R]):
exempt: Literal[True]
def __call__(self, *args: _P.args, **kwds: _P.kwargs) -> _R:
...
def mark_exempt(func: Callable[_P, _R]) -> CallableExemptForCSRF[_P, _R]:
@wraps(func)
def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _R:
return func(*args, **kwargs)
setattr(wrapped, "exempt", True)
return wrapped # This line errors in type checkers Pyright errors due to protocol incompatibility (exempt is not present) If I ignore the pyright/mypy errors for @mark_exempt
def a():
pass
reveal_type(a.exempt)
# Revealed type is "Literal[True]" I don't think a # type:ignore is the way to go here. I could cast to the protocol but I tend to avoid casting as I prefer not to bypass the typechecker. An alternative is marking the protocol as runtime_checkable and adding How would I type this correctly without errors? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
You're modifying the value in a way that's not type safe, so you'd need to use a Code sample in pyright playground from functools import wraps
from typing import Callable, Literal, ParamSpec, Protocol, TypeVar, cast
_R = TypeVar("_R", covariant=True)
_P = ParamSpec("_P")
class CallableExemptForCSRF(Protocol[_P, _R]):
exempt: Literal[True]
def __call__(self, *args: _P.args, **kwds: _P.kwargs) -> _R:
...
def mark_exempt(func: Callable[_P, _R]) -> CallableExemptForCSRF[_P, _R]:
@wraps(func)
def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _R:
return func(*args, **kwargs)
setattr(wrapped, "exempt", True)
return cast(CallableExemptForCSRF[_P, _R], wrapped) |
Beta Was this translation helpful? Give feedback.
You're modifying the value in a way that's not type safe, so you'd need to use a
cast
call.Code sample in pyright playground