如何向 Flask 全局上下文 g 添加 Python 类型注释?

15

我有一个装饰器,它可以将用户添加到Flask全局上下文g中:

class User:
    def __init__(self, user_data) -> None:
        self.username: str = user_data["username"]
        self.email: str = user_data["email"]

def login_required(f):
    @wraps(f)
    def wrap(*args, **kwargs):
        user_data = get_user_data()
        user = User(user_data)
        g.user = User(user_data)

        return f(*args, **kwargs)

    return wrap
我希望在访问控制器中的g.user时能知道g.user的类型(用户类型)。我该如何实现?(我正在使用pyright)。

我希望在访问控制器中的g.user时能知道g.user的类型(用户类型)。我该如何实现?(我正在使用pyright)。

4个回答

15

我在Typechecking dynamically added attributes中遇到了类似的问题。其中一种解决方案是使用typing.TYPE_CHECKING添加自定义类型提示:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from flask.ctx import _AppCtxGlobals

    class MyGlobals(_AppCtxGlobals):
        user: 'User'

    g = MyGlobals()
else:
    from flask import g

现在举个例子。

reveal_type(g.user)

会发出

note: Revealed type is 'myapp.User'

如果自定义类型应在多个模块中重复使用,则可以为 flask 引入部分存根。存根的位置取决于类型检查器,例如 mypyMYPY_PATH 环境变量中读取自定义存根,pyright 在项目根目录中查找 typings 目录等。以下是一个部分存根示例:

# _typeshed/flask/__init__.pyi

from typing import Any
from flask.ctx import _AppCtxGlobals
from models import User


def __getattr__(name: str) -> Any: ...  # incomplete


class MyGlobals(_AppCtxGlobals):
    user: User
    def __getattr__(self, name: str) -> Any: ...  # incomplete


g: MyGlobals

4
这是一个带有观点的解决方案: flask.g 是一种神奇的技术,与服务器实现紧密联系。在我看来,应将其使用保持在最小限度内。
我已经创建了一个管理 g 的文件,这使我可以键入它。
    # request_context.py
    from flask import g
    from somewhere import User
    
    def set_user(user: User) -> None:
       g.user = user
    
    def get_user() -> User:
       # you could validate here that the user exists
       return g.user

然后在您的代码中:

    # yourcode.py
    import request_context
    
    class User:
        ...
    
    def login_required(f):
        @wraps(f)
        def wrap(*args, **kwargs):
            user_data = get_user_data()
            user = User(user_data)
            request_context.set_user(User(user_data))
    
            return f(*args, **kwargs)
    
        return wrap


3
您可以代理g对象。考虑以下实现:
import flask


class User:
    ...


class _g:

    user: User
    # Add type hints for other attributes
    # ...

    def __getattr__(self, key):
        return getattr(flask.g, key)


g = _g()


非常简单的实现,做了我想要的一切。谢谢! - Jesse Reich

-2

你可以在类上注释一个属性,即使那个类不是你的,只需在其后加上一个冒号。例如:

g.user: User

就是这样。既然它在任何地方都是有效的,我会将它放在你的代码顶部:

from functools import wraps

from flask import Flask, g

app = Flask(__name__)


class User:
    def __init__(self, user_data) -> None:
        self.username: str = user_data["username"]
        self.email: str = user_data["email"]


# Annotate the g.user attribute
g.user: User


def login_required(f):
    @wraps(f)
    def wrap(*args, **kwargs):
        g.user = User({'username': 'wile-e-coyote',
                       'email': 'coyote@localhost'})

        return f(*args, **kwargs)

    return wrap


@app.route('/')
@login_required
def hello_world():
    return f'Hello, {g.user.email}'


if __name__ == '__main__':
    app.run()

就是这样。


2
当我这样做时,我会得到以下的mypy错误:无法在非self属性的赋值中声明类型。 - axwell
考虑到 mypy 被认为是参考实现,我认为回答能够与 mypy 兼容非常重要。如果这不应该发生,那么我猜这是 mypy 中的一个 bug,应该向 mypy 报告。 - Teymour
非常不幸,这个在mypy中不起作用。PyCharm也无法识别这个结构。@Ken Kinder,你是怎么做到的呢?你使用的是什么IDE? - MEE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接