'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