Как добавить аннотации типа python в глобальный контекст фляги g?
У меня есть декоратор, который добавляет пользователя в глобальный контекст фляги 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 в контроллерах. Как я могу этого добиться? (Я использую пирог)
4 ответа
У меня была аналогичная проблема, описанная в разделе "Проверка типов" динамически добавляемых атрибутов. Одно из решений - добавить подсказки настраиваемого типа с помощью 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
Это решение с мнением:
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
Вы можете проксировать 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()
Вы можете аннотировать атрибут класса, даже если этот класс не ваш, просто поставив после него двоеточие. Например:
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()
Вот и все.