在flask-migrate或alembic迁移中创建种子数据

时间:2013-10-12 13:15:09

标签: python flask flask-sqlalchemy alembic flask-migrate

如何在首次迁移中插入一些种子数据?如果迁移不是最好的地方,那么最佳做法是什么?

"""empty message

Revision ID: 384cfaaaa0be
Revises: None
Create Date: 2013-10-11 16:36:34.696069

"""

# revision identifiers, used by Alembic.
revision = '384cfaaaa0be'
down_revision = None

from alembic import op
import sqlalchemy as sa


def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.create_table('list_type',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=80), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('name')
    )
    op.create_table('job',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('list_type_id', sa.Integer(), nullable=False),
    sa.Column('record_count', sa.Integer(), nullable=False),
    sa.Column('status', sa.Integer(), nullable=False),
    sa.Column('sf_job_id', sa.Integer(), nullable=False),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('compressed_csv', sa.LargeBinary(), nullable=True),
    sa.ForeignKeyConstraint(['list_type_id'], ['list_type.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    ### end Alembic commands ###

    # ==> INSERT SEED DATA HERE <==


def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('job')
    op.drop_table('list_type')
    ### end Alembic commands ###

4 个答案:

答案 0 :(得分:63)

Alembic作为其中一项行动bulk_insert()。该文档给出了以下示例(我已经包含了一些修复):

from datetime import date
from sqlalchemy.sql import table, column
from sqlalchemy import String, Integer, Date
from alembic import op

# Create an ad-hoc table to use for the insert statement.
accounts_table = table('account',
    column('id', Integer),
    column('name', String),
    column('create_date', Date)
)

op.bulk_insert(accounts_table,
    [
        {'id':1, 'name':'John Smith',
                'create_date':date(2010, 10, 5)},
        {'id':2, 'name':'Ed Williams',
                'create_date':date(2007, 5, 27)},
        {'id':3, 'name':'Wendy Jones',
                'create_date':date(2008, 8, 15)},
    ]
)

另请注意,alembic有一个execute()操作,就像SQLAlchemy中的普通execute()函数一样:您可以运行任何您想要的SQL,如文档示例所示:

from sqlalchemy.sql import table, column
from sqlalchemy import String
from alembic import op

account = table('account',
    column('name', String)
)
op.execute(
    account.update().\
        where(account.c.name==op.inline_literal('account 1')).\
        values({'name':op.inline_literal('account 2')})
        )

请注意,用于创建update语句中使用的元数据的表是直接在架构中定义的。这可能看起来像是DRY(不是您应用程序中已定义的表),但实际上非常必要。如果您尝试使用属于应用程序的表或模型定义,则在应用程序中对表/模型进行更改时,将会中断此迁移。您的迁移脚本应该一成不变:对模型的未来版本的更改不应更改迁移脚本。使用应用程序模型意味着定义将根据您检出的模型版本(最有可能是最新版本)而改变。因此,您需要在迁移脚本中自包含表定义。

要讨论的另一件事是你是否应该将种子数据放入一个作为自己的命令运行的脚本(例如使用Flask-Script命令,如另一个答案中所示)。这可以使用,但你应该小心。如果您加载的数据是测试数据,那么这是一回事。但我理解“种子数据”是指应用程序正常工作所需的数据。例如,如果您需要在“角色”表中为“admin”和“user”设置记录。该数据应该作为迁移的一部分插入。请记住,脚本只能与最新版本的数据库一起使用,而迁移将与您要迁移到的特定版本一起使用。如果您希望脚本加载角色信息,则可能需要为每个版本的数据库创建一个脚本,并为“roles”表提供不同的模式。

此外,通过依赖脚本,您将使在迁移之间运行脚本变得更加困难(例如,迁移3-&gt; 4要求初始迁移中的种子数据位于数据库中)。您现在需要修改Alembic的默认运行方式来运行这些脚本。而且这仍然没有忽略这些脚本随着时间的推移而变化的问题,以及谁知道你从源代码控制中检出的应用程序的版本。

答案 1 :(得分:26)

迁移应该仅限于架构更改,不仅如此,重要的是,当应用向上或向下迁移时,数据库之前存在的数据将尽可能保留。作为迁移的一部分插入种子数据可能会破坏预先存在的数据。

与Flask的大多数事情一样,您可以通过多种方式实现这一点。在我看来,向Flask-Script添加一个新命令是一种很好的方法。例如:

@manager.command
def seed():
    "Add seed data to the database."
    db.session.add(...)
    db.session.commit()

然后你跑:

python manager.py seed

答案 2 :(得分:5)

MarkHildreth提供了一个很好的解释,说明alembic如何处理这个问题。但是,OP专门针对如何修改flask-migration迁移脚本。我将在下面发布一个答案,以节省人们根本不需要查看alembic的时间。

警告 对于正常的数据库信息,Miguel的答案是准确的。也就是说,应该遵循他的建议,绝对不要使用这种方法用“普通”行填充数据库。这种方法专门用于应用程序运行所需的数据库行,这是一种我认为是“种子”数据的数据。

OP的脚本被修改为种子数据:

"""empty message

Revision ID: 384cfaaaa0be
Revises: None
Create Date: 2013-10-11 16:36:34.696069

"""

# revision identifiers, used by Alembic.
revision = '384cfaaaa0be'
down_revision = None

from alembic import op
import sqlalchemy as sa


def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    list_type_table = op.create_table('list_type',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=80), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('name')
    )
    op.create_table('job',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('list_type_id', sa.Integer(), nullable=False),
    sa.Column('record_count', sa.Integer(), nullable=False),
    sa.Column('status', sa.Integer(), nullable=False),
    sa.Column('sf_job_id', sa.Integer(), nullable=False),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('compressed_csv', sa.LargeBinary(), nullable=True),
    sa.ForeignKeyConstraint(['list_type_id'], ['list_type.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    ### end Alembic commands ###


    op.bulk_insert(
        list_type_table,
        [
            {'name':'best list'},
            {'name': 'bester list'}
        ]
    )


def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('job')
    op.drop_table('list_type')
    ### end Alembic commands ###

来自flask_migrate的新手

的上下文

Flask migrate会在migrations/versions生成迁移脚本。这些脚本按顺序在数据库上运行,以便将其升级到最新版本。 OP包括这些自动生成的迁移脚本之一的示例。要添加种子数据,必须手动修改相应的自动生成的迁移文件。我上面发布的代码就是一个例子。

改变了什么?

很少。您将注意到,在新文件中,我将从create_table返回的表list_type存储在名为list_type_table的变量中。然后,我们使用op.bulk_insert对该表进行操作,以创建一些示例行。

答案 3 :(得分:2)

您也可以使用Python的faker库,这可能会更快,因为您不需要自己提供任何数据。配置它的一种方法是将方法放在要生成数据的类中,如下所示。

from faker import Faker
from users.models import User

fake = Faker()
    for _ in range(100):
        User.seed(fake)

然后实现一个调用种子方法的方法,该方法看起来像这样:

class Handler<E> {
    Function<Filter, E> getter;
    BiConsumer<Filter, E> setter;
    public Handler(Function<Filter, E> getter, BiConsumer<Filter, E> setter) {
        this.getter = getter;
        this.setter = setter;
    }
}