web-dev-qa-db-ja.com

フラスコ移行またはアレンビック移行でシードデータを作成する

最初の移行でシードデータを挿入するにはどうすればよいですか?移行がこれに最適な場所ではない場合、ベストプラクティスは何ですか?

"""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 ###
43
Mark Richman

Alembicの操作の1つとして、 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の通常の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 [〜#〜] (アプリケーションで既に定義されているテーブルではありません)が、実際には非常に必要です。アプリケーションの一部であるテーブルまたはモデル定義を使用しようとした場合、アプリケーションでテーブル/モデルに変更を加えると、この移行が中断されます。移行スクリプトは明確に設定する必要があります。モデルの将来のバージョンを変更しても、移行スクリプトは変更されません。アプリケーションモデルを使用すると、チェックアウトしたモデルのバージョン(ほとんどの場合最新のもの)に応じて定義が変更されます。そのため、移行スクリプトに含まれるテーブル定義が必要です。

もう1つの話は、独自のコマンドとして実行されるスクリプトにシードデータを配置する必要があるかどうかです(他の回答に示されているように、Flask-Scriptコマンドを使用するなど)。これは使用できますが、注意が必要です。ロードするデータがテストデータである場合、それは1つのことです。しかし、「シードデータ」とは、アプリケーションが正しく機能するために必要なデータを意味すると理解しています。たとえば、「ロール」テーブルで「管理者」と「ユーザー」のレコードを設定する必要がある場合。このデータは、移行の一部として挿入する必要があります。スクリプトはデータベースの最新バージョンでのみ機能するのに対し、移行は移行先の特定のバージョンで機能することに注意してください。ロール情報をロードするスクリプトが必要な場合は、「ロール」テーブルのスキーマが異なるデータベースのすべてのバージョンのスクリプトが必要になる場合があります。

また、スクリプトに依存することにより、移行間でスクリプトを実行するのがより難しくなります(たとえば、移行3から4では、初期移行のシードデータがデータベースに存在する必要があります)。これらのスクリプトを実行するには、Alembicのデフォルトの実行方法を変更する必要があります。そして、それはこれらのスクリプトが時間とともに変化しなければならないという事実と、ソース管理からチェックアウトしたアプリケーションのバージョンを誰が知っているかという問題をまだ無視していません。

65
Mark Hildreth

移行はスキーマの変更のみに限定する必要があり、それだけでなく、移行のアップまたはダウンを適用するときに、以前からデータベースに存在していたデータを可能な限り保持することが重要です。移行の一部としてシードデータを挿入すると、既存のデータが台無しになる場合があります。

Flaskのほとんどの機能と同様に、これをさまざまな方法で実装できます。私の意見では、Flask-Scriptに新しいコマンドを追加することはこれを行う良い方法です。例えば:

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

だから、あなたは実行します:

python manager.py seed
24
Miguel

MarkHildrethは、アレンビックがこれをどのように処理できるかについての優れた説明を提供しました。ただし、OPは、フラスコ移行移行スクリプトを変更する方法に特化しています。以下の回答を投稿して、人々がアレンビックを調べる時間を節約します。

警告ミゲルの答えは、通常のデータベース情報に関して正確です。つまり、彼のアドバイスに従うべきであり、このアプローチを使用してデータベースに「通常の」行を入れることは絶対にしないでください。このアプローチは、アプリケーションが機能するために必要なデータベース行、つまり「シード」データと考える一種のデータ専用です。

シードデータに変更された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には、これらの自動生成された移行スクリプトの1つの例が含まれています。シードデータを追加するには、適切な自動生成された移行ファイルを手動で変更する必要があります。上記のコードはその一例です。

変更点

ほんの少し。新しいファイルでは、create_tableと呼ばれる変数にlist_typeから返されるテーブルをlist_type_tableから保存していることに注意してください。次に、op.bulk_insertを使用してそのテーブルを操作し、いくつかのサンプル行を作成します。

5
melchoir55

また、Pythonのフェイカーライブラリを使用することもできます。これは、データを自分で作成する必要がないため、少し速くなる場合があります。それを設定する1つの方法は、以下に示すように、データを生成したいクラスにメソッドを配置することです。

from extensions import bcrypt, db

class User(db.Model):
    # this config is used by sqlalchemy to store model data in the database
    __table= 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(150))
    email = db.Column(db.String(100), unique=True)
    password = db.Column(db.String(100))

    def __init__(self, name, email, password, fav_movie):
        self.name = name
        self.email = email
        self.password = password

    @classmethod
    def seed(cls, fake):
        user = User(
            name = fake.name(),
            email = fake.email(),
            password = cls.encrypt_password(fake.password()),
        )
        user.save()

    @staticmethod
    def encrypt_password(password):
        return bcrypt.generate_password_hash(password).decode('utf-8')

    def save(self):
        db.session.add(self)
        db.session.commit()

次に、次のように見えるシードメソッドを呼び出すメソッドを実装します。

from faker import Faker
from users.models import User

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