[英]How to run/invoke a flask cli command programmatically?
I am using python 3 and flask, with flask-migrate (which uses alembic) to handle my SQL migrations.我正在使用 python 3 和flask,使用flask-migrate(使用alembic)来处理我的SQL 迁移。 When I run local integration tests, I want to rebuild the database each time so I can run my API calls against a clean db for each api call i'm testing (yes, i could use sqlite, but i want to check constraints are correct).当我运行本地集成测试时,我想每次都重建数据库,以便我可以针对我正在测试的每个 api 调用针对干净的数据库运行我的 API 调用(是的,我可以使用 sqlite,但我想检查约束是否正确)。
I can do the following on the command line easily:我可以轻松地在命令行上执行以下操作:
mysql -uroot -e 'drop database DBNAME; create database DBNAME;'
FLASK_APP=flask_app.py flask db upgrade
But I would rather run it in the python code for 2 reasons:但我宁愿在 python 代码中运行它,原因有两个:
The app
object (created with app = Flask(__name__)
) has a cli
property, but it requires a context object, and it doesn't feel like i'm using the right tool. app
对象(用app = Flask(__name__)
)有一个cli
属性,但它需要一个上下文对象,而且我觉得我没有使用正确的工具。 I expected app.cli.invoke('db', 'upgrade')
or similar...我期望app.cli.invoke('db', 'upgrade')
或类似的...
Any suggestions on how to invoke flask commands from the code without a child cli process?关于如何在没有子 cli 进程的情况下从代码调用烧瓶命令的任何建议?
I use the following pattern (see below).我使用以下模式(见下文)。 An alternate approach can be seen at https://flask.palletsprojects.com/en/1.1.x/cli/?highlight=click#application-context可以在https://flask.palletsprojects.com/en/1.1.x/cli/?highlight=click#application-context上看到另一种方法
# file: commands.py
import click
from click import pass_context
from flask.cli import AppGroup, with_appcontext
from flask import current_app
from flask_migrate import Migrate
from alembic import command
from extensions import flask_db as db
db_cli = AppGroup('db', help='Various database management commands.')
@db_cli.command('init')
def db_init():
"""Initialize the database."""
db.create_all()
click.echo("Create all tables.")
@db_cli.command('drop')
def db_drop():
"""Drop the database."""
db.engine.execute("SET FOREIGN_KEY_CHECKS=0;")
db.drop_all()
db.engine.execute("SET FOREIGN_KEY_CHECKS=1;")
click.echo("Drop all tables.")
@db_cli.command('migrate')
def db_migrate():
"Migrate with alembic."
config = Migrate(current_app, db).get_config()
command.upgrade(config, 'head')
@db_cli.command('db_upgrade')
@pass_context
def db_upgrade(ctx):
"""Alias for 'db reset'."""
db_drop.invoke(ctx)
db_init.invoke(ctx)
db_migrate.invoke(ctx)
# file: extensions.py
# Keep your extenstions separate to allow importing without import loops.
from flask_sqlalchemy import SQLAlchemy
flask_db = SQLAlchemy()
# file: app.py (app/__init__.py) wherever your app is built
from extensions import flask_db
app = Flask(__name__)
flask_db.init_app(app) # I'm not sure if the order matters here.
app.cli.add_command(db_cli)
# file: wsgi.py (top level file)
# This file lets you run 'flask' commands (e.g. flask routes)
# noinspection PyUnresolvedReferences
from app import app as application # noqa
# file layout
- /
- app/ (or app.py)
- __init__.py (optional)
- commands.py
- extensions.py
- wsgi.py
Usage: flask db upgrade
用法: flask db upgrade
It's not great, but in the end I avoided using flask commands directly and this seems to do what i need:这不是很好,但最后我避免直接使用烧瓶命令,这似乎满足了我的需要:
from my.app import app, db, initialize_app
from flask_migrate import Migrate
from alembic import command
from my.settings import settings
from sqlalchemy_utils.functions import drop_database, create_database, database_exists
test_db_name = 'test_db'
db_url = f'mysql+pymysql://mysqluser@127.0.0.1/{test_db_name}'
settings.SQLALCHEMY_DATABASE_URI = db_url
def reset():
if database_exists(db_url):
drop_database(db_url)
create_database(db_url)
initialize_app(app) # sets flask config SQLALCHEMY_DATABASE_URI to include test_db
with app.app_context():
config = Migrate(app, db).get_config()
command.upgrade(config, 'head')
Is there any reason that you can't simply wrap the functions that have all the logic for the CLI?是否有任何理由不能简单地包装具有 CLI 的所有逻辑的函数?
something like就像是
# cli.py
def db_init(cli=False):
db.create_all()
if cli:
print("Created all tables")
@app.cli.command('db-init')
def _db_init(): # I'm just a wrapper function
db_init(cli=True)
If you do it that way, there should be no reason why you can't simply do this afterwards:如果你这样做,就没有理由事后不能简单地这样做:
# some_module.py
from .cli import db_init
db_init()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.