【问题标题】:How can I add python type annotations to the flask global context g?如何将 python 类型注释添加到烧瓶全局上下文 g?
【发布时间】:2020-05-24 03:42:44
【问题描述】:

我有一个装饰器,可以将用户添加到烧瓶全局上下文 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)

【问题讨论】:

    标签: python flask mypy python-typing pyright


    【解决方案1】:

    我遇到了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
    

    【讨论】:

      【解决方案2】:

      这是一个有意见的解决方案: flask.g 很神奇,并且与服务器实现密切相关。 IMO,它的使用应该被控制和最小化。

      我创建了一个文件来管理 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()
        
        

        【讨论】:

        • 非常简单的实现,可以满足我的所有需求。谢谢!
        【解决方案4】:

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

        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()
        

        就是这样。

        【讨论】:

        • 当我这样做时,我得到以下 mypy 错误:不能在分配给非自我属性时声明类型
        • 鉴于 mypy 被声明为参考实现,我认为答案与 mypy 一起使用非常重要。如果这不应该发生,那么我猜这是 mypy 中的一个错误,应该报告给 mypy。
        • 很遗憾,这不适用于 mypy。 PyCharm 也不识别此构造。 @Ken Kinder,你是怎么做到的?您使用的是什么 IDE?
        猜你喜欢
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多