I’m trying to do some possibly weird stuff with decorators and I’m struggling to get the typing right.
I have created a decorator to which you provide some metadata, and when it’s called on a function it will put that function in a container with the associated metadata and then store that container with the same name as the original function.
The functions that I store in the container are generic in their arguments and return types, and I cannot seem to get everything working together.
Here is a simple example of what I’m trying to do
from typing import Protocol, TypeVar, Callable, Generic
from dataclasses import dataclass
T = TypeVar("T", contravariant=True)
S = TypeVar("S", covariant=True)
class TestProtocol(Protocol[T, S]):
def __call__(self, test: T, *args, **kwargs) -> S:
...
@dataclass(frozen=True, slots=True)
class TestContainer(Generic[T, S]):
a: int
b: str
func: TestProtocol[T, S]
def test_decorator(a: int, b: str) -> Callable[[TestProtocol[T, S]], TestContainer[T, S]]:
def inner_decorator(func: TestProtocol[T, S]) -> TestContainer[T, S]:
return TestContainer(a=a, b=b, func=func)
return inner_decorator
@test_decorator(1, "b")
def test_func(test: int) -> str:
return str(test)
Mypy gives the following error:
mypy_test.py:26: error: Argument 1 has incompatible type "Callable[[int], str]"; expected "TestProtocol[<nothing>, <nothing>]" [arg-type]
However if I remove the *args, **kwargs
in TestProtocol.__call__
Mypy and Pyright are both happy.
But, my code requires these functions to take arbritrary *args
and **kwargs
, and I’m not sure how to make the type checkers happy in this case. This seems like a place to use ParamSpec
but I’m not sure how to use ParamSpec
together with Protocol
.
Anybody knows what I should do to make the checkers happy?