How Can I Add Python Type Annotations To The Flask Global Context G?
Solution 1:
I had a similar issue described in Typechecking dynamically added attributes. One solution is to add the custom type hints using typing.TYPE_CHECKING
:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from flask.ctx import _AppCtxGlobals
classMyGlobals(_AppCtxGlobals):
user: 'User'
g = MyGlobals()
else:
from flask import g
Now e.g.
reveal_type(g.user)
will emit
note: Revealed typeis'myapp.User'
If the custom types should be reused in multiple modules, you can introduce a partial stub for flask
. The location of the stubs is dependent on the type checker, e.g. 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:
# _typeshed/flask/__init__.pyi
from typing import Any
from flask.ctx import _AppCtxGlobals
from models import User
def__getattr__(name: str) -> Any: ... # incompleteclassMyGlobals(_AppCtxGlobals):user: User
def__getattr__(self, name: str) -> Any: ... # incompleteg: MyGlobals
Solution 2:
This is a solution with an opinion:
flask.g
is magic and is tied really hard to the server implementation. IMO, its usage should be kept contained and minimal.
I have created a file to manage g, which allowed me to type it
# request_context.pyfrom flask import g
from somewhere import User
defset_user(user: User) -> None:
g.user = user
defget_user() -> User:
# you could validate here that the user existsreturn g.user
and then in your code:
# yourcode.pyimport request_context
classUser:
...
deflogin_required(f):
@wraps(f)defwrap(*args, **kwargs):
user_data = get_user_data()
user = User(user_data)
request_context.set_user(User(user_data))
return f(*args, **kwargs)
return wrap
Solution 3:
You could proxy the g
object. Consider the following implementation:
import flask
classUser:
...
class_g:
user: User
# Add type hints for other attributes# ...def__getattr__(self, key):
returngetattr(flask.g, key)
g = _g()
Solution 4:
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__)
classUser:
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
deflogin_required(f):
@wraps(f)defwrap(*args, **kwargs):
g.user = User({'username': 'wile-e-coyote',
'email': 'coyote@localhost'})
return f(*args, **kwargs)
return wrap
@app.route('/')@login_requireddefhello_world():
returnf'Hello, {g.user.email}'if __name__ == '__main__':
app.run()
That's it.
Post a Comment for "How Can I Add Python Type Annotations To The Flask Global Context G?"