web-dev-qa-db-fra.com

Comment exécuter des insertions et des mises à jour dans un script de mise à niveau d'Alembic?

J'ai besoin de modifier les données lors d'une mise à niveau d'Alembic.

J'ai actuellement une table 'joueurs' dans une première révision:

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

Je veux présenter un tableau des "équipes". J'ai créé une deuxième révision:

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

J'aimerais que la deuxième migration ajoute également les données suivantes:

  1. Remplissez le tableau des équipes:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
    
  2. Mettez à jour players.team_id en fonction du nom de players.team:

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

Comment exécuter des insertions et des mises à jour dans le script de mise à niveau?

69
Arek S

Ce que vous demandez est une migration de données , par opposition à la migration de schéma qui est le plus répandu dans les documents d'Alembic.

Cette réponse suppose que vous utilisez déclarative (par opposition à class-Mapper-Table ou core) pour définir vos modèles. Il devrait être relativement simple de l'adapter aux autres formes.

Notez qu'Alembic fournit quelques fonctions de données de base: op.bulk_insert() et op.execute() . Si les opérations sont assez minimes, utilisez-les. Si la migration nécessite des relations ou d'autres interactions complexes, je préfère utiliser toute la puissance des modèles et des sessions comme décrit ci-dessous.

Voici un exemple de script de migration qui configure certains modèles déclaratifs qui seront utilisés pour manipuler des données dans une session. Les points clés sont:

  1. Définissez les modèles de base dont vous avez besoin, avec les colonnes dont vous aurez besoin. Vous n'avez pas besoin de chaque colonne, seulement la clé primaire et celles que vous utiliserez.
  2. Dans la fonction de mise à niveau, utilisez op.get_bind() pour obtenir la connexion actuelle et établissez une session avec celle-ci.

    • Ou utilisez bind.execute() pour utiliser le niveau inférieur de SQLAlchemy pour écrire directement des requêtes SQL. Ceci est utile pour les migrations simples.
  3. Utilisez les modèles et la session comme vous le feriez normalement dans votre application.

"""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):
    __table= '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):
    __table= '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 migration définit des modèles distincts car les modèles de votre code représentent l'état actuel de la base de données, tandis que les migrations représentent étapes le long du chemin . Votre base de données peut se trouver dans n'importe quel état le long de ce chemin, il se peut donc que les modèles ne se synchronisent pas encore avec la base de données. Sauf si vous êtes très prudent, l'utilisation directe des modèles réels entraînera des problèmes avec des colonnes manquantes, des données non valides, etc. Il est plus clair d'indiquer explicitement quelles colonnes et quels modèles vous utiliserez dans la migration.

116
davidism

Je recommande d'utiliser les instructions de base SqlAlchemy en utilisant une table ad hoc, comme détaillé dans la documentation officielle , car elle permet l'utilisation de l'écriture agnostique SQL et Pythonic et est également autonome. SqlAlchemy Core est le meilleur des deux mondes pour les scripts de migration.

Voici un exemple du concept:

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
2
cmc

Vous pouvez également utiliser SQL direct voir ( Alembic Operation Reference ) comme dans l'exemple suivant:

from alembic import op

# revision identifiers, used by Alembic.
revision = '1ce7873ac4ced2'
down_revision = '1cea0ac4ced2'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands made by andrew ###
    op.execute('UPDATE STOCK SET IN_STOCK = -1 WHERE IN_STOCK IS NULL')
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###
1
Martlark