如何将 python 类型注释添加到烧瓶全局上下文 g?
Posted
技术标签:
【中文标题】如何将 python 类型注释添加到烧瓶全局上下文 g?【英文标题】:How can I add python type annotations to the flask global context 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)
【问题讨论】:
【参考方案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
引入部分存根。存根的位置取决于类型检查器,例如mypy
从MYPY_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?以上是关于如何将 python 类型注释添加到烧瓶全局上下文 g?的主要内容,如果未能解决你的问题,请参考以下文章