angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Загадочный Flask Application Context, мои вопросы и ответы

Контекст приложения инициализируется, толкается и выскакивает, впервые пользовательские вещи могут быть запутанными. Пора начинать читать.

30 января 2020
В Flask
post main image
https://unsplash.com/@donovan_valdivia

Когда вы начинаете с Flask , вы читаете немного о Application Context, TL;DR. Не знаю, как вы, но я, конечно, не до конца вас понял. Что такое приложение, что такое current_app, в чем разница, вы просто начинаете программировать свое приложение. Все время в фоновом режиме возникает эта странная шумиха: что же такое Application Context ... Затем в определенный момент при использовании класса, который вы инстанцировали на вашей фабрике create_app() вы столкнулись с этой ошибкой:

RuntimeError: working outside of application context

ВТФ! Затем вы начинаете читать, но, конечно, не слишком много, TL;DR снова. Ладно, решение в том, чтобы поставить "с" перед этим, и ты хорош:

with app_context(app):
    ...

Становится раздражающе

Но медленно это становится все более и более раздражающим. Например, я инстанцирую объект во время создания приложения в create_app(). У этого объекта есть методы с протоколирующими утверждениями типа:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

Но если вызвать этот метод из Blueprint, то я получу сообщение об ошибке. Теперь я должен использовать операцию протоколирования с current_app:

     current_app.logger.debug(fname  +  ': message, var = {}'.format(var))

Это сбивает с толку

Но это может быть и потому, что я не очень опытный программист Python Flask . В create_app() я прикрепляю логгер к объекту приложения:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

но в той же create_app() мы также прикрепляем события типа @before_request. В @before_request можно использовать и то, и другое:

    app.logger.debug(fname  +  ': message, var = {}'.format(var))

и

     current_app.logger.debug(fname  +  ': message, var = {}'.format(var))

Зачем? Я понимаю, что оба работают, но оба дадут один и тот же результат? В @before_request current_app (вероятно) является нажатием Application Context. Но правда ли это? Также я искал способ хранения данных в Application Context. В документации сказано:

"Хранение данных": Application Context - хорошее место для хранения общих данных во время запроса или команды CLI. Flask предоставляет для этой цели объект g. Это простой объект пространства имен, который имеет то же самое время жизни, что и Application Context'.

Обратите внимание, что там написано: УВЕРЖДАЮТ просьбу. Также сказано использовать объект g. В любом случае, пора начинать читать и делать тесты. Я попытался ответить на ряд вопросов и подумал записать их, чтобы никогда не забыть. Надеюсь, ответы верны ... :-( Кстати, в моем приложении я использую заводской шаблон:

def  create_app(config_name):

    ...

    @app.before_request
    def  before_request():
        ...

    return app

Вопрос: Уничтожен ли отодвинутый запрос Application Context после запроса?

Короткий ответ: Да.

Чем больше ты об этом читаешь, тем больше путаницы. Например, из документации Flask :

Application Context создается и при необходимости уничтожается. Когда приложение Flask начинает обрабатывать запрос, оно толкает Application Context и контекст запроса. Когда запрос заканчивается, всплывает контекст запроса, затем Application Context. Обычно Application Context будет иметь такое же время жизни, как и запрос".

Посмотрим на это предложение: ' Application Context создается и уничтожается по мере необходимости'. Что именно означает это "необходимо"?

В документации сказано, что Application Context уничтожается после запроса, но это не так, или так? Чтобы проверить это, я добавил переменную к толкнутому Application Context в какой-нибудь функции просмотра, например:

def home():
    fname = 'home'
    ...
    # test to check if config can be accessed and is maintained between requests
    if 'SOME_PARAM_KEY' not in  current_app.config:
         current_app.logger.debug(fname  +  ': SOME_PARAM_KEY not in  current_app.config')
        # add it now
         current_app.config['SOME_PARAM_KEY'] = 'some_param_value'
    else:		
         current_app.logger.debug(fname  +  ': SOME_PARAM_KEY in  current_app.config,  current_app.config[SOME_PARAM] = {}'.format(current_app.config['SOME_PARAM_KEY']))

При первом запросе SOME_PARAM_KEY не находится в конфиге, мы распечатываем его, а затем устанавливаем. Следующие запросы показывают, что SOME_PARAM_KEY находится в конфиге, и конфиг держит правильное значение для SOME_PARAM_KEY. Это означает, что Application Context не уничтожается, а вместо этого, похоже, что изменяется только ссылка на Application Context .

Вот что не так: вышеизложенное было сделано с Flask в режиме DEBUG. Если вы сделаете точно такой же тест в режиме ПРОДУКЦИИ, вы НИКОГДА не найдете SOME_PARAM_KEY в current_app.config при следующем запросе. Итак, предупреждаем, в каком состоянии вы используете Flask. В режиме DEBUG оказывается, что Flask сохраняет Application Context, очевидно, для отладки.

Вопрос: можете ли вы прикрепить объект базы данных во время создания приложения и получить доступ к нему позже в ваших чертежах?

Короткий ответ: нет.

С доступом к нему позже я имею в виду: по последующим запросам. Вы должны быть очень осторожны с тем, что вы прикрепляете к Application Context во время создания приложения. Если вы прикрепили объект базы данных SQLAlchemy во время создания приложения, например, если вы прикрепили объект базы данных SQLAlchemy во время создания приложения.

    app.languages = db.session.query(Language).all()

Затем во время первого запроса Вы сможете получить доступ к языкам в Blueprint типа:

    for language in  current_app.languages:
        ...

Но затем помните, что сессия уничтожается (удаляется) во время дубляжа:

    @app.teardown_appcontext
    def teardown_db(resp_or_exc):
         current_app.db.session.remove()

При следующем запросе, если вы попытаетесь получить доступ к такому объекту, вы получите сообщение об ошибке:

(DetachedInstanceError('Instance <Setting at 0x7f4466739d90> is not bound to a Session; attribute refresh operation cannot proceed'),)

Этого следовало ожидать, но я попал в него.

Вопрос: 'всплывает Application Context' означает ли это, что Application Context все еще доступен?

Короткий ответ: нет.

Из того же самого текста, что и выше: 'Когда запрос заканчивается, всплывает контекст запроса, затем Application Context'. Итак, мы говорим о папе. Это может означать две вещи:

  1. Application Context выскакивает в синее небо.
  2. Когда они говорят о Application Context, на самом деле они означают указатель на Application Context

Почему это так важно? Потому что в первом случае это означает, что Application Context доступен только для чтения, а во втором случае Application Context между запросами не уничтожается. См. также ответ в предыдущем вопросе.

Вопрос: следует ли держать глобальный объект вне объекта приложения?

Короткий ответ: зависит.

Объект логгера

Сначала давайте посмотрим на лесозаготовки. Здесь мы прикрепляем логгер к объекту приложения.

def  create_app(config_name):

    ...
    # set app log handler level
    app_log_handler.setLevel(app_log_level)
    # set app logger
    app.logger.setLevel(app_log_level)
    app.logger.addHandler(app_log_handler)
    ...


    return app

А затем в Чертежах мы можем использовать логгер следующим образом:

from flask import  current_app

def user_list(page_number):
    fname = 'user_list'
     current_app.logger.debug(fname  +  '()')
    ...

Логгер инициализируется во время создания приложения и прикрепляется к объекту приложения и прекрасно работает при его использовании в функциях просмотра Blueprint.

Объект Базы данных

Для Flask-SQLAlchemy и других расширений предлагается другой метод. Здесь мы сохраняем объект SQLAlchemy вне объекта приложения. Во время создания приложения мы вызываем init_app(app) для конфигурирования и подготовки объекта SQLAlchemy .

from flask_sqlalchemy import  SQLAlchemy
db =  SQLAlchemy()


def  create_app(config_name):

    app =  Flask()
    ...
    # set up
    db.init_app(app)
    ...

    return app

Тогда мы можем использовать это в функции просмотра blueprint , что-то вроде того:

from my_app import db

def list_users:
    users = db.session.query(User).all()

Конечно же, в объекте SQLAlchemy происходит какая-то магия. Но здесь, в отличие от логгера, объект базы данных находится вне объекта приложения.

Вопрос: Application Context доступен только для чтения?

Короткий ответ: зависит.

Во время запроса вы можете прикрепить его, прочитать. Но в следующий раз, когда все это исчезнет.

Вопрос: является ли Application Context уникальным для каждого потока?

Короткий ответ: да.

Из документации:

Когда приложение Flask начинает обрабатывать запрос, оно выталкивает контекст запроса, который также выталкивает The Application Context".

Означает ли это, что путаница с Application Context, используя current_app, может повлиять на другие потоки? Мне все еще не ясно.

Что делать?

Я составил для себя список правил о Application Context:

  1. Никогда не храните данные / объекты в Application Context во время создания приложения.
  2. Никогда не прикрепляйте данные / объекты к Application Context во время запроса.
  3. Хранить глобальные объекты вне Application Context
  4. Используйте g вместо current_app для прикрепления данных/объектов, которые будут использоваться во время запроса.
  5. Используйте приложение только в create_app(), но в случае, если обработчики, прикрепленные в create_app(), всегда используйте current_app.
  6. Повсюду используйте current_app.

Это означает, например, что если у вас есть данные, которые являются глобальными для всех запросов, а не в конфигурации Flask , то вы должны иметь эти данные, например, где-нибудь в файловой системе или во внешнем кэше, а затем каждый раз, в начале запроса, вы должны загружать эти данные и прикреплять их к объекту g. Первый момент, когда вы можете это сделать, находится в обработчике @before_request . Поэтому @before_request - это место для прикрепления ваших данных и других вещей, таких как сессия базы данных, которая должна быть доступна во время всех запросов. Это означает, что у вас нет доступа к БД в обработчике события @app.url_value_preproprocessor, но это не так уж и важно.

Резюме

Среди прочего, я искал способ хранения данных, например, конфигурации приложения, в Application Context , а затем где-то изменить это, сделав изменения доступными для всех потоков. Я думал, что это можно сделать, но мы не можем получить доступ к inital Application Context после запуска приложения. Меня обманули в режиме DEBUG. Этот текст написан человеком, мной, который написал свое первое приложение Flask , так что некоторые утверждения могут быть некорректными. Однажды я надеюсь лучше понять Application Context . До этого дня я буду использовать Application Context очень консервативно.

Ссылки / кредиты

Demystifying Flask’s Application Context
https://hackingandslacking.com/demystifying-flasks-application-context-c7bd31a53817

Documention for "The Application Context" is confusing #1151
https://github.com/pallets/flask/issues/1151

Making an object persist between requests and available to the whole app
https://www.reddit.com/r/flask/comments/9kenzp/making_an_object_persist_between_requests_and/

number of threads in flask
https://stackoverflow.com/questions/59320537/number-of-threads-in-flask

The Application Context
https://flask.palletsprojects.com/en/1.1.x/appcontext/

Understand Flask (---)
http://songroger.win/understand-flask-s1/

What is the purpose of Flask's context stacks?
https://stackoverflow.com/questions/20036520/what-is-the-purpose-of-flasks-context-stacks

Подробнее

Flask

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.