Почему колба CLI рекомендуется вместо Flask.run?

13

В Flask 0.11 flaskбыл введен CLI. И документы, и состояние изменений это рекомендуется.

Документы по разработке сервера :

Начиная с Flask 0.11, существует несколько встроенных способов запуска сервера разработки. Лучшей из них является утилита командной строки flask, но вы также можете продолжить использовать Flask.run()метод.

Командная строка

Сценарий командной строки flask (Интерфейс командной строки) настоятельно рекомендуется для разработки, поскольку он обеспечивает превосходную перезагрузку благодаря загрузке приложения. Основное использование таково:

$ export FLASK_APP=my_application
$ export FLASK_DEBUG=1
$ flask run

Журнал изменений :

  • Добавлен flaskи flask.cliмодуль для запуска локального сервера отладки через систему CLI. Это рекомендуется по сравнению со старым flask.run()методом, поскольку он работает быстрее и надежнее из-за другой конструкции, а также заменяет Flask-Script.

До сих пор я не заметил этот «превосходный опыт перезагрузки». Я не вижу смысла использования CLI поверх пользовательского сценария.

При использовании Flask.runя бы просто написал файл Python:

#!/usr/bin/env python3
from my_app import app


if __name__ == '__main__':
    app.run(debug=True)

При использовании CLI, нужно будет указать переменные среды. В документации CLI указано, что это может быть интегрировано в activateскрипт virtualenvwrapper. Лично я считаю, что это является частью приложения и думаю, что это должно быть под контролем версий. Увы, необходим скрипт оболочки:

#!/usr/bin/env bash
export FLASK_APP=my_app:app
export FLASK_DEBUG=1

flask run

Конечно, это будет сопровождаться дополнительным скриптом bat, как только все пользователи Windows начнут сотрудничать.

Также первый вариант позволяет выполнить настройку, написанную на Python, перед запуском самого приложения.

Это позволяет, например,

  • проанализировать аргументы командной строки в Python
  • настроить ведение журнала перед запуском приложения

Похоже, они способствуют добавлению пользовательских команд. Я не понимаю, почему это лучше, чем написание простых скриптов Python, опционально доступных через точки входа.

Пример вывода журнала при использовании настроенного регистратора с использованием сценария запуска Python:

$ ./run.py 
   DEBUG 21:51:22 main.py:95) Configured logging
    INFO 21:51:22 _internal.py:87)  * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    INFO 21:51:22 _internal.py:87)  * Restarting with inotify reloader
   DEBUG 21:51:22 main.py:95) Configured logging
 WARNING 21:51:22 _internal.py:87)  * Debugger is active!
    INFO 21:51:22 _internal.py:87)  * Debugger pin code: 263-225-431
   DEBUG 21:51:25 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
   DEBUG 21:51:25 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
    INFO 21:51:25 _internal.py:87)  * Detected change in 'my_app/main.py', reloading
    INFO 21:51:26 _internal.py:87)  * Restarting with inotify reloader
   DEBUG 21:51:26 main.py:95) Configured logging
 WARNING 21:51:26 _internal.py:87)  * Debugger is active!
    INFO 21:51:26 _internal.py:87)  * Debugger pin code: 263-225-431

Пример вывода журнала при использовании сконфигурированного регистратора с использованием CLI: обратите внимание, что корневой регистратор не может быть настроен достаточно рано в процессе.

$ ./run.sh 
 * Serving Flask app "appsemble.api.main:app"
 * Forcing debug mode on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with inotify reloader
   DEBUG 21:51:33 main.py:95) Configured logging
 * Debugger is active!
 * Debugger pin code: 187-758-498
   DEBUG 21:51:34 main.py:95) Configured logging
   DEBUG 21:51:37 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
   DEBUG 21:51:37 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
 * Detected change in 'my_app/main.py', reloading
    INFO 21:51:37 _internal.py:87)  * Detected change in 'my_app/main.py', reloading
 * Restarting with inotify reloader
    INFO 21:51:38 _internal.py:87)  * Restarting with inotify reloader
 * Debugger is active!
 * Debugger pin code: 187-758-498
   DEBUG 21:51:38 main.py:95) Configured logging

Мой актуальный вопрос просто:

Почему рекомендуется использовать колбу CLI Flask.run?

Ремко Хасинг
источник

Ответы:

11

В документации по серверам разработки они утверждают, что существуют проблемы с вызовом run () и автоматической перезагрузкой кода:

Это хорошо работает для общего случая, но не очень хорошо для разработки, поэтому начиная с Flask 0.11 и далее рекомендуется метод колб. Причина этого в том, что из-за того, как работает механизм перезагрузки, существуют некоторые странные побочные эффекты (например, выполнение определенного кода дважды, иногда сбой без сообщения или смерть при возникновении ошибки синтаксиса или импорта).

Они утверждают, что CLI не страдает от этой проблемы.

Первый коммит, который, кажется, касается этой проблемы: https://github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f

И там Армин Ронахер написал:

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

Как упоминал Аарон Холл, кажется, что использование run () может быть проблематичным из-за того факта, что все объекты, которые являются экземплярами классов, определенных в заменяемых модулях, не будут восстановлены, и всякий раз, когда модуль перезагружается, модули, которые он импортирует, также не перезагружаются.

Подробная информация об этом может быть найдена для Python 3 по адресу: https://docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib.

Здесь утверждается:

Как и со всеми другими объектами в Python, старые объекты восстанавливаются только после того, как их число ссылок упадет до нуля.

Другие ссылки на старые объекты (например, имена, внешние по отношению к модулю) не привязываются к новым объектам и должны обновляться в каждом пространстве имен, где они встречаются, если это необходимо.

Когда модуль перезагружается, его словарь (содержащий глобальные переменные модуля) сохраняется. Переопределения имен переопределят старые определения, так что это обычно не проблема. Если новая версия модуля не определяет имя, которое было определено старой версией, старое определение остается.

Таким образом, создав новый процесс и убив старый, вы естественным образом удаляете все устаревшие ссылки.

Кроме того, в интерфейсе командной строки Flask используется модуль «щелчка», что позволяет очень легко добавлять пользовательские команды, но самое главное, помимо исправления ошибки перезагрузки, интерфейс командной строки предлагает стандартизированный способ запуска приложений и добавления пользовательских команд. Это звучит как очень хорошая вещь, потому что это делает знакомство с Flask более переносимым между различными командами и приложениями, вместо того, чтобы иметь несколько способов сделать одно и то же.

Кажется, это настоящий способ сделать Flask более подходящим для Zen of Python:

Должен быть один - и желательно только один - очевидный способ сделать это.

Мартин Юнгблют Шрайнер
источник
2
Вот что, я думаю, Армин подразумевает под «плохо поддерживаемым»: в Python перезагрузка модуля не перезагружает модули, которые импортирует этот модуль, и при этом не связывает имена в других модулях с указанием на старые объекты на новые из нового модуля - горячая замена нового модуля в тот же процесс проблематична. Вам гораздо лучше начинать новый процесс, когда вы хотите внести изменения в код.
Аарон Холл
Теперь, когда вы упомянули это, я вспомнил поведение, которое вы описали, спасибо за разъяснения! Я отредактирую ответ соответственно.
Мартин Юнгблют Шрайнер,
хорошо, плюс 1 за цитирование :)
Аарон Холл
Дополнение от Аарона Холла прояснило это для меня. Благодарю. :)
Ремко Хэзинг
7
Почему мы должны использовать переменную окружения FLASK_APP? Это присуще тому, как это работает? Мне любопытно, почему flask runне принимают то же самое в качестве аргумента, который облегчит новичков. Спасибо.
Джон Уилер