【问题标题】:Flask route decorator type hintingFlask 路由装饰器类型提示
【发布时间】:2021-11-27 00:36:23
【问题描述】:

我有一个简单的 Flask 应用程序,我在其中添加了一个装饰器,以确保每个请求都存在特定的标头。

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 装饰器添加什么类型提示?

【问题讨论】:

    标签: python python-decorators type-hinting python-typing


    【解决方案1】:

    你的装饰器接受一个可调用的参数(任何类型),并返回一个完全相同类型的可调用参数。因此:

    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 允许原始类型通过装饰器不变。

    【讨论】:

    • 感谢您的回答,我正在使用带有 --strict 标志的 mypy,在添加您的建议后,我收到以下错误;缺少泛型“t.Callable”的类型参数,函数缺少一个或多个参数的类型注释,从声明的函数返回任何返回“_C”。
    • 您需要在绑定中明确给出 Callable[..., Any] 类型然后 -- 更新答案!
    【解决方案2】:

    如果您正在使用--strict 设置运行 Mypy,那么目前以下是最好的输入方式。这与@Samwise's answer 非常相似,但请注意TypeVar 绑定到Callable[..., Any] 而不是裸Callable(Mypy --strict 不允许任何未参数化的泛型,即使作为TypeVar 的参数) .当涉及到 requires_header 的返回类型时,我们还必须帮助 Mypy ——它无法验证我们实际上返回了我们说我们应该应该的类型回来了,所以我们用a call to typing.cast帮助Mypy。

    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 还不支持该功能,所以很遗憾还没用!

    【讨论】:

    • 哦,天哪,在输入转换返回值的装饰器时,烦人的复制+粘贴 ParamSpec 的数量会救我...
    猜你喜欢
    • 2018-06-05
    • 2012-06-19
    • 2018-04-14
    • 1970-01-01
    • 2017-09-29
    • 2019-05-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多