Pourquoi la CLI flask est-elle recommandée par rapport à Flask.run?

13

Dans Flask 0.11, une flaskCLI a été introduite. Les documents et le journal des modifications indiquent que cela est recommandé.

Documents du serveur de développement :

À partir de Flask 0.11, il existe plusieurs façons intégrées d'exécuter un serveur de développement. Le meilleur est l' utilitaire de ligne de commande flask , mais vous pouvez également continuer à utiliser la Flask.run()méthode.

Ligne de commande

Le script de ligne de commande flask (interface de ligne de commande) est fortement recommandé pour le développement car il offre une expérience de rechargement supérieure en raison de la façon dont il charge l'application. L'utilisation de base est la suivante:

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

Journal des modifications :

  • Ajouté flasket le flask.climodule pour démarrer le serveur de débogage local via le système CLI click. Ceci est recommandé par rapport à l'ancienne flask.run()méthode car elle fonctionne plus rapidement et plus fiable en raison d'une conception différente et remplace également Flask-Script.

Jusqu'à présent, je n'ai pas remarqué cette "expérience de rechargement supérieure". Je ne vois pas l'intérêt d'utiliser la CLI sur un script personnalisé.

Si Flask.runj'utilise, j'écrirais simplement un fichier python:

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


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

Si vous utilisez l'interface CLI, il faudrait spécifier des variables d'environnement. Dans la documentation CLI, il est indiqué que cela peut être intégré dans le activatescript de virtualenvwrapper. Personnellement, je considère que cela fait partie de l'application et je pense que cela devrait être sous contrôle de version. Hélas, un script shell est nécessaire:

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

flask run

Bien sûr, cela sera accompagné d'un script bat supplémentaire dès que les utilisateurs de Windows commenceront à collaborer.

La première option permet également une configuration écrite en Python avant de démarrer l'application réelle.

Cela permet par exemple

  • analyser les arguments de ligne de commande en Python
  • pour configurer la journalisation avant d'exécuter l'application

Ils semblent promouvoir qu'il est possible d'ajouter des commandes personnalisées. Je ne vois pas pourquoi c'est mieux que d'écrire de simples scripts Python, éventuellement exposés via des points d'entrée.

Exemple de sortie de journalisation lors de l'utilisation d'un enregistreur configuré à l'aide du script d'exécution 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

Exemple de sortie de journalisation lors de l'utilisation d'un enregistreur configuré à l'aide de l'interface CLI:, notez que l'enregistreur racine n'a pas pu être configuré suffisamment tôt dans le processus.

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

Ma vraie question est simplement:

Pourquoi la CLI Flask est-elle recommandée Flask.run?

Remco Haszing
la source

Réponses:

11

Dans les documents du serveur de développement, ils indiquent qu'il y a des problèmes avec l'appel de run () et le rechargement automatique du code:

Cela fonctionne bien pour le cas commun mais cela ne fonctionne pas bien pour le développement, c'est pourquoi à partir de la fiole 0.11, la méthode de la fiole est recommandée. La raison en est qu'en raison du fonctionnement du mécanisme de rechargement, il y a des effets secondaires bizarres (comme exécuter deux fois certains codes, parfois planter sans message ou mourir lorsqu'une erreur de syntaxe ou d'importation se produit).

Ils affirment que la CLI ne souffre pas de ce problème.

Le premier commit qui semble toucher à ce problème est le suivant: https://github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f

Et là, Armin Ronacher a écrit:

Il n'est pas recommandé d'utiliser cette fonction pour le développement avec rechargement automatique car cela est mal pris en charge. Au lieu de cela, vous devez utiliser le support flaskdu script de ligne de commande runserver.

Comme mentionné par Aaron Hall, il semble que l'utilisation de run () pourrait être problématique du fait que tous les objets qui sont des instances de classes définies dans les modules remplacés ne seront pas réinstanciés, et chaque fois qu'un module est rechargé, le les modules qu'il importe ne sont pas rechargés également.

Les détails à ce sujet peuvent être trouvés pour Python 3 à: https://docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib

Il est dit:

Comme pour tous les autres objets en Python, les anciens objets ne sont récupérés qu'après que leur nombre de références soit tombé à zéro.

Les autres références aux anciens objets (comme les noms externes au module) ne sont pas rebondies pour faire référence aux nouveaux objets et doivent être mises à jour dans chaque espace de noms où elles se produisent si cela est souhaité.

Lorsqu'un module est rechargé, son dictionnaire (contenant les variables globales du module) est conservé. Les redéfinitions de noms remplaceront les anciennes définitions, donc ce n'est généralement pas un problème. Si la nouvelle version d'un module ne définit pas un nom qui a été défini par l'ancienne version, l'ancienne définition reste.

Ainsi, en créant un nouveau processus et en tuant l'ancien, vous éliminez naturellement toutes les références obsolètes.

En outre, la CLI de Flask utilise le module `` clic '', ce qui facilite l'ajout de commandes personnalisées, mais surtout, en plus de corriger le bogue de rechargement, la CLI offre un moyen standardisé d'exécuter des applications et d'ajouter des commandes personnalisées. Cela semble être une très bonne chose, car cela rend la familiarité avec Flask plus transférable entre différentes équipes et applications, plutôt que d'avoir plusieurs façons de faire la même chose.

Cela semble être un véritable moyen de rendre Flask plus conforme au Zen de Python:

Il devrait y avoir une - et de préférence une seule - manière évidente de le faire.

Martin Jungblut Schreiner
la source
2
Voici ce que je pense qu'Armin entend par "mal pris en charge": en Python, le rechargement d'un module ne recharge pas les modules que ce module importe, ni ne réassocie les noms dans d'autres modules de pointer vers des objets anciens vers de nouveaux objets du nouveau module - l'échange à chaud d'un nouveau module dans le même processus est problématique. Il est préférable de commencer un nouveau processus lorsque vous souhaitez modifier un code.
Aaron Hall
Maintenant que vous le mentionnez, je me souviens du comportement que vous avez décrit, merci pour la clarification! Je modifierai la réponse en conséquence.
Martin Jungblut Schreiner
ok, plus 1 pour me citer. :)
Aaron Hall
L'ajout d'Aaron Hall l'a clarifié pour moi. Merci. :)
Remco Haszing
7
Pourquoi devons-nous utiliser la variable d'environnement FLASK_APP? Est-ce intrinsèque à la façon dont cela fonctionne? Je suis curieux de savoir pourquoi flask runn'accepte pas la même chose comme argument, ce qui faciliterait l'intégration des nouveaux arrivants. Je vous remercie.
John Wheeler