Considere the following code:
from typing import Callable, Any
TFunc = Callable[..., Any]
def get_authenticated_user(): return "John"
def require_auth() -> Callable[TFunc, TFunc]:
def decorator(func: TFunc) -> TFunc:
def wrapper(*args, **kwargs) -> Any:
user = get_authenticated_user()
if user is None:
raise Exception("Don't!")
return func(*args, **kwargs)
return wrapper
return decorator
@require_auth()
def foo(a: int) -> bool:
return bool(a % 2)
foo(2) # Type check OK
foo("no!") # Type check failing as intended
This piece of code is working as intended. Now imagine I want to extend this, and instead of just executing func(*args, **kwargs)
I want to inject the username in the arguments. Therefore, I modify the function signature.
from typing import Callable, Any
TFunc = Callable[..., Any]
def get_authenticated_user(): return "John"
def inject_user() -> Callable[TFunc, TFunc]:
def decorator(func: TFunc) -> TFunc:
def wrapper(*args, **kwargs) -> Any:
user = get_authenticated_user()
if user is None:
raise Exception("Don't!")
return func(*args, user, **kwargs) # <- call signature modified
return wrapper
return decorator
@inject_user()
def foo(a: int, username: str) -> bool:
print(username)
return bool(a % 2)
foo(2) # Type check OK
foo("no!") # Type check OK <---- UNEXPECTED
I can't figure out a correct way to type this. I know that on this example, decorated function and returned function should technically have the same signature (but even that is not detected).
You can't use
Callable
to say anything about additional arguments; they are not generic. Your only option is to say that your decorator takes aCallable
and that a differentCallable
is returned.In your case you can nail down the return type with a typevar:
Even then the resulting decorated
foo()
function has a typing signature ofdef (*Any, **Any) -> builtins.bool*
when you usereveal_type()
.Various proposals are currently being discussed to make
Callable
more flexible but those have not yet come to fruition. SeeCallable
to be able to specify argument names and kindsfor some examples. The last one in that list is an umbrella ticket that includes your specific usecase, the decorator that alters the callable signature: