In Flask 0.11 a flask
CLI was introduced. Both the docs and the changelog state this is recommended.
Starting with Flask 0.11 there are multiple built-in ways to run a development server. The best one is the flask command line utility but you can also continue using the
Flask.run()
method.Command Line
The flask command line script (Command Line Interface) is strongly recommended for development because it provides a superior reload experience due to how it loads the application. The basic usage is like this:
$ export FLASK_APP=my_application $ export FLASK_DEBUG=1 $ flask run
- Added
flask
and theflask.cli
module to start the local debug server through the click CLI system. This is recommended over the oldflask.run()
method as it works faster and more reliable due to a different design and also replacesFlask-Script
.
So far I didn't notice this "superior reload experience". I fail to see the point of using the CLI over a custom script.
If using Flask.run
, I would simply write a python file:
#!/usr/bin/env python3
from my_app import app
if __name__ == '__main__':
app.run(debug=True)
If using the CLI, one would have to specify environment variables. In the CLI docs is stated that this can be integrated in the activate
script of virtualenvwrapper. Personally I consider this to be part of the application and think it should be under version control. Alas, a shell script is needed:
#!/usr/bin/env bash
export FLASK_APP=my_app:app
export FLASK_DEBUG=1
flask run
Of course this will be accompanied by an additional bat script as soon as any Windows users start to collaborate.
Also the first option allows setup written in Python before starting the actual app.
This allows for example
- to parse command line arguments in Python
- to setup logging before running the app
They seem to promote that it's possible to add custom commands. I fail to see why this is better than writing simple Python scripts, optionally exposed through entry points.
Example logging output when using a configured logger using the Python run script:
$ ./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
Example logging output when using a configured logger using the CLI:, notice that the root logger couldn't be setup early enough in the process.
$ ./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
My actual question is simply:
Why is flask CLI recommended over Flask.run
?
1 Answer 1
In the development server docs, they state there are issues with calling run() and automatically reloading code:
This works well for the common case but it does not work well for development which is why from Flask 0.11 onwards the flask method is recommended. The reason for this is that due to how the reload mechanism works there are some bizarre side-effects (like executing certain code twice, sometimes crashing without message or dying when a syntax or import error happens).
They claim the CLI doesn't suffer from this problem.
The first commit that seems to touch on this issue is this: https://github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f
And there Armin Ronacher wrote:
It is not recommended to use this function for development with automatic reloading as this is badly supported. Instead you should be using the
flask
command line script'srunserver
support.
As mentioned by Aaron Hall, it seems like the use of run() could be problematic due to the fact that all objects which are instances of classes defined in the modules being replaced won't be reinstantiated, and whenever a module is reloaded, the modules it imports aren't reloaded as well.
The details about this may be found for Python 3 at: https://docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib
It states:
As with all other objects in Python the old objects are only reclaimed after their reference counts drop to zero.
Other references to the old objects (such as names external to the module) are not rebound to refer to the new objects and must be updated in each namespace where they occur if that is desired.
When a module is reloaded, its dictionary (containing the module’s global variables) is retained. Redefinitions of names will override the old definitions, so this is generally not a problem. If the new version of a module does not define a name that was defined by the old version, the old definition remains.
So, by creating a new process and killing the old one, you naturally eliminate all obsolete references.
Also, Flask's CLI uses the 'click' module, making it very easy to add custom commands, but most importantly, besides fixing the reloading bug, the CLI offers a standardised way to run applications and add custom commands. This sounds like a very good thing, because it makes familiarity with Flask more transferable between different teams and applications, rather than having multiple ways to do the same thing.
It seems like a genuine way to make Flask more in accordance to the Zen of Python:
There should be one-- and preferably only one --obvious way to do it.
-
2Here's what I think Armin means by "badly supported": In Python, reloading a module doesn't reload the modules that that module imports, nor does it reassociate names in other modules from pointing to old objects to new ones from the new module - so hot swapping a new module into the same process is problematic. You're far better off starting a new process when you want to make a code change.Aaron Hall– Aaron Hall08/03/2016 21:04:50Commented Aug 3, 2016 at 21:04
-
Now that you mention it, I do recall the behaviour you described, thank you for the clarification! I'll edit the answer accordingly.Martin Jungblut Schreiner– Martin Jungblut Schreiner08/04/2016 00:06:20Commented Aug 4, 2016 at 0:06
-
ok, plus 1 for citing me. :)Aaron Hall– Aaron Hall08/04/2016 01:18:11Commented Aug 4, 2016 at 1:18
-
The addition from Aaron Hall clarified it for me. Thanks. :)Remco Haszing– Remco Haszing08/11/2016 09:18:20Commented Aug 11, 2016 at 9:18
-
7Why do we have to use the environment variable
FLASK_APP
? Is that intrinsic to how this works? I'm curious whyflask run
doesn't accept the same as an argument, which would make onboarding newcomers easier. Thank you.John Wheeler– John Wheeler09/06/2016 01:47:44Commented Sep 6, 2016 at 1:47