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

Python 在flask迁移或alembic迁移中创建种子数据,python,flask,flask-sqlalchemy,alembic,flask-migrate,Python,Flask,Flask Sqlalchemy,Alembic,Flask Migrate,如何在第一次迁移中插入一些种子数据?如果迁移不是实现这一点的最佳场所,那么最佳实践是什么 """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

如何在第一次迁移中插入一些种子数据?如果迁移不是实现这一点的最佳场所,那么最佳实践是什么

"""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 ###
“空消息
修订ID:384cfaaaa0be
修订:无
创建日期:2013-10-11 16:36:34.696069
"""
#Alembic使用的修订标识符。
修订版='384cfaaaa0be'
向下修订=无
从alembic导入op
将sqlalchemy作为sa导入
def升级():
###Alembic自动生成的命令-请调整###
op.create_表('列表类型',
sa.Column('id',sa.Integer(),null=False),
sa.Column('name',sa.String(长度=80),nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('名称')
)
op.create_表格('作业',
sa.Column('id',sa.Integer(),null=False),
sa.Column('list\u type\u 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\u type\u id'],['list\u type.id'],),
sa.PrimaryKeyConstraint('id'))
)
###结束Alembic命令###

#==>此处插入种子数据迁移应仅限于模式更改,而且不仅如此,在应用向上或向下迁移时,数据库中以前存在的数据应尽可能保留。作为迁移的一部分插入种子数据可能会弄乱预先存在的数据

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

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

python manager.py seed

Alembic作为其业务之一。文档给出了以下示例(包括一些修复):

还要注意的是,alembic有一个操作,它与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
语句中使用的元数据的表直接在模式中定义。这看起来像是中断了(不是应用程序中已经定义的表),但实际上是非常必要的。如果尝试使用作为应用程序一部分的表或模型定义,则在应用程序中更改表/模型时,会中断此迁移。您的迁移脚本应该一成不变:对模型未来版本的更改不应更改迁移脚本。使用应用程序模型意味着定义将根据您签出的模型的版本(很可能是最新版本)而更改。因此,您需要表定义在迁移脚本中是自包含的

另一件要讨论的事情是,您是否应该将种子数据放入作为自己的命令运行的脚本中(例如使用Flask脚本命令,如另一个答案中所示)。这是可以使用的,但是你应该小心。如果您正在加载的数据是测试数据,那么这是一回事。但我把“种子数据”理解为应用程序正常运行所需的数据。例如,如果需要在“角色”表中设置“管理员”和“用户”的记录。此数据应作为迁移的一部分插入。请记住,脚本只适用于数据库的最新版本,而迁移将适用于您要迁移到的或从中迁移的特定版本。如果您想要一个脚本来加载角色信息,您可能需要为数据库的每个版本使用一个脚本,并为“角色”表使用不同的模式


此外,通过依赖脚本,在迁移之间运行脚本会变得更加困难(例如,迁移3->4要求初始迁移中的种子数据位于数据库中)。现在需要修改Alembic的默认运行方式来运行这些脚本。这仍然没有忽略这些脚本必须随着时间的推移而改变这一事实的问题,谁知道您已经从源代码管理中签出了应用程序的哪个版本。

MarkHildreth对alembic如何处理这一问题提供了极好的解释。然而,OP专门讨论了如何修改flask迁移脚本。我将在下面发布一个答案,以节省人们研究alembic的时间

警告 米格尔的回答与正常数据库信息相比是准确的。也就是说,应该遵循他的建议,绝对不要使用这种方法用“正常”行填充数据库。这种方法专门用于应用程序运行所需的数据库行,我认为这是一种“种子”数据

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在迁移/版本中生成迁移脚本。这些脚本在数据库上按顺序运行,以便将其更新为最新版本。OP包括这些自动生成的迁移脚本之一的示例。为了添加种子数据,必须手动修改相应的自动生成的迁移文件。我上面发布的代码就是一个例子

发生了什么变化?


很少。您会注意到,在新文件中,我将从
create\u table
list\u type
返回的表存储在名为
list\u type\u table
的变量中。然后,我们使用
op.bulk\u insert
对该表进行操作,以创建一些示例行。

您也可以使用Python的faker库,这可能会更快一些,因为您不需要自己提供任何数据。一w
"""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 ###
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)