Использование 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.Option
s ко всем командам 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):
...