[英]How can I add python type annotations to the flask global context g?
I have a decorator which adds a user onto the flask global context g:我有一个装饰器,可以将用户添加到 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
I want the type (User) of g.user to be known when I access g.user in the controllers.我希望在控制器中访问 g.user 时知道 g.user 的类型(用户)。 How can I achieve this?我怎样才能做到这一点? (I am using pyright) (我正在使用pyright)
I had a similar issue described in Typechecking dynamically added attributes .我在Typechecking dynamic added attributes 中描述了一个类似的问题。 One solution is to add the custom type hints using typing.TYPE_CHECKING
:一种解决方案是使用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
Now eg现在例如
reveal_type(g.user)
will emit会发出
note: Revealed type is 'myapp.User'
If the custom types should be reused in multiple modules, you can introduce a partial stub for flask
.如果自定义类型应该在多个模块中重用,您可以为flask
引入部分存根。 The location of the stubs is dependent on the type checker, eg mypy
reads custom stubs from the MYPY_PATH
environment variable, pyright
looks for a typings
directory in the project root dir etc. Example of a partial stub:的短截线的位置是依赖于类型检查器,例如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
This is a solution with an opinion: flask.g
is magic and is tied really hard to the server implementation.这是一个有意见的解决方案: flask.g
很神奇,并且与服务器实现非常紧密地联系在一起。 IMO, its usage should be kept contained and minimal. IMO,它的使用应该保持在最小范围内。
I have created a file to manage g, which allowed me to type it我创建了一个文件来管理 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
and then in your code:然后在你的代码中:
# 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
You could proxy the g
object.您可以代理g
对象。 Consider the following implementation:考虑以下实现:
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()
You can annotate an attribute on a class, even if that class isn't yours, simply with a colon after it.您可以注释一个类的属性,即使该类不是您的,只需在其后加一个冒号即可。 For example:例如:
g.user: User
That's it.就是这样。 Since it's presumably valid everywhere, I would put it at the top of your code:由于它可能在任何地方都有效,我会将它放在代码的顶部:
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()
That's it.就是这样。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.