'Use Flask's Click CLI with the app factory pattern
I define my Flask application using the app factory pattern. When using Flask-Script, I can pass the factory function to the Manager. I'd like to use Flask's built-in Click CLI instead. How do I use the factory with Click?
My current code uses Flask-Script. How do I do this with Click?
from flask import Flask
from flask_script import Manager, Shell
def create_app():
app = Flask(__name__)
...
return app
manager = Manager(create_app)
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role)
manager.add_command('shell', Shell(make_context=make_shell_context))
if __name__ == '__main__':
manager.run()
Solution 1:[1]
In order to pass arguments to your app factory, you need to make use of script_info like so...
manage.py
#!/usr/bin/env python
import click
import config
from flask import Flask
from flask.cli import FlaskGroup, pass_script_info
def create_app(script_info):
app = Flask(__name__)
if script_info.config_mode:
obj = getattr(config, script_info.config_mode)
flask_config.from_object(obj)
...
return app
@click.group(cls=FlaskGroup, create_app=create_app)
@click.option('-m', '--config-mode', default="Development")
@pass_script_info
def manager(script_info, config_mode):
script_info.config_mode = config_mode
if __name__ == "__main__":
manager()
config.py
class Config(object):
TESTING = False
class Production(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class Development(Config):
DATABASE_URI = 'sqlite:///app.db'
class Testing(Config):
TESTING = True
DATABASE_URI = 'sqlite:///:memory:'
now in the command line you can do manage -m Production run (after either adding the entry_points to setup.py as @davidism mentioned, or running pip install manage.py).
Solution 2:[2]
Newly updated for Flask >= 2.1. See my other answer for Flask < 2.1.
In order to pass arguments to our app, we store them in script_info. And in order to do that, we create a custom Click interface using flask.cli.FlaskGroup.
However, passing script_info directly to app factories is deprecated in Flask 2, so we use
Click's get_current_context function to get the current context and then access script_info from that context.
manage.py
#!/usr/bin/env python
import click
import config
from click import get_current_context
from flask import Flask
from flask.cli import FlaskGroup, pass_script_info
def create_app(*args, **kwargs):
app = Flask(__name__)
ctx = get_current_context(silent=True)
if ctx:
script_info = ctx.obj
config_mode = script_info.config_mode
elif kwargs.get("config_mode"):
# Production server, e.g., gunincorn
# We don't have access to the current context, so must
# read kwargs instead.
config_mode = kwargs["config_mode"]
...
return app
@click.group(cls=FlaskGroup, create_app=create_app)
@click.option('-m', '--config-mode', default="Development")
@pass_script_info
def manager(script_info, config_mode):
script_info.config_mode = config_mode
...
if __name__ == "__main__":
manager()
Now you can run the dev server and set your desired config_mode by using either -m or --config-mode. Note, until Flask 2.1 drops you have to install Flask@aa13521d42bfdb
pip install git+https://github.com/pallets/flask.git@aa13521d42bfdb
python manage.py -m Production run
Production servers like gunincorn don't have access to the current context, so we pass what we need via kwargs.
gunicorn app:create_app\(config_mode=\'Production\'\) -w 3 -k gevent
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
