¿Por qué se recomienda la CLI del matraz sobre Flask.run?

13

En el matraz 0.11 flaskse introdujo un CLI. Tanto los documentos como el registro de cambios indican que esto se recomienda.

Documentos del servidor de desarrollo :

A partir de Flask 0.11, hay varias formas integradas de ejecutar un servidor de desarrollo. La mejor es la utilidad de línea de comando del matraz , pero también puede continuar usando el Flask.run()método.

Línea de comando

La secuencia de comandos de la línea de comandos del matraz (Interfaz de línea de comandos) se recomienda encarecidamente para el desarrollo, ya que proporciona una experiencia de recarga superior debido a cómo carga la aplicación. El uso básico es así:

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

Registro de cambios :

  • Se agregó flaskel flask.climódulo para iniciar el servidor de depuración local a través del sistema CLI de clic. Esto se recomienda sobre el flask.run()método anterior , ya que funciona más rápido y más confiable debido a un diseño diferente y también reemplaza Flask-Script.

Hasta ahora no noté esta "experiencia de recarga superior". No veo el punto de usar la CLI sobre un script personalizado.

Si lo uso Flask.run, simplemente escribiría un archivo de Python:

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


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

Si usa la CLI, uno tendría que especificar variables de entorno. En los documentos de la CLI se afirma que esto se puede integrar en el activatescript de virtualenvwrapper. Personalmente considero que esto es parte de la aplicación y creo que debería estar bajo control de versiones. Por desgracia, se necesita un script de shell:

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

flask run

Por supuesto, esto irá acompañado de un script de murciélago adicional tan pronto como los usuarios de Windows comiencen a colaborar.

Además, la primera opción permite la configuración escrita en Python antes de iniciar la aplicación real.

Esto permite por ejemplo

  • analizar los argumentos de la línea de comandos en Python
  • configurar el registro antes de ejecutar la aplicación

Parecen promover que es posible agregar comandos personalizados. No veo por qué esto es mejor que escribir scripts Python simples, opcionalmente expuestos a través de puntos de entrada.

Ejemplo de salida de registro cuando se usa un registrador configurado usando el script de ejecución de 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

Ejemplo de salida de registro cuando se usa un registrador configurado usando la CLI: observe que el registrador raíz no se pudo configurar lo suficientemente temprano en el proceso.

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

Mi pregunta real es simplemente:

¿Por qué se recomienda la CLI del matraz Flask.run?

Remco Haszing
fuente

Respuestas:

11

En los documentos del servidor de desarrollo, afirman que hay problemas con la llamada a run () y la recarga automática del código:

Esto funciona bien para el caso común, pero no funciona bien para el desarrollo, por eso desde el Frasco 0.11 en adelante se recomienda el método del matraz. La razón de esto es que, debido a cómo funciona el mecanismo de recarga, hay algunos efectos secundarios extraños (como ejecutar dos veces cierto código, a veces fallar sin mensaje o morir cuando ocurre un error de sintaxis o importación).

Afirman que la CLI no sufre este problema.

El primer compromiso que parece tocar este problema es este: https://github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f

Y allí Armin Ronacher escribió:

No se recomienda utilizar esta función para el desarrollo con recarga automática ya que esto no es compatible. En su lugar, debería usar el soporte flaskdel script de línea de comandos runserver.

Según lo mencionado por Aaron Hall, parece que el uso de run () podría ser problemático debido al hecho de que todos los objetos que son instancias de clases definidas en los módulos que se están reemplazando no se reinstalarán, y cada vez que se recargue un módulo, el los módulos que importa no se vuelven a cargar también.

Los detalles sobre esto se pueden encontrar para Python 3 en: https://docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib

Afirma:

Al igual que con todos los demás objetos en Python, los objetos antiguos solo se recuperan después de que sus recuentos de referencia caen a cero.

Otras referencias a los objetos antiguos (como los nombres externos al módulo) no se rebotan para referirse a los nuevos objetos y deben actualizarse en cada espacio de nombres donde ocurran si así se desea.

Cuando se recarga un módulo, se retiene su diccionario (que contiene las variables globales del módulo). Las redefiniciones de nombres anularán las definiciones antiguas, por lo que esto generalmente no es un problema. Si la nueva versión de un módulo no define un nombre definido por la versión anterior, la definición anterior permanece.

Entonces, al crear un nuevo proceso y eliminar el anterior, elimina naturalmente todas las referencias obsoletas.

Además, la CLI de Flask utiliza el módulo 'clic', lo que hace que sea muy fácil agregar comandos personalizados, pero lo más importante, además de corregir el error de recarga, la CLI ofrece una forma estandarizada de ejecutar aplicaciones y agregar comandos personalizados. Esto suena muy bueno, porque hace que la familiaridad con Flask sea más transferible entre diferentes equipos y aplicaciones, en lugar de tener múltiples formas de hacer lo mismo.

Parece una forma genuina de hacer que Flask esté más de acuerdo con el Zen de Python:

Debe haber una, y preferiblemente solo una, forma obvia de hacerlo.

Martin Jungblut Schreiner
fuente
2
Esto es lo que creo que Armin quiere decir con "mal soporte": en Python, la recarga de un módulo no recarga los módulos que ese módulo importa, ni reasocia los nombres en otros módulos para que no apunten a objetos viejos a nuevos del nuevo módulo. Por lo tanto, el intercambio en caliente de un nuevo módulo en el mismo proceso es problemático. Es mucho mejor comenzar un nuevo proceso cuando desee hacer un cambio de código.
Aaron Hall el
Ahora que lo menciona, recuerdo el comportamiento que describió, ¡gracias por la aclaración! Editaré la respuesta en consecuencia.
Martin Jungblut Schreiner
ok, más 1 por citarme. :)
Aaron Hall el
La adición de Aaron Hall me lo aclaró. Gracias. :)
Remco Haszing
77
¿Por qué tenemos que usar la variable de entorno FLASK_APP? ¿Es eso intrínseco a cómo funciona esto? Tengo curiosidad por qué flask runno acepta lo mismo como argumento, lo que facilitaría la incorporación de los recién llegados. Gracias.
John Wheeler