首页 文章

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

提问于
浏览
36

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

"""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 回答

  • 25

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

    from extensions import bcrypt, db
    
    class User(db.Model):
        # this config is used by sqlalchemy to store model data in the database
        __tablename__ = '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)
    
  • 4

    作为其中一项业务,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-> 4要求初始迁移中的种子数据位于数据库中) . 您现在需要修改Alembic的默认运行方式来运行这些脚本 . 而且这仍然没有忽视这些脚本必须随时间变化的问题,以及谁知道您从源代码管理中检出的应用程序的版本 .

  • 61

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

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

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

    那么你运行:

    python manager.py seed
    
  • 2

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

    Warning 对于正常的数据库信息,Miguel的答案是准确的 . 也就是说,应该遵循他的建议,绝对不要使用这种方法用"normal"行填充数据库 . 这种方法专门用于应用程序运行所需的数据库行,这种数据我认为是"seed"数据 .

    OP's script modified to seed data:

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

    Context for those new to flask_migrate

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

    What changed?

    很少 . 您将注意到,在新文件中,我将 create_tablelist_type 返回的表存储在名为 list_type_table 的变量中 . 然后我们继续经营该表使用 op.bulk_insert 创建一些示例行 .

相关问题