最初の移行でシードデータを挿入するにはどうすればよいですか?移行がこれに最適な場所ではない場合、ベストプラクティスは何ですか?
"""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 ###
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のデフォルトの実行方法を変更する必要があります。そして、それはこれらのスクリプトが時間とともに変化しなければならないという事実と、ソース管理からチェックアウトしたアプリケーションのバージョンを誰が知っているかという問題をまだ無視していません。
移行はスキーマの変更のみに限定する必要があり、それだけでなく、移行のアップまたはダウンを適用するときに、以前からデータベースに存在していたデータを可能な限り保持することが重要です。移行の一部としてシードデータを挿入すると、既存のデータが台無しになる場合があります。
Flaskのほとんどの機能と同様に、これをさまざまな方法で実装できます。私の意見では、Flask-Scriptに新しいコマンドを追加することはこれを行う良い方法です。例えば:
@manager.command
def seed():
"Add seed data to the database."
db.session.add(...)
db.session.commit()
だから、あなたは実行します:
python manager.py seed
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
を使用してそのテーブルを操作し、いくつかのサンプル行を作成します。
また、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)