Por que a CLI do flask é recomendada sobre o Flask.run?

13

No frasco 0.11, uma flaskCLI foi introduzida. Tanto os documentos quanto o estado do registro de alterações são recomendados.

Documentos do Development Server :

A partir do Flask 0.11, existem várias maneiras internas de executar um servidor de desenvolvimento. O melhor é o utilitário de linha de comando do balão, mas você também pode continuar usando o Flask.run()método

Linha de comando

O script da linha de comando do balão (Command Line Interface) é altamente recomendado para desenvolvimento, pois fornece uma experiência de recarga superior devido à forma como carrega o aplicativo. O uso básico é assim:

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

Changelog :

  • Adicionado flaske o flask.climódulo para iniciar o servidor de depuração local por meio do sistema CLI do clique. Isso é recomendado em relação ao flask.run()método antigo , pois funciona mais rápido e mais confiável devido a um design diferente e também substitui Flask-Script.

Até agora, eu não percebi essa "experiência de recarga superior". Não consigo entender o motivo de usar a CLI em um script personalizado.

Se estiver usando Flask.run, eu simplesmente escreveria um arquivo python:

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


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

Se você estiver usando a CLI, será necessário especificar variáveis ​​de ambiente. Na CLI, os documentos afirmam que isso pode ser integrado ao activatescript do virtualenvwrapper. Pessoalmente, considero que isso faz parte do aplicativo e acho que deveria estar sob controle de versão. Infelizmente, é necessário um script de shell:

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

flask run

Obviamente, isso será acompanhado por um script bat adicional assim que qualquer usuário do Windows começar a colaborar.

Além disso, a primeira opção permite a configuração escrita em Python antes de iniciar o aplicativo real.

Isso permite, por exemplo,

  • analisar argumentos de linha de comando em Python
  • configurar o log antes de executar o aplicativo

Eles parecem promover que é possível adicionar comandos personalizados. Não vejo por que isso é melhor do que escrever scripts Python simples, opcionalmente expostos através de pontos de entrada.

Exemplo de saída de log ao usar um criador de logs configurado usando o script de execução 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

Exemplo de saída de log ao usar um agente configurado usando a CLI :, observe que o agente raiz não pôde ser configurado com antecedência suficiente no processo.

$ ./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

Minha pergunta real é simplesmente:

Por que a CLI do balão é recomendada Flask.run?

Remco Haszing
fonte

Respostas:

11

Nos documentos do servidor de desenvolvimento, eles afirmam que há problemas ao chamar run () e recarregar automaticamente o código:

Isso funciona bem para o caso comum, mas não funciona bem para o desenvolvimento, e é por isso que a partir do Flask 0.11 em diante o método do balão é recomendado. A razão para isso é que, devido à maneira como o mecanismo de recarga funciona, existem alguns efeitos colaterais bizarros (como executar certo código duas vezes, às vezes travando sem mensagem ou morrendo quando ocorre um erro de sintaxe ou de importação).

Eles afirmam que a CLI não sofre com esse problema.

O primeiro commit que parece abordar esse problema é o seguinte: https://github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f

E lá Armin Ronacher escreveu:

Não é recomendável usar esta função para desenvolvimento com recarregamento automático, pois isso é muito suportado. Em vez disso, você deve usar o suporte flaskdo script da linha de comando runserver.

Conforme mencionado por Aaron Hall, parece que o uso de run () pode ser problemático devido ao fato de que todos os objetos que são instâncias de classes definidas nos módulos que estão sendo substituídos não serão restabelecidos e, sempre que um módulo for recarregado, o módulos importados também não são recarregados.

Os detalhes sobre isso podem ser encontrados no Python 3 em: https://docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib

Afirma:

Como em todos os outros objetos em Python, os objetos antigos são recuperados somente depois que suas contagens de referência caem para zero.

Outras referências aos objetos antigos (como nomes externos ao módulo) não são recuperadas para se referir aos novos objetos e devem ser atualizadas em cada espaço de nome em que ocorrem, se desejado.

Quando um módulo é recarregado, seu dicionário (contendo as variáveis ​​globais do módulo) é mantido. Redefinições de nomes substituirão as definições antigas, portanto isso geralmente não é um problema. Se a nova versão de um módulo não definir um nome que foi definido pela versão antiga, a definição antiga permanecerá.

Assim, criando um novo processo e matando o antigo, você naturalmente elimina todas as referências obsoletas.

Além disso, a CLI do Flask usa o módulo 'click', facilitando a adição de comandos personalizados, mas o mais importante é que, além de corrigir o erro de recarregamento, a CLI oferece uma maneira padronizada de executar aplicativos e adicionar comandos personalizados. Isso parece uma coisa muito boa, porque torna a familiaridade com o Flask mais transferível entre equipes e aplicativos diferentes, em vez de ter várias maneiras de fazer a mesma coisa.

Parece uma maneira genuína de tornar o Flask mais de acordo com o Zen do Python:

Deve haver uma - e preferencialmente apenas uma - maneira óbvia de fazê-lo.

Martin Jungblut Schreiner
fonte
2
Aqui está o que eu acho que Armin significa "mal suportado": No Python, recarregar um módulo não recarrega os módulos que esse módulo importa, nem reassocia nomes em outros módulos de apontar para objetos antigos para novos do novo módulo - tão quente trocar um novo módulo para o mesmo processo é problemático. Você está muito melhor iniciando um novo processo quando deseja fazer uma alteração no código.
Aaron Hall
Agora que você mencionou, eu me lembro do comportamento que você descreveu, obrigado pelo esclarecimento! Vou editar a resposta de acordo.
Martin Jungblut Schreiner
ok, mais 1 por me citar. :)
Aaron Hall
A adição de Aaron Hall me esclareceu. Obrigado. :)
Remco Haszing
7
Por que precisamos usar a variável de ambiente FLASK_APP? Isso é intrínseco ao modo como isso funciona? Estou curioso para saber por flask runque não aceita o mesmo que um argumento, o que tornaria mais fácil a entrada de novos integrantes. Obrigado.
John Wheeler