Run Alembic Operation Objects Directly (as in from autogenerate)

The Operations object has a method known as Operations.invoke() that will generically invoke a particular operation object. We can therefore use the autogenerate.produce_migrations() function to run an autogenerate comparison, get back a ops.MigrationScript structure representing the changes, and with a little bit of insider information we can invoke them directly.

Operations 对象有一个称为 Operations.invoke() 的方法,它通常会调用特定的操作对象。 因此,我们可以使用 autogenerate.produce_migrations() 函数来运行自动生成比较,返回一个表示更改的 ops.MigrationScript 结构,并且通过一些内部信息,我们可以直接调用它们。

The traversal through the ops.MigrationScript structure is as follows:

通过 ops.MigrationScript 结构的遍历如下:

use_batch = engine.name == "sqlite" stack = [migrations.upgrade_ops] while stack: elem = stack.pop(0) if use_batch and isinstance(elem, ModifyTableOps): with operations.batch_alter_table( elem.table_name, schema=elem.schema ) as batch_ops: for table_elem in elem.ops: # work around Alembic issue #753 (fixed in 1.5.0) if hasattr(table_elem, "column"): table_elem.column = table_elem.column.copy() batch_ops.invoke(table_elem) elif hasattr(elem, "ops"): stack.extend(elem.ops) else: # work around Alembic issue #753 (fixed in 1.5.0) if hasattr(elem, "column"): elem.column = elem.column.copy() operations.invoke(elem)

Above, we detect elements that have a collection of operations by looking for the .ops attribute. A check for ModifyTableOps allows us to use a batch context if we are supporting that. Finally there’s a workaround for an Alembic issue that exists for SQLAlchemy 1.3.20 and greater combined with Alembic older than 1.5.

上面,我们通过查找 .ops 属性来检测具有操作集合的元素。 如果我们支持,对 ModifyTableOps 的检查允许我们使用批处理上下文。 最后,对于 SQLAlchemy 1.3.20 和更高版本以及早于 1.5 的 Alembic 存在的 Alembic 问题,有一个解决方法。

A full example follows. The overall setup here is copied from the example at autogenerate.compare_metadata():

下面是一个完整的例子。 这里的整体设置是从 autogenerate.compare_metadata() 的示例复制而来的:

from sqlalchemy import Column from sqlalchemy import create_engine from sqlalchemy import Integer from sqlalchemy import MetaData from sqlalchemy import String from sqlalchemy import Table from alembic.autogenerate import produce_migrations from alembic.migration import MigrationContext from alembic.operations import Operations from alembic.operations.ops import ModifyTableOps engine = create_engine("sqlite://", echo=True) with engine.connect() as conn: conn.execute( """ create table foo ( id integer not null primary key, old_data varchar(50), x integer )""" ) conn.execute( """ create table bar ( data varchar(50) )""" ) metadata = MetaData() Table( "foo", metadata, Column("id", Integer, primary_key=True), Column("data", Integer), Column("x", Integer, nullable=False), ) Table("bat", metadata, Column("info", String(100))) mc = MigrationContext.configure(engine.connect()) migrations = produce_migrations(mc, metadata) operations = Operations(mc) use_batch = engine.name == "sqlite" stack = [migrations.upgrade_ops] while stack: elem = stack.pop(0) if use_batch and isinstance(elem, ModifyTableOps): with operations.batch_alter_table( elem.table_name, schema=elem.schema ) as batch_ops: for table_elem in elem.ops: # work around Alembic issue #753 (fixed in 1.5.0) if hasattr(table_elem, "column"): table_elem.column = table_elem.column.copy() batch_ops.invoke(table_elem) elif hasattr(elem, "ops"): stack.extend(elem.ops) else: # work around Alembic issue #753 (fixed in 1.5.0) if hasattr(elem, "column"): elem.column = elem.column.copy() operations.invoke(elem)