If you're running Mypy with the --strict setting, the following is the best way to type this at present. This is very similar to @Samwise's answer, but note that the TypeVar is bound to Callable[..., Any] rather than a bare Callable (Mypy --strict will not allow any unparameterised generics, even as arguments to a TypeVar). We also have to help Mypy out when it comes to the return type of requires_header — it can't verify that we're actually returning the type we say we should be returning, so we help Mypy out with a call to typing.cast.
import functools
from http import HTTPS
from typing import Callable, TypeVar, cast, Any
import flask
C = TypeVar('C', bound=Callable[..., Any])
def requires_header(func: C) -> C:
@functools.wraps(func)
def check_headers(*args: Any, **kwargs: Any) -> Any:
if not flask.request.headers.get("X-Foo"):
flask.abort(HTTPStatus.NOT_FOUND)
return func(*args, **kwargs)
return cast(C, check_headers)
The above solution obviously has its flaws, however, mainly the number of times we've had to use Any, which should generally be used as little as possible. Python 3.10 has introduced a new feature specifically to help with typing decorators, ParamSpec. Mypy unfortunately does not yet support this feature, but when it does, we will be able to type your decorator like this instead:
import functools
from http import HTTPS
from typing import Callable, TypeVar, cast, ParamSpec
import flask
P = ParamSpec('P')
R = TypeVar('R')
def requires_header(func: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(func)
def check_headers(*args: P.args, **kwargs: P.kwargs) -> R:
if not flask.request.headers.get("X-Foo"):
flask.abort(HTTPStatus.NOT_FOUND)
return func(*args, **kwargs)
return cast(Callable[P, R], check_headers)
It may be that using ParamSpec will also do away with the need for the cast in the last line of the decorator — but it's difficult to tell, since Mypy doesn't yet support the feature, so it's sadly not yet usable!