Как мне выполнять вставки и обновления в скрипте обновления Alembic?

Мне нужно изменить данные во время обновления Alembic.

В настоящее время у меня есть таблица игроков в первой редакции:

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

Хочу представить "командную" таблицу. Я создал вторую ревизию:

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

Я бы хотел, чтобы вторая миграция также добавила следующие данные:

  1. Заполните таблицу команд:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
    
  2. Обновите player.team_id на основе названия player.team:

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

Как мне выполнять вставки и обновления внутри скрипта обновления?


person Arek S    schedule 07.07.2014    source источник


Ответы (3)


То, что вы просите, - это миграция данных, в отличие от миграции схемы, которая наиболее распространена в документации Alembic.

Этот ответ предполагает, что вы используете декларативный (в отличие от class-Mapper-Table или core) для определения ваших моделей. Это должно быть относительно просто адаптировать к другим формам.

Обратите внимание, что Alembic предоставляет некоторые основные функции обработки данных: _1 _ и op.execute(). Если операции довольно минимальны, используйте их. Если для миграции требуются отношения или другие сложные взаимодействия, я предпочитаю использовать всю мощь моделей и сеансов, как описано ниже.

Ниже приведен пример сценария миграции, который устанавливает некоторые декларативные модели, которые будут использоваться для управления данными в сеансе. Ключевые моменты:

  1. Определите основные модели, которые вам нужны, с нужными столбцами. Вам не нужны все столбцы, только первичный ключ и те, которые вы будете использовать.

  2. В функции обновления используйте op.get_bind(), чтобы получить текущее соединение и создайте с ним сеанс.

    • Or use bind.execute() to use SQLAlchemy's lower level to write SQL queries directly. This is useful for simple migrations.
  3. Используйте модели и сеанс, как обычно в своем приложении.

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

Миграция определяет отдельные модели, потому что модели в вашем коде представляют текущее состояние базы данных, а миграции представляют собой шаги по пути. Ваша база данных может находиться в любом состоянии на этом пути, поэтому модели могут еще не синхронизироваться с базой данных. Если вы не будете очень осторожны, использование реальных моделей напрямую вызовет проблемы с отсутствующими столбцами, недопустимыми данными и т. Д. Более ясно указать, какие именно столбцы и модели вы будете использовать при миграции.

person davidism    schedule 08.07.2014

Вы также можете использовать прямой SQL, см. (Справочник по работе с Alembic ) как в следующем примере:

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 ###
person Martlark    schedule 29.06.2019
comment
В случае, если я всегда хотел прочитать оператор SQL из внешнего файла, а затем передать его op.execute в upgrade(), есть ли способ предоставить шаблон по умолчанию, который будет использоваться командой alembic revision (тело по умолчанию для сгенерированного файла .py)? - person Quentin; 11.07.2020
comment
Я не знаю @Quentin. Это интересная идея. - person Martlark; 12.07.2020

Я рекомендую использовать основные операторы SQLAlchemy с использованием специальной таблицы как описано в официальной документации, поскольку он позволяет использовать агностический SQL и питоническое письмо, а также является автономным. SQLAlchemy Core - лучшее из обоих миров для сценариев миграции.

Вот пример концепции:

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
person cmc    schedule 16.01.2019