Flask 路由装饰器类型提示
Flask route decorator type hinting
我有一个简单的 Flask 应用程序,我在其中添加了一个装饰器以确保特定的 header 出现在每个请求中。
import functools
from http import HTTPStatus
import flask
from flask.typing import ResponseReturnType
app = flask.Flask(__name__)
# What type hints should be added to this?
def requires_header(func):
@functools.wraps(func)
def check_headers(*args, **kwargs):
if not flask.request.headers.get("X-Foo"):
flask.abort(HTTPStatus.NOT_FOUND)
return func(*args, **kwargs)
return check_headers
@app.route("/", methods=["GET"])
@requires_header
def root() -> ResponseReturnType:
return flask.jsonify(success=True)
if __name__ == "__main__":
flask.run(host=0.0.0.0, port=3000)
我应该向 requires_header
装饰器添加什么类型的提示?
您的装饰器接受一个可调用参数(任何类型),并且 returns 一个完全相同类型的可调用参数。因此:
from typing import Any, Callable, TypeVar
_C = TypeVar("_C", bound=Callable[..., Any])
def requires_header(func: _C) -> _C:
...
请注意,这与执行以下操作不同:
def requires_header(func: Callable) -> Callable:
...
因为这不能保证装饰函数与原始函数具有相同的类型(它实际上变成了两个 不同的 Callable[..., Any]
类型,这会破坏类型检查任何调用该函数的东西)。使用 TypeVar
允许原始类型不变地通过装饰器。
如果您是运行 Mypy 并设置了--strict
,以下是目前 的最佳输入方式。这与 , 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)
上面的解决方案显然有它的缺陷,但是,主要是我们不得不使用的次数Any
,一般应该尽量少用。 Python 3.10 有 introduced a new feature 专门用于帮助键入装饰器,ParamSpec
。不幸的是,Mypy 还不支持这个功能,但是当它支持时,我们将能够像这样输入你的装饰器:
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)
可能使用 ParamSpec
也将消除装饰器最后一行中对 cast
的需要——但这很难说,因为 Mypy 尚不支持这个功能,所以遗憾的是它还不能用!
我有一个简单的 Flask 应用程序,我在其中添加了一个装饰器以确保特定的 header 出现在每个请求中。
import functools
from http import HTTPStatus
import flask
from flask.typing import ResponseReturnType
app = flask.Flask(__name__)
# What type hints should be added to this?
def requires_header(func):
@functools.wraps(func)
def check_headers(*args, **kwargs):
if not flask.request.headers.get("X-Foo"):
flask.abort(HTTPStatus.NOT_FOUND)
return func(*args, **kwargs)
return check_headers
@app.route("/", methods=["GET"])
@requires_header
def root() -> ResponseReturnType:
return flask.jsonify(success=True)
if __name__ == "__main__":
flask.run(host=0.0.0.0, port=3000)
我应该向 requires_header
装饰器添加什么类型的提示?
您的装饰器接受一个可调用参数(任何类型),并且 returns 一个完全相同类型的可调用参数。因此:
from typing import Any, Callable, TypeVar
_C = TypeVar("_C", bound=Callable[..., Any])
def requires_header(func: _C) -> _C:
...
请注意,这与执行以下操作不同:
def requires_header(func: Callable) -> Callable:
...
因为这不能保证装饰函数与原始函数具有相同的类型(它实际上变成了两个 不同的 Callable[..., Any]
类型,这会破坏类型检查任何调用该函数的东西)。使用 TypeVar
允许原始类型不变地通过装饰器。
如果您是运行 Mypy 并设置了--strict
,以下是目前 的最佳输入方式。这与 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)
上面的解决方案显然有它的缺陷,但是,主要是我们不得不使用的次数Any
,一般应该尽量少用。 Python 3.10 有 introduced a new feature 专门用于帮助键入装饰器,ParamSpec
。不幸的是,Mypy 还不支持这个功能,但是当它支持时,我们将能够像这样输入你的装饰器:
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)
可能使用 ParamSpec
也将消除装饰器最后一行中对 cast
的需要——但这很难说,因为 Mypy 尚不支持这个功能,所以遗憾的是它还不能用!