O que você está pedindo é uma migração de dados , em oposição à migração de esquema que é mais comum nos documentos do Alembic.
Esta resposta assume que você está usando declarativo (ao contrário de class-Mapper-Table ou core) para definir seus modelos. Deve ser relativamente simples adaptar isso às outras formas.
Observe que o Alembic fornece algumas funções básicas de dados: op.bulk_insert()
e op.execute()
. Se as operações forem mínimas, use-as. Se a migração exigir relacionamentos ou outras interações complexas, prefiro usar todo o poder dos modelos e sessões conforme descrito abaixo.
A seguir está um script de migração de exemplo que configura alguns modelos declarativos que serão usados para manipular dados em uma sessão. Os pontos principais são:
Defina os modelos básicos de que você precisa, com as colunas de que precisa. Você não precisa de todas as colunas, apenas da chave primária e das que usará.
Na função de atualização, use op.get_bind()
para obter a conexão atual e faça uma sessão com ela.
- Ou use
bind.execute()
para usar o nível inferior do SQLAlchemy para escrever consultas SQL diretamente. Isso é útil para migrações simples.
Use os modelos e a sessão como faria normalmente em seu aplicativo.
"""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')
A migração define modelos separados porque os modelos em seu código representam o estado atual do banco de dados, enquanto as migrações representam etapas ao longo do caminho . Seu banco de dados pode estar em qualquer estado ao longo desse caminho, então os modelos podem não sincronizar com o banco de dados ainda. A menos que você seja muito cuidadoso, usar os modelos reais diretamente causará problemas com colunas ausentes, dados inválidos, etc. É mais claro declarar explicitamente quais colunas e modelos você usará na migração.
op.execute
dentroupgrade()
, há uma maneira de fornecer um modelo padrão para ser usado poralembic revision
comando (um corpo padrão para o.py
arquivo gerado )?