Использование Click CLI Flask с шаблоном фабрики приложений

Я определяю свое приложение Flask, используя шаблон фабрики приложений. При использовании Flask-Script я могу передать заводскую функцию Manager, Вместо этого я бы хотел использовать встроенный Click CLI в Flask. Как мне использовать фабрику с Click?

Мой текущий код использует Flask-Script. Как мне это сделать с помощью 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()

4 ответа

Решение

flask команда является интерфейсом Click, созданным с flask.cli.FlaskGroup, Создайте свою собственную группу и передайте ей заводскую функцию. использование app.shell_context_processor добавить объекты в оболочку.

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

Запустите ваш файл вместо flask команда. Вы получите интерфейс Click, используя вашу фабрику.

FLASK_DEBUG=1 python app.py run

В идеале, создать точку входа и установить свой пакет в вашей среде. Затем вы можете вызвать скрипт как команду. Создать setup.py файл, по крайней мере, со следующим.

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

Использование собственного интерфейса командной строки менее надежно, чем встроенный flask команда. Потому что ваш cli объект определяется другим кодом, ошибка на уровне модуля приведет к сбою перезагружателя, поскольку он больше не может импортировать объект. flask Команда отделена от вашего проекта, поэтому на нее не влияют ошибки в вашем модуле.

Недавно обновлено для Flask >= 2.1. Смотрите мой другой ответ для Flask <2.1.

Чтобы передать аргументы нашему приложению, мы сохраняем их в файле . И для этого мы создаем собственный интерфейс Click, используя flask.cli.FlaskGroup.

Однако прямой переход к фабрикам приложений во Flask 2 устарел , поэтому мы используем Click’s get_current_contextфункция, чтобы получить текущий контекст, а затем получить доступ script_infoиз того контекста.

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

Теперь вы можете запустить сервер разработки и установить желаемый config_modeс помощью либо -mили же --config-mode. Обратите внимание: пока не выйдет Flask 2.1, вам необходимо установить Flask@aa13521d42bfdb.

      pip install git+https://github.com/pallets/flask.git@aa13521d42bfdb
python manage.py -m Production run

Производственные серверы, такие как gunincorn, не имеют доступа к текущему контексту, поэтому мы передаем то, что нам нужно, через kwargs.

      gunicorn app:create_app\(config_mode=\'Production\'\) -w 3 -k gevent

Чтобы передать аргументы в фабрику приложений, вам необходимо использовать script_info вот так...

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

теперь в командной строке вы можете сделать manage -m Production run (после добавления entry_points к setup.py как упоминалось в @davidism или работает pip install manage.py).

Я нашел другой способ добавитьclick.Options ко всем командам Flask и передайте параметрыcreate_app. Это аналогичный подход к ответу @reubano.

      from flask import Flask
from flask.cli import FlaskGroup, ScriptInfo
import click

def create_app(say_hi: bool = False, say_goodbye: bool = False):
    app = Flask(__name__)
    
    if say_hi:
        print("hi")
    if say_goodbye:
        print("goodbye")

    return app


def update_create_app(ctx: click.Context, param: click.Option, value):
    """updates Flask's ScriptInfo instance for click commands, updating the create_app callable with our extra
    cli options."""
    if value is None:
        return None

    info = ctx.ensure_object(ScriptInfo)
    params = info.data.get("create_app_args", {})
    info.data["create_app_args"] = {**params, param.human_readable_name: value}
    info.create_app = lambda: create_app(**info.data["create_app_args"])
    return value


_hi = click.Option(
    ["--say-hi"],
    is_flag=True,
    default=False,
    callback=update_create_app,
)
_bye = click.Option(
    ["--say-goodbye"],
    is_flag=True,
    default=False,
    callback=update_create_app,
)


@click.group(cls=FlaskGroup, params=(_hi, _bye))
def customized_flask(*args, **kwargs):
    ...
Другие вопросы по тегам