Flask: Update app factory docs for Flask's CLI

Created on 18 Sep 2016  Â·  11Comments  Â·  Source: pallets/flask

Most helpful comment

I think the factory function should be coded in a way that it is straightforward to call directly. I do not like the idea of adding the ScriptInfo argument, just because the Click integration code requires it.

I honestly haven't found a way to use an app factory with Click that matches what you can do with Flask-Script. This is how I typically code my app factory functions:

def create_app(config_name=None):
    if config_name is None:
        config_name = os.environ.get('FLASK_CONFIG', 'development')
    app = Flask(__name__)
    ...
    return app

With this structure, Flask-Script can be given the app factory and it will call it without arguments, which runs the development configuration unless the FLASK_CONFIG environment variable is set to some other configuration. In unit tests, you just call create_app('testing') and you get a test ready app. In my opinion this is very handy. Depending on the application, I can add additional arguments to create_app, as long as all the arguments have defaults for when Flask-Scripts invokes it.

The closest I have achieved with Click requires the use of a wsgi.py file in which create_app() is called explicitly. Obviously Flask never sees the factory function, it gets the app created in wsgi.py, but at least I can write my app factory function with the arguments that I need/want.

All 11 comments

So would best practice be to create a runner/instantiation script? (For using the development server)

Yeah. Also I'm not sure why "multiple instances" is mentioned as usecase, since we already use locks in Flask to ensure that one app object can be used with more than one server in one process.

I create a FlaskGroup and add an entry point for it, then use that entry point instead of flask to call any commands.

my_app/app.py

from flask import Flask
from flask.cli import FlaskGroup

def create_app(info=None):
    app = Flask('my_app')
    ...
    return app

cli = FlaskGroup(create_app=create_app)

setup.py

setup(
    ...,
    entry_points={
        'console_scripts': [
            'my_app=my_app:cli.main',
        ],
    },
)
pip install -e .
FLASK_DEBUG=1 my_app run

You can of course adapt this to use a separate file for the cli so it can be called without installing too, but I'd rather encourage the install pattern.

@davidism how do you make use of the kwarg "info"?

I don't, but it has to be there to make Click happy. I think it can be used to pass data around within Click.

I think the factory function should be coded in a way that it is straightforward to call directly. I do not like the idea of adding the ScriptInfo argument, just because the Click integration code requires it.

I honestly haven't found a way to use an app factory with Click that matches what you can do with Flask-Script. This is how I typically code my app factory functions:

def create_app(config_name=None):
    if config_name is None:
        config_name = os.environ.get('FLASK_CONFIG', 'development')
    app = Flask(__name__)
    ...
    return app

With this structure, Flask-Script can be given the app factory and it will call it without arguments, which runs the development configuration unless the FLASK_CONFIG environment variable is set to some other configuration. In unit tests, you just call create_app('testing') and you get a test ready app. In my opinion this is very handy. Depending on the application, I can add additional arguments to create_app, as long as all the arguments have defaults for when Flask-Scripts invokes it.

The closest I have achieved with Click requires the use of a wsgi.py file in which create_app() is called explicitly. Obviously Flask never sees the factory function, it gets the app created in wsgi.py, but at least I can write my app factory function with the arguments that I need/want.

I hadn't considered optional arguments even though @mbr pointed that out repeatedly. On that basis we might actually include a basic way to use factories.

On 19 September 2016 03:24:52 CEST, Miguel Grinberg [email protected] wrote:

I think the factory function should be coded in a way that it is
straightforward to call directly. I do not like the idea of adding the
ScriptInfo argument, just because the Click integration code requires
it.

I honestly haven't found a way to use an app factory with Click that
matches what you can do with Flask-Script. This is how I typically code
my app factory functions:

def create_app(config_name=None):
   if config_name is None:
       config_name = os.environ.get('FLASK_CONFIG', 'development')
   app = Flask(__name__)
   ...
   return app

With this structure, Flask-Script can be given the app factory and it
will call it without arguments, which runs the development
configuration unless the FLASK_CONFIG environment variable is set to
some other configuration. In unit tests, you just call
create_app('testing') and you get a test ready app. In my opinion
this is very handy. Depending on the application, I can add additional
arguments to create_app, as long as all the arguments have defaults
for when Flask-Scripts invokes it.

The closest I have achieved with Click requires the use of a wsgi.py
file in which create_app() is called explicitly. Obviously Flask
never sees the factory function, it gets the app created in wsgi.py,
but at least I can write my app factory function with the arguments
that I need/want.

You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub:
https://github.com/pallets/flask/issues/2027#issuecomment-247897066

Sent from my Android device with K-9 Mail. Please excuse my brevity.

@davidism what kind of package structure do you use to add commands to the cli? (using the method you outlined above) i.e. do you import the cli object from my_app/app.py into a separate module and add commands using @cli?

Cross posting from the .env discussion:

One option for making this play nicer with the factory pattern is to allow making a call in the FLASK_APP var. Gunicorn and uWSGI allow this. gunicorn bwportal.app:create_app(where='dev')

I would like to be able to do the same with the flask cli:

gunicorn -c "python:config.gunicorn" --reload "myapp.app:create_app()"

(Not necessarily the config loading, but definitely invoking the app factory)

After reading this thread and mixing @davidism and @miguelgrinberg ideas, I have been using the following structure which allows to:

  • write an app factory function without the ScriptInfo argument,
  • load the correct app config via FLASK_CONFIG,
  • write as many CLI commands as needed,
  • and run in debug mode with app --debug run.

That way, I don't have to ever set FLASK_APP or FLASK_DEBUG.

app/__init__.py:

def create_app(config_name=None):
    if not config_name:
        config_name = os.environ.get('FLASK_CONFIG', 'development')
    app = Flask(__name__)
    # …
    return app

app/commands.py:

def create_cli_app(info):
    return create_app()


@click.group(cls=FlaskGroup, create_app=create_cli_app)
@click.option('--debug', is_flag=True, default=False)
def cli(debug):
    if debug:
        os.environ['FLASK_DEBUG'] = '1'


@cli.command()
def initdb():
    """Initialize the database."""
    db.create_all()

# …

if __name__ == '__main__':
    cli()

setup.py:

setup(
    # …
    entry_points={
        'console_scripts': [
            'app=app.commands:cli',
        ],
    },
)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  Â·  3Comments

jab picture jab  Â·  4Comments

ghostbod99 picture ghostbod99  Â·  4Comments

rochacbruno picture rochacbruno  Â·  3Comments

sungjinp11 picture sungjinp11  Â·  3Comments