without script query example python flask flask-sqlalchemy alembic flask-migrate

python - script - sql alchemy migrate



Crear datos de inicialización en una migración de matraz migratoria o alambique (4)

¿Cómo puedo insertar algunos datos iniciales en mi primera migración? Si la migración no es el mejor lugar para esto, ¿cuál es la mejor práctica?

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


Alambic tiene, como una de sus operaciones, bulk_insert() . La documentación brinda el siguiente ejemplo (con algunas correcciones que he incluido):

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)}, ] )

Tenga en cuenta también que el alambique tiene una operación execute() , que es como la función normal execute() en SQLAlchemy: puede ejecutar cualquier SQL que desee, como muestra el ejemplo de documentación:

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'')}) )

Observe que la tabla que se utiliza para crear los metadatos que se utiliza en la declaración de update se define directamente en el esquema. Esto puede parecer que rompe DRY (no es la tabla ya definida en su aplicación), pero en realidad es bastante necesario. Si tratara de usar la tabla o la definición de modelo que es parte de su aplicación, rompería esta migración cuando realice cambios en su tabla / modelo en su aplicación. Sus scripts de migración deben ser inamovibles: un cambio a una versión futura de sus modelos no debe cambiar los scripts de migración. El uso de los modelos de la aplicación significará que las definiciones cambiarán según la versión de los modelos que haya revisado (lo más probable es que sea la última). Por lo tanto, necesita que la definición de tabla sea independiente en el script de migración.

Otra cosa de la que hablar es si debe poner sus datos iniciales en un script que se ejecute como su propio comando (como usar un comando Flask-Script, como se muestra en la otra respuesta). Esto se puede usar, pero debes tener cuidado al respecto. Si los datos que está cargando son datos de prueba, eso es una cosa. Pero entendí que "datos iniciales" significa los datos que se requieren para que la aplicación funcione correctamente. Por ejemplo, si necesita configurar registros para "admin" y "usuario" en la tabla "roles". Esta información DEBE insertarse como parte de las migraciones. Recuerde que un script solo funcionará con la última versión de su base de datos, mientras que una migración funcionará con la versión específica desde la que está migrando. Si desea que un script cargue la información de roles, puede necesitar un script para cada versión de la base de datos con un esquema diferente para la tabla de "roles".

Además, al basarse en un script, le resultaría más difícil ejecutar el script entre migraciones (por ejemplo, la migración 3-> 4 requiere que los datos iniciales en la migración inicial estén en la base de datos). Ahora necesita modificar la forma predeterminada de ejecución de Alembic para ejecutar estos scripts. Y eso aún no ignora los problemas con el hecho de que estas secuencias de comandos tendrían que cambiar con el tiempo, y quién sabe qué versión de la aplicación ha retirado del control de la fuente.


Las migraciones deben limitarse solo a cambios de esquema, y ​​no solo eso, es importante que cuando se aplica una migración hacia arriba o hacia abajo, los datos que existieron en la base de datos desde antes se conserven tanto como sea posible. Insertar datos iniciales como parte de una migración puede arruinar datos preexistentes.

Como la mayoría de las cosas con Flask, puedes implementar esto de muchas maneras. Agregar un nuevo comando a Flask-Script es una buena manera de hacerlo, en mi opinión. Por ejemplo:

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

Entonces corres:

python manager.py seed


MarkHildreth ha proporcionado una excelente explicación de cómo el alambique puede manejar esto. Sin embargo, el OP fue específicamente sobre cómo modificar una secuencia de comandos de migración de matraz. Voy a publicar una respuesta a la siguiente para salvar a la gente el tiempo de tener que mirar el alambique en absoluto.

Advertencia La respuesta de Miguel es precisa con respecto a la información de base de datos normal. Es decir, uno debe seguir su consejo y no utilizar este enfoque para completar una base de datos con filas "normales". Este enfoque es específicamente para filas de bases de datos que se requieren para que la aplicación funcione, un tipo de datos que considero datos de "semilla".

La secuencia de comandos de OP modificada para generar datos:

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

Contexto para los nuevos en flask_migrate

Flask migrate genera scripts de migrations/versions en migrations/versions . Estos scripts se ejecutan en orden en una base de datos para llevarlos a la última versión. El OP incluye un ejemplo de uno de estos scripts de migración autogenerados. Para agregar datos iniciales, se debe modificar manualmente el archivo de migración generado automáticamente. El código que publiqué arriba es un ejemplo de eso.

¿Qué cambió?

Muy poco. create_table que en el nuevo archivo estoy almacenando la tabla devuelta desde create_table para list_type en una variable llamada list_type_table . Luego operamos en esa tabla usando op.bulk_insert para crear algunas filas de ejemplo.


También puede usar la biblioteca de fakers de Python, que puede ser un poco más rápida, ya que no necesita obtener ningún dato usted mismo. Una forma de configurarlo sería poner un método en una clase para la que desea generar datos, como se muestra a continuación.

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()

Y luego implemente un método que llame al método seed, que podría verse más o menos así:

from faker import Faker from users.models import User fake = Faker() for _ in range(100): User.seed(fake)