对 Alembic 升级进行 dry-运行
Do a dry-run of an Alembic upgrade
有时 alembic upgrade head
可能会在 运行 时对我的生产数据库失败,即使它对我的测试数据库运行良好。例如,迁移可能会向我的测试环境中以前不包含 NULL
的列添加 NOT NULL
约束,但 did 包含 NULL
正在制作中。
规划部署时,最好能够在 运行 迁移之前检查它是否能够干净利落地应用。对于像 MySQL 这样不支持事务性 DDL(在事务中进行模式更改)的数据库,这大概是不可能的,但原则上对于 do 支持的数据库应该是可能的事务性 DDL,如 PostgreSQL; Alembic 可以尝试在事务中执行升级,然后将其回滚。
(一个警告:这是一个不完美的解决方案,因为 PostgreSQL 允许某些约束为 DEFERRED
,这意味着它们在您提交之前不会被检查。检查这些的 dry-运行 是,我想,如果不创建数据库副本是不可能的。但是,执行 DDL 和回滚方法总比没有好。)
Alembic 是否支持此类功能?如果没有,是否有一些 hacky 方法来实现它?
您可以创建自己的 alembic 配置、脚本目录和环境上下文。至关重要的是,这应该有一个已经创建的连接传递给它,您可以在完成 dry-运行.
后回滚
engine = sa.create_engine(url)
session = Session(bind=engine)
try:
alembic_config = AlembicConfig(config_args={...})
alembic_config.attributes.update({"connection": session.connection()})
script_directory = ScriptDirectory.from_config(alembic_config)
env_context = EnvironmentContext(
alembic_config,
script_directory,
fn=lambda rev, context: script_directory._upgrade_revs("head", rev),
as_sql=False,
)
env_context.configure(connection=session.connection())
env_context.run_migrations()
finally:
session.rollback()
session.close()
实现这一点的一个简单技巧是在 env.py
中的 run_migrations_online
函数中注入一个条件回滚,只有当某些标志出现时才会触发,表明我们想要一个 dry-运行.
如果你的已经被修改,请回想一下 alembic init
创建的 run_migrations_online
函数的默认实现如下所示:
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
注意:
context.begin_transaction()
的 __enter__
方法——我们已经在默认实现中调用了它——为我们提供了一个带有 rollback()
方法的交易对象,if后端使用事务性 DDL,或者如果事务性 ddl 被强制使用 transactional_ddl 标志 和
- 我们的
context
对象有一个 get_x_argument
方法,我们可以使用它来支持将自定义参数传递给 alembic
命令。
因此,通过以下小改动(除了添加 as transaction
和最后三行之外,下面的所有内容都是相同的)我们可以拥有我们的 dry-运行 功能:
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
# ensure the context will create a transaction
# for backends that dont normally use transactional DDL.
# note that ROLLBACK will not roll back DDL structures
# on databases such as MySQL, as well as with SQLite
# Python driver's default settings
transactional_ddl=True,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction() as transaction:
context.run_migrations()
if 'dry-run' in context.get_x_argument():
print('Dry-run succeeded; now rolling back transaction...')
transaction.rollback()
现在,做一个干-运行,做:
alembic -x dry-run upgrade head
并做一个真正的 运行,只需做:
alembic upgrade head
和以前一样。
有时 alembic upgrade head
可能会在 运行 时对我的生产数据库失败,即使它对我的测试数据库运行良好。例如,迁移可能会向我的测试环境中以前不包含 NULL
的列添加 NOT NULL
约束,但 did 包含 NULL
正在制作中。
规划部署时,最好能够在 运行 迁移之前检查它是否能够干净利落地应用。对于像 MySQL 这样不支持事务性 DDL(在事务中进行模式更改)的数据库,这大概是不可能的,但原则上对于 do 支持的数据库应该是可能的事务性 DDL,如 PostgreSQL; Alembic 可以尝试在事务中执行升级,然后将其回滚。
(一个警告:这是一个不完美的解决方案,因为 PostgreSQL 允许某些约束为 DEFERRED
,这意味着它们在您提交之前不会被检查。检查这些的 dry-运行 是,我想,如果不创建数据库副本是不可能的。但是,执行 DDL 和回滚方法总比没有好。)
Alembic 是否支持此类功能?如果没有,是否有一些 hacky 方法来实现它?
您可以创建自己的 alembic 配置、脚本目录和环境上下文。至关重要的是,这应该有一个已经创建的连接传递给它,您可以在完成 dry-运行.
后回滚engine = sa.create_engine(url)
session = Session(bind=engine)
try:
alembic_config = AlembicConfig(config_args={...})
alembic_config.attributes.update({"connection": session.connection()})
script_directory = ScriptDirectory.from_config(alembic_config)
env_context = EnvironmentContext(
alembic_config,
script_directory,
fn=lambda rev, context: script_directory._upgrade_revs("head", rev),
as_sql=False,
)
env_context.configure(connection=session.connection())
env_context.run_migrations()
finally:
session.rollback()
session.close()
实现这一点的一个简单技巧是在 env.py
中的 run_migrations_online
函数中注入一个条件回滚,只有当某些标志出现时才会触发,表明我们想要一个 dry-运行.
如果你的已经被修改,请回想一下 alembic init
创建的 run_migrations_online
函数的默认实现如下所示:
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
注意:
context.begin_transaction()
的__enter__
方法——我们已经在默认实现中调用了它——为我们提供了一个带有rollback()
方法的交易对象,if后端使用事务性 DDL,或者如果事务性 ddl 被强制使用 transactional_ddl 标志 和- 我们的
context
对象有一个get_x_argument
方法,我们可以使用它来支持将自定义参数传递给alembic
命令。
因此,通过以下小改动(除了添加 as transaction
和最后三行之外,下面的所有内容都是相同的)我们可以拥有我们的 dry-运行 功能:
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
# ensure the context will create a transaction
# for backends that dont normally use transactional DDL.
# note that ROLLBACK will not roll back DDL structures
# on databases such as MySQL, as well as with SQLite
# Python driver's default settings
transactional_ddl=True,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction() as transaction:
context.run_migrations()
if 'dry-run' in context.get_x_argument():
print('Dry-run succeeded; now rolling back transaction...')
transaction.rollback()
现在,做一个干-运行,做:
alembic -x dry-run upgrade head
并做一个真正的 运行,只需做:
alembic upgrade head
和以前一样。