Here’s an MRE:
import functools
from collections.abc import Callable
from typing import Literal, reveal_type
type Decorator[**P, T] = Callable[[Callable[P, T]], Callable[P, T]]
def decorator_factory[**P, T](text: str, method: Literal["upper", "lower"] = "upper") -> Decorator[P, T]:
def decorator(func: Callable[P, T]) -> Callable[P, T]:
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
match method:
case "upper":
print(text.upper())
case "lower":
print(text.lower())
return func(*args, **kwargs)
return wrapper
return decorator
@decorator_factory("hello", method = "upper")
def foo(a: int, b: str) -> bool:
return bool(a) and bool(b)
reveal_type(foo)
Type checkers disagree on this.
mypy doesn’t like it:
main.py:20: error: Argument 1 has incompatible type "Callable[[int, str], bool]"; expected "Callable[[VarArg(Never), KwArg(Never)], Never]" [arg-type]
main.py:23: note: Revealed type is "def (*Never, **Never) -> Never"
pyright likes it:
Type of "foo" is "(a: int, b: str) -> bool"
pyrefly doesn’t like it:
INFO 23:12-17: revealed type: (ParamSpec(Unknown)) -> Unknown [reveal-type]
ty says @todo
so I’m leaving it out.