I like to start my flask projects with a simple
__name__ == "__main__" with arparse and
app.run(). But obviously the flask inbuilt server is not the fastest and probably not the safest either. This is why you should eventually use a wsgi runner like waitress to run your app. This post will show how to migrate, while keeping any standalone capabilities.
In short, everything in your
if __name__ block is not going to be executed anymore, the simplest solution is to move it to a
@app.before_first_request anotated function, so:
app = Flask("LOL") if __init__ == "__name__": # arparse stuff ... # init stuff ... app.run(host="127.0.0.1", port=5300)
app = Flask("LOL") @app.before_first_request def doStuff(): # init stuff ... if __init__ == "__name__": # arparse stuff ... # write arparse results to config app.run(host="127.0.0.1", port=5300)
Obviously we now need to create an alternative option for configuration to argparse. When the app is started through WSGI and write any argparse-inputs to the runtime version of this config so that it can be used in our init function and we can avoid duplicated code. You can create a config.py and load it with
app.config.from_object or you can simply use:
app.config['OPTION'] = "something"
We can then change it like a namespace-object (basicly a dict-object). Remember that any options you pass to app.run don’t have to be/can’t be written to app.config, as they will later be supplied by the WSGI-runner configuration.
app = Flask("LOL") app.config.from_object("config") # note the missing '.py' app.config["MORE_OPTIONS"] = "Hello" @app.before_first_request def doStuff(): doInitSuff(app.config.SOME_OPTION) if __init__ == "__name__": parser = argparse.ArgumentParser() parser.add_argument(...) args = parse.parse_args() app.config['SOME_OPTION'] = args.SOME_OPTIONS app.run(host="127.0.0.1", port=53000)
Finally you need to add an entry point. The simplest way to do this, is to create a new file and add a really simple function, which returns the flask application in your main module, e.g. the following app.py with ‘app’ being the global variable app in server.py, aka your main file, presumably containing all the anontated functions/webserver paths etc. :
import server as moduleContainingApp # note the missing'.py' def createApp(envivorment=None, start_response=None): # default value is required return moduleContainingApp.app
You can also use the enviroment for on the fly configuration.
The simplest production server for the WSGI-protocol is waitress, you can install it with pip, or via apt as python3-waitress if you OS is Debian. Then run it with:
waitress-serve --host 127.0.0.1 --port 5300 --call 'app:createApp'
createApp is your entrypoint, app your app.py containing this entry point.
Write this to a file called lol.service in /etc/systemd/user/
[Unit] Description=LOL After=network.target [Service] WorkingDirectory=/path/to/appDir/ Type=simple User=www-data ExecStart=/usr/bin/waitress-serve/ --host 127.0.0.1 --port 5004 --call 'app:createApp' [Install] WantedBy=multi-user.target
Enable it with it’s full path, then start it by it’s name:
systemctl enable /etc/systemd/user/lol.service systemctl start lol
Now it will run and start automaticly on reboot. No more excuses to use cron-@reboot. You people anoy me. Yes I’m looking at you.
Below are some of the commits, which implemented this transition in my projects:
Feel free to send me a mail to share your thoughts!