2013-10-12 51 views
30

如何在我的第一次迁移中插入一些种子数据?如果迁移不是最好的地方,那么最佳做法是什么?在烧瓶中创建种子数据 - 迁移或alembic迁移

"""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 ### 
+0

对文档的轻微更新显示了如何创建表,然后立即从创建的表中批量插入:http://alembic.readthedocs.org/en/latest/ops.html#alembic.operations.Operations.create_table – iJames

+0

在创建种子数据方面,您可以查看https://github.com/FactoryBoy/factory_boy和https://github.com/heavenshell/py-sqlalchemy_seed –

+0

另请参阅https://github.com/klen/mixer –

回答

52

蒸馏器具有作为其操作之一,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)}, 
    ] 
) 

还需要注意的是,蒸馏器有一个execute()操作,这就像在SQLAlchemy的正常​​功能:你可以运行你的任何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”的记录。这个数据应该作为迁移的一部分插入。请记住,脚本只能用于数据库的最新版本,而迁移将与您要迁移到的特定版本一起使用。如果您想要脚本加载角色信息,则可能需要针对每个版本的数据库使用脚本,并为“角色”表使用不同的架构。

另外,通过依赖脚本,您可能会在迁移之间运行脚本变得更加困难(例如迁移3-> 4要求初始迁移中的种子数据位于数据库中)。您现在需要修改Alembic的默认运行方式来运行这些脚本。而且,这些脚本将不得不随时间而改变,并且谁知道您从源代码管理中签出的应用程序的版本是否仍然存在问题。

+2

是否与'bulk_insert()'相反?我相信没有,这会降低写下降级的难度。即使有bulk_delete,如果应用程序更改了数据并且看起来与通过'bulk_insert'插入数据时看起来完全不同,你会怎么做?如果在同一次迁移中添加了表,那么降级只是安全的,因为在这种情况下,您必须删除表,但其他情况不容易处理。不过,我不觉得有必要降低你的答案。 – Miguel

+3

如果在创建表(通常是这种情况)时执行了'bulk_insert',则删除表就足够了。否则,你可以使用'execute'来删除。这对于以这种方式使用alembic不是问题,这是数据库迁移的问题。它们并不容易,没有工具可以让它们变得简单(只是让它们更容易)。另外,我在添加评论后删除了我的downvote。没有难过的感觉:) –

+0

@MarkHildreth我用你的方法去了,因为我在这个迁移中存储在表中的所有常量都是必需的,而且这个表是只读的。我同意特设表非常干。谢谢!!! –

25

迁移应仅限于架构的改变而已,不仅如此,重要的是,当应用迁移上涨或下跌,从之前在数据库中存在的数据被保存尽可能多地。作为迁移的一部分插入种子数据可能会混淆已有的数据。

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

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

,那么你运行:

python manager.py seed 
+0

为什么有人会投票呢? –

+19

对不起,有点触发很高兴,但我强烈反对:“迁移应该只限于模式更改”。如果你想使种子数据成为一个单独的命令,答案是好的。但是,例如,如果您想安装诸如“角色”(管理员,用户等)之类的东西,那么进行迁移就非常合适。事实上,添加命令而不是将其放入迁移中意味着您现在必须在安装过程中执行两个步骤(迁移,数据加载)而不是一个步骤。取决于你的环境,无论哪种方式。但请不要说迁移应该是“有限”的。 –

+0

好吧,马克让你如何做到这一点,作为上述迁移的一部分? –

2

MarkHildreth提供了一个关于alembic如何处理这个问题的绝佳解释。但是,操作系统具体是关于如何修改烧瓶迁移迁移脚本。我将在下面发布一个答案,以便让人们不必再去看看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

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

什么改变?

很少。你会注意到,在新文件中,我将从create_table返回的表格存储在list_type的变量中,名称为list_type_table。然后,我们使用op.bulk_insert在该表上操作以创建几个示例行。