flask-migrate/alembic - 如何跳过特定的 sqlalchemy 绑定

flask-migrate/alembic - how to skip a specific sqlalchemy bind

有什么方法可以使用 flask db migrate / alembic 跳过绑定吗?

我有两个 SQLALCHEMY_BINDS 使用公共数据库的存储库,但数据库不同。在我的例子中,成员回购使用 db membersSQLALCHEMY_BINDS={'users': usersdb_uri},合同回购使用 db contractsSQLALCHEMY_BINDS={'users': usersdb_uri}.

我希望成员回购处理 users 数据库的迁移,并且合同回购在数据库迁移时忽略它。

我正在尝试使用 flask-migrate 进行初始迁移以添加用户绑定到合同 repo,这需要对 contracts db

进行一些更改

在合同回购中,我尝试修改 alembic 的 env.py 以从 SQLALCHEMY_BINDS

中弹出用户绑定
bind_names = []
# skip 'users' bind because this database migration is handled in https://github.com/louking/members
current_app.config['SQLALCHEMY_BINDS'].pop('users')
for bind in current_app.config.get("SQLALCHEMY_BINDS"):
    context.config.set_section_option(
        bind, "sqlalchemy.url",
        str(current_app.extensions['migrate'].db.get_engine(
            current_app, bind).url).replace('%', '%%'))
    bind_names.append(bind)

我从 flask db migrate -m "common user database"

看到以下输出
INFO  [alembic.env] Migrating database <default>
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'localinterest'
INFO  [alembic.autogenerate.compare] Detected added table 'localuser'
INFO  [alembic.autogenerate.compare] Detected removed table 'roles_users'
INFO  [alembic.autogenerate.compare] Detected removed index 'email' on 'user'
INFO  [alembic.autogenerate.compare] Detected removed table 'user'
INFO  [alembic.autogenerate.compare] Detected removed index 'name' on 'role'
INFO  [alembic.autogenerate.compare] Detected removed table 'role'
Generating C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts\migrations\versions\cee4ca015898_common_user_database.py ...  done

这正确地跳过了 users 绑定,但是在修订文件中 upgrade()downgrade() 函数是空的。

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = 'cee4ca015898'
down_revision = '321e28a8aa56'
branch_labels = None
depends_on = None


def upgrade():
    pass


def downgrade():
    pass

编辑以在没有 pop() 的情况下显示错误

(venv) C:\Users\lking\Documents\Lou's Software\projects\contracts\contracts>flask db migrate -m "common user database"
INFO  [alembic.env] Migrating database <default>
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'localinterest'
INFO  [alembic.autogenerate.compare] Detected added table 'localuser'
INFO  [alembic.autogenerate.compare] Detected removed index 'name' on 'role'
INFO  [alembic.autogenerate.compare] Detected removed table 'role'
INFO  [alembic.autogenerate.compare] Detected removed index 'email' on 'user'
INFO  [alembic.autogenerate.compare] Detected removed table 'user'
INFO  [alembic.autogenerate.compare] Detected removed table 'roles_users'
INFO  [alembic.env] Migrating database users
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
ERROR [flask_migrate] Error: Can't locate revision identified by 'cacdee34a411'

还尝试跳过迁移

我还尝试在以下代码中跳过用户,但这也会导致空 upgrade()downgrade() 函数。

        for name, rec in engines.items():
            # skip 'users' bind because this database migration is handled in https://github.com/louking/members
            if name == 'users': continue
            logger.info("Migrating database %s" % (name or '<default>'))
            context.configure(
                connection=rec['connection'],
                upgrade_token="%s_upgrades" % name,
                downgrade_token="%s_downgrades" % name,
                target_metadata=get_metadata(name),
                process_revision_directives=process_revision_directives,
                **current_app.extensions['migrate'].configure_args
            )
            context.run_migrations(engine_name=name)

您正在进行的 pop() 调用打乱了您的 Flask 配置。与其以这种方式进行,我建议您使用 Alembic 中的 include_object 来让它跳过您不想迁移的表。

原来问题是因为我跳过了一步。我没有为多数据库重新创建 env.py(它之前是为单个数据库创建的),而是从成员仓库中复制了 env.py。

然而,我忽略了复制script.py.mako。当我复制 script.py.mako 时,修订文件创建正确,并且 flask db upgrade 也可以正常工作。

这是

bind_names = []
# skip 'users' bind because this database migration is handled in https://github.com/louking/members
current_app.config['SQLALCHEMY_BINDS'].pop('users')
for bind in current_app.config.get("SQLALCHEMY_BINDS"):
    context.config.set_section_option(
        bind, "sqlalchemy.url",
        str(current_app.extensions['migrate'].db.get_engine(
            current_app, bind).url).replace('%', '%%'))
    bind_names.append(bind)

现在我在修订文件中看到了

"""common user database

Revision ID: 6f403f3025b2
Revises: 321e28a8aa56
Create Date: 2022-03-31 14:46:16.806041

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = '6f403f3025b2'
down_revision = '321e28a8aa56'
branch_labels = None
depends_on = None


def upgrade(engine_name):
    globals()["upgrade_%s" % engine_name]()


def downgrade(engine_name):
    globals()["downgrade_%s" % engine_name]()





def upgrade_():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('localinterest',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('interest_id', sa.Integer(), nullable=True),
    sa.Column('version_id', sa.Integer(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('localuser',
    sa.Column('user_id', sa.Integer(), nullable=True),
    sa.Column('email', sa.String(length=100), nullable=True),
    sa.Column('name', sa.String(length=256), nullable=True),
    sa.Column('given_name', sa.String(length=256), nullable=True),
    sa.Column('active', sa.Boolean(), nullable=True),
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('interest_id', sa.Integer(), nullable=True),
    sa.Column('version_id', sa.Integer(), nullable=False),
    sa.ForeignKeyConstraint(['interest_id'], ['localinterest.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.drop_table('roles_users')
    op.drop_index('name', table_name='role')
    op.drop_table('role')
    op.drop_index('email', table_name='user')
    op.drop_table('user')
    # ### end Alembic commands ###


def downgrade_():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('user',
    sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
    sa.Column('email', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('name', mysql.VARCHAR(length=256), nullable=True),
    sa.Column('given_name', mysql.VARCHAR(length=256), nullable=True),
    sa.Column('last_login_at', mysql.DATETIME(), nullable=True),
    sa.Column('current_login_at', mysql.DATETIME(), nullable=True),
    sa.Column('last_login_ip', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('current_login_ip', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('login_count', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.Column('active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('confirmed_at', mysql.DATETIME(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset='utf8',
    mysql_engine='InnoDB'
    )
    op.create_index('email', 'user', ['email'], unique=True)
    op.create_table('role',
    sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
    sa.Column('name', mysql.VARCHAR(length=32), nullable=True),
    sa.Column('description', mysql.VARCHAR(length=512), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset='utf8',
    mysql_engine='InnoDB'
    )
    op.create_index('name', 'role', ['name'], unique=True)
    op.create_table('roles_users',
    sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
    sa.Column('user_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.Column('role_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True),
    sa.ForeignKeyConstraint(['role_id'], ['role.id'], name='roles_users_ibfk_2'),
    sa.ForeignKeyConstraint(['user_id'], ['user.id'], name='roles_users_ibfk_1'),
    sa.PrimaryKeyConstraint('id'),
    mysql_default_charset='utf8',
    mysql_engine='InnoDB'
    )
    op.drop_table('localuser')
    op.drop_table('localinterest')
    # ### end Alembic commands ###