2013-10-12 50 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在該表上操作以創建幾個示例行。