是否有 python-alembic 方法在删除和添加列之间转换数据?

Is there a python-alembic way to convert data between dropping and adding a column?

我有一个 sqlite3 数据库在 python3 中使用 SQLAlchemy 访问它。 我想使用数据库迁移工具 alembic 添加一个新列并删除一个旧列。简单示例:

class Model(_Base):
  __tablename__ = 'Model'
  _oid = Column('oid', sa.Integer, primary_key=True)
  _number_int = sa.Column('number_int', sa.Integer)

迁移后应该是这样的:

class Model(_Base):
  __tablename__ = 'Model'
  _oid = Column('oid', sa.Integer, primary_key=True)
  _number_str = sa.Column('number_str', sa.String(length=30))

这里的相关点是_number_int中有数据应该像这样转换成_number_str

number_conv = {1: 'one', 2: 'two', 3: 'three'}
_number_str = number_conv[_number_int]

是否有 alembic 方式 来解决这个问题?这意味着 alembic 本身是否会处理其 concept/design 中的情况? 我想知道我是否可以为此使用 alembic 工具,或者我是否必须为此编写自己的额外代码。

当然,原始数据转换起来稍微复杂一些。这只是一个例子。

这里是 alembic operation reference. There is a method called bulk_insert() 用于批量插入内容,但没有用于迁移现有内容。似乎蒸馏器没有内置。但是你可以自己实现数据迁移。

文章 "Migrating content with alembic" 中描述了一种可能的方法。您需要在迁移文件中定义中间 table,其中包含两列(number_intnumber_str):

import sqlalchemy as sa

model_helper = sa.Table(
    'Model',
    sa.MetaData(),
    sa.Column('oid', sa.Integer, primary_key=True),
    sa.Column('number_int', sa.Integer),
    sa.Column('number_str', sa.String(length=30)),
)

并使用这个中间体 table 将数据从旧列迁移到新列:

from alembic import op


def upgrade():
    # add the new column first
    op.add_column(
        'Model',
        sa.Column(
            'number_str',
            sa.String(length=30),
            nullable=True
        )
    )

    # build a quick link for the current connection of alembic
    connection = op.get_bind()

    # at this state right now, the old column is not deleted and the
    # new columns are present already. So now is the time to run the
    # content migration. We use the connection to grab all data from
    # the table, convert each number and update the row, which is 
    # identified by its id
    number_conv = {1: 'one', 2: 'two', 3: 'three'}
    for item in connection.execute(model_helper.select()):
        connection.execute(
            model_helper.update().where(
                model_helper.c.id == item.id
            ).values(
                number_str=number_conv[item.number_int]
            )
        )

    # now that all data is migrated we can just drop the old column
    # without having lost any data
    op.drop_column('Model', 'number_int')

这种方法有点嘈杂(您需要手动定义 table),但它确实有效。