from collections.abc import Callable
from functools import wraps
def my_decorator[T, **P](fn: Callable[P, T]) -> Callable[P, T]:
# ... your logic
@wraps(fn)
def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
# ... more logic
return fn(*args, **kwargs)
return wrapped
@my_decorator
def foo(x: int, y: str) -> bool: ...
reveal_type(foo) # Type of "foo" is "(x: int, y: str) -> bool"
# which means it does what we expect:
foo(1, 'hi') # ok
foo('oops') # type error
Note the python 3.12 generics
[T, **P]
instead of needingT = TypeVar("T")
andP = ParamSpec("P")
if you already knew about this you probably don't need to be here...
Often people use ...
instead of a ParamSpec
, which means you lose the parameter type hinting:
def my_decorator[T](fn: Callable[..., T]) -> Callable[..., T]:
# ... your logic
@wraps(fn)
def wrapped(*args: Any, **kwargs: Any) -> T: # ❌ now we lost our types
# ... more logic
return fn(*args, **kwargs)
return wrapped
# use it
@my_decorator
def foo(x: int, y: str) -> bool: ...
reveal_type(foo) # Type of "foo" is "(...) -> bool"
# which means we have no idea what it's params are:
foo(1, 'hi') # ok
foo('oops') # also ok, but it shouldn't be!