Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update app factory docs for Flask's CLI #2027

Closed
untitaker opened this issue Sep 18, 2016 · 11 comments · Fixed by #2490
Closed

Update app factory docs for Flask's CLI #2027

untitaker opened this issue Sep 18, 2016 · 11 comments · Fixed by #2490
Assignees
Milestone

Comments

@untitaker
Copy link
Contributor

see #1536

@wgwz
Copy link
Contributor

wgwz commented Sep 18, 2016

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

@untitaker
Copy link
Contributor Author

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.

@davidism
Copy link
Member

davidism commented Sep 18, 2016

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.

@wgwz
Copy link
Contributor

wgwz commented Sep 19, 2016

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

@davidism
Copy link
Member

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.

@miguelgrinberg
Copy link
Contributor

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.

@untitaker
Copy link
Contributor Author

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 notifications@github.com 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:
#2027 (comment)

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

@wgwz
Copy link
Contributor

wgwz commented Dec 27, 2016

@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?

@davidism
Copy link
Member

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')

@wgwz
Copy link
Contributor

wgwz commented Jan 30, 2017

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)

@bovarysme
Copy link
Contributor

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',
        ],
    },
)

@davidism davidism added the cli label Jul 15, 2017
@davidism davidism self-assigned this Aug 1, 2017
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants