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)