2014-07-07 12 views
37

Ho bisogno di modificare i dati durante un aggiornamento Alembic.Come si eseguono inserimenti e aggiornamenti in uno script di aggiornamento di Alembic?

Al momento ho un tavolo 'giocatori' in una prima revisione:

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

voglio introdurre un tavolo 'squadre'. Ho creato un seconda revisione:

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

vorrei che la seconda migrazione di aggiungere anche i seguenti dati:

  1. Popola tavolo squadre:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players; 
    
  2. Aggiornamento giocatori. team_id in base al nome players.team:

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

Come si eseguono inserimenti e aggiornamenti all'interno dello script di aggiornamento?

risposta

65

Quello che chiediamo è una migrazione di dati , in contrapposizione alla migrazione schema che è più prevalente nei documenti Alambicco.

Questa risposta presuppone che si stia utilizzando dichiarativo (al contrario di class-Mapper-Table o core) per definire i propri modelli. Dovrebbe essere relativamente semplice adattarlo alle altre forme.

Si noti che Alembic fornisce alcune funzioni di dati di base: op.bulk_insert() e op.execute(). Se le operazioni sono abbastanza minime, usa quelle. Se la migrazione richiede relazioni o altre interazioni complesse, preferisco utilizzare tutta la potenza dei modelli e delle sessioni come descritto di seguito.

Il seguente è uno script di migrazione di esempio che imposta alcuni modelli dichiarativi che verranno utilizzati per manipolare i dati in una sessione. I punti chiave sono:

  1. Definire i modelli di base necessari, con le colonne necessarie. Non hai bisogno di ogni colonna, solo la chiave primaria e quelle che userete.
  2. All'interno della funzione di aggiornamento, utilizzare op.get_bind() per ottenere la connessione corrente e creare una sessione con esso.
  3. Utilizzare i modelli e la sessione come si farebbe normalmente nella propria applicazione.

"""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 
from flask_sqlalchemy import _SessionSignalEvents 
import sqlalchemy as sa 
from sqlalchemy import event 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker, Session as BaseSession, relationship 

Session = sessionmaker() 

event.remove(BaseSession, 'before_commit', _SessionSignalEvents.session_signal_before_commit) 
event.remove(BaseSession, 'after_commit', _SessionSignalEvents.session_signal_after_commit) 
event.remove(BaseSession, 'after_rollback', _SessionSignalEvents.session_signal_after_rollback) 

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

Le event.remove linee sono a che fare con Flask-SQLAlchemy. L'estensione aggiunge alcuni eventi alla sessione, ma poiché la migrazione avviene al di fuori del contesto dell'app, questi eventi generano eccezioni. Se non si utilizza tale estensione o si utilizza una versione> = 2.0 (non ancora rilasciata), non sono necessarie tali righe (o le relative importazioni).

+0

Cosa potrebbe impedirti di utilizzare i modelli di applicazione esistenti all'interno di una migrazione alambicco? –

+17

@AndriyYurchuk il problema è che i modelli rappresentano lo * stato corrente * del database, mentre le migrazioni rappresentano * passaggi lungo la strada *. Ma il tuo database potrebbe trovarsi in qualsiasi stato lungo quel percorso, quindi i modelli potrebbero non essere ancora sincronizzati con il database. A meno che tu non sia * molto * attento (e questo mi ha morso in modi imprevedibili), l'uso diretto dei modelli causerà problemi con colonne mancanti, dati non validi, ecc. È più chiaro indicare esplicitamente quali colonne e modelli utilizzerai nel migrazione. – davidism

+6

@AndriyYurchuk Evito di utilizzare i modelli a meno che non abbia intenzione di eseguire una migrazione dei dati complessa che implica l'esecuzione di query tra le relazioni. Per migrazioni semplici, c'è sempre "op.execute" a patto che tu voglia scrivere direttamente SQL. – davidism

Problemi correlati