flask-migrate/alembic - 如何跳过特定的 sqlalchemy 绑定
flask-migrate/alembic - how to skip a specific sqlalchemy bind
有什么方法可以使用 flask db migrate / alembic 跳过绑定吗?
我有两个 SQLALCHEMY_BINDS 使用公共数据库的存储库,但数据库不同。在我的例子中,成员回购使用 db members
、SQLALCHEMY_BINDS={'users': usersdb_uri}
,合同回购使用 db contracts
、SQLALCHEMY_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 ###
有什么方法可以使用 flask db migrate / alembic 跳过绑定吗?
我有两个 SQLALCHEMY_BINDS 使用公共数据库的存储库,但数据库不同。在我的例子中,成员回购使用 db members
、SQLALCHEMY_BINDS={'users': usersdb_uri}
,合同回购使用 db contracts
、SQLALCHEMY_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 ###