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()
The flask command is a click interface built with flask.cli.FlaskGroup . Create your own group and pass it the factory function. Use app.shell_context_processor to add objects to the shell.
from flask import Flask from flask.cli import FlaskGroup from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def create_app(script_info=None): app = Flask(__name__) db.init_app(app) ... @app.shell_context_processor def shell_context(): return {'app': app, 'db': db} return app cli = FlaskGroup(create_app=create_app) @cli.command def custom_command(): pass if __name__ == '__main__': cli() Run your file instead of the flask command. You will get the Click interface using its factory.
FLASK_DEBUG=1 python app.py run Ideally, create an entry point and install your package into your env. You can then call the script as a command. Create a setup.py file with at least the following.
project/ app/ __init__.py setup.py from setuptools import setup, find_packages setup( name='my_app', version='1.0.0', packages=find_packages(), entry_points={ 'console_scripts': [ 'app=app:cli', ], }, ) pip install -e /path/to/project FLASK_DEBUG=1 app run Using your own CLI is less robust than the built-in flask command. Because your cli object is defined with your other code, a module-level error will cause the reloader to fail because it can no longer import the object. The flask command is separate from your project, so it is not affected by errors in your module.
To pass arguments to your app factory, you need to make use of script_info like this...
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, on the command line, you can manage -m Production run (after adding the setup.py entry_points mentioned in @davidism, or run pip install manage.py ).
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