Как я могу добавить аннотации типа 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 в контроллерах. Как я могу этого добиться? (Я использую пирог)


person Chris St Pierre    schedule 08.02.2020    source источник


Ответы (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
person hoefling    schedule 21.06.2020

Это решение с мнением: 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

person Mr. Nun.    schedule 09.02.2021

Вы можете проксировать объект 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()

person Abdurrahman Talaat    schedule 18.06.2020

Вы можете аннотировать атрибут класса, даже если этот класс не ваш, просто поставив после него двоеточие. Например:

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

Вот и все.

person Ken Kinder    schedule 16.06.2020
comment
Когда я это делаю, я получаю следующую ошибку mypy: Тип не может быть объявлен в назначении несамостоятельному атрибуту - person axwell; 18.06.2020
comment
Учитывая, что mypy заявлена ​​как эталонная реализация, я думаю, что очень важно, чтобы ответ работал с mypy. Если этого не должно происходить, я думаю, это ошибка в mypy, о которой следует сообщить mypy. - person Teymour Aldridge; 05.08.2020