¿Cómo ejecuto inserciones y actualizaciones en un script de actualización de Alembic?

Necesito alterar los datos durante una actualización de Alembic.

Actualmente tengo una mesa de ‘jugadores’ en una primera revisión:

def upgrade(): op.create_table('player', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.Unicode(length=200), nullable=False), sa.Column('position', sa.Unicode(length=200), nullable=True), sa.Column('team', sa.Unicode(length=100), nullable=True) sa.PrimaryKeyConstraint('id') ) 

Quiero introducir una mesa de ‘equipos’. He creado una segunda revisión:

 def upgrade(): op.create_table('teams', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=80), nullable=False) ) op.add_column('players', sa.Column('team_id', sa.Integer(), nullable=False)) 

Me gustaría que la segunda migración también agregue los siguientes datos:

  1. Tabla de poblar equipos:

     INSERT INTO teams (name) SELECT DISTINCT team FROM players; 
  2. Actualice players.team_id basado en players.team name:

     UPDATE players AS p JOIN teams AS t SET p.team_id = t.id WHERE p.team = t.name; 

¿Cómo ejecuto inserciones y actualizaciones dentro del script de actualización?

Related of "¿Cómo ejecuto inserciones y actualizaciones en un script de actualización de Alembic?"

Lo que solicita es una migración de datos , a diferencia de la migración de esquema que prevalece en los documentos de Alembic.

Esta respuesta asume que está utilizando declarativo (a diferencia de class-Mapper-Table o Core) para definir sus modelos. Debería ser relativamente sencillo adaptar esto a las otras formas.

Tenga en cuenta que Alembic proporciona algunas funciones básicas de datos: op.bulk_insert() y op.execute() . Si las operaciones son mínimas, úsalas. Si la migración requiere relaciones u otras interacciones complejas, prefiero usar todo el poder de los modelos y las sesiones como se describe a continuación.

El siguiente es un ejemplo de script de migración que configura algunos modelos declarativos que se utilizarán para manipular los datos en una sesión. Los puntos clave son:

  1. Defina los modelos básicos que necesita, con las columnas que necesitará. No necesita cada columna, solo la clave principal y las que usará.
  2. Dentro de la función de actualización, use op.get_bind() para obtener la conexión actual, y haga una sesión con ella.

    • O use bind.execute() para usar el nivel inferior de SQLAlchemy para escribir consultas SQL directamente. Esto es útil para migraciones simples.
  3. Use los modelos y la sesión como lo haría normalmente en su aplicación.

 """create teams table Revision ID: 169ad57156f0 Revises: 29b4c2bfce6d Create Date: 2014-06-25 09:00:06.784170 """ revision = '169ad57156f0' down_revision = '29b4c2bfce6d' from alembic import op import sqlalchemy as sa from sqlalchemy import orm from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Player(Base): __tablename__ = 'players' id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String, nullable=False) team_name = sa.Column('team', sa.String, nullable=False) team_id = sa.Column(sa.Integer, sa.ForeignKey('teams.id'), nullable=False) team = orm.relationship('Team', backref='players') class Team(Base): __tablename__ = 'teams' id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String, nullable=False, unique=True) def upgrade(): bind = op.get_bind() session = orm.Session(bind=bind) # create the teams table and the players.team_id column Team.__table__.create(bind) op.add_column('players', sa.Column('team_id', sa.ForeignKey('teams.id'), nullable=False) # create teams for each team name teams = {name: Team(name=name) for name in session.query(Player.team).distinct()} session.add_all(teams.values()) # set player team based on team name for player in session.query(Player): player.team = teams[player.team_name] session.commit() # don't need team name now that team relationship is set op.drop_column('players', 'team') def downgrade(): bind = op.get_bind() session = orm.Session(bind=bind) # re-add the players.team column op.add_column('players', sa.Column('team', sa.String, nullable=False) # set players.team based on team relationship for player in session.query(Player): player.team_name = player.team.name session.commit() op.drop_column('players', 'team_id') op.drop_table('teams') 

La migración define modelos separados porque los modelos en su código representan el estado actual de la base de datos, mientras que las migraciones representan pasos en el camino . Su base de datos podría estar en cualquier estado a lo largo de esa ruta, por lo que los modelos podrían no estar sincronizados con la base de datos todavía. A menos que tenga mucho cuidado, usar los modelos reales directamente causará problemas con las columnas faltantes, los datos no válidos, etc. Es más claro que indique explícitamente qué columnas y modelos utilizará en la migración.

Recomiendo usar las declaraciones centrales de SqlAlchemy utilizando una tabla ad-hoc, como se detalla en la documentación oficial , porque permite el uso de escrituras agnósticas de SQL y pythonic y también es independiente. SqlAlchemy Core es lo mejor de ambos mundos para los scripts de migración.

Aquí hay un ejemplo del concepto:

 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')}) ) # If insert is required from sqlalchemy.sql import insert from sqlalchemy import orm session = orm.Session(bind=bind) bind = op.get_bind() data = { "name": "John", } ret = session.execute(insert(account).values(data)) # for use in other insert calls account_id = ret.lastrowid