Alembic yükseltme komut dosyasında eklemeleri ve güncellemeleri nasıl yürütürüm?


105

Alembic yükseltme sırasında verileri değiştirmem gerekiyor.

Şu anda ilk revizyonda bir 'oyuncular' masam var:

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

Bir 'takımlar' tablosu tanıtmak istiyorum. İkinci bir revizyon oluşturdum:

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

İkinci geçişin aşağıdaki verileri de eklemesini istiyorum:

  1. Takım tablosunu doldurun:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
    
  2. Players.team_id öğesini oyuncular.team adına göre güncelleyin:

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

Yükseltme komut dosyası içindeki eklemeleri ve güncellemeleri nasıl yürütebilirim?

Yanıtlar:


161

İstediğiniz şey , Alembic belgelerinde en yaygın olan şema geçişinin aksine bir veri geçişidir.

Bu cevap, modellerinizi tanımlamak için bildirime dayalı (class-Mapper-Table veya core yerine) kullandığınızı varsayar. Bunu diğer biçimlere uyarlamak görece basit olmalıdır.

Alembic'in bazı temel veri işlevlerini sağladığını unutmayın: op.bulk_insert()veop.execute() . İşlemler oldukça azsa, bunları kullanın. Taşıma, ilişkiler veya diğer karmaşık etkileşimler gerektiriyorsa, aşağıda açıklandığı gibi modellerin ve oturumların tüm gücünü kullanmayı tercih ederim.

Aşağıda, bir oturumdaki verileri işlemek için kullanılacak bazı bildirimsel modelleri ayarlayan örnek bir geçiş betiği verilmiştir. Kilit noktalar şunlardır:

  1. İhtiyacınız olan temel modelleri ihtiyaç duyacağınız sütunlarla tanımlayın. Her sütuna ihtiyacınız yok, sadece birincil anahtara ve kullanacağınızlara ihtiyacınız var.

  2. Yükseltme işlevi içinde kullanın op.get_bind() mevcut bağlantıyı almak ve onunla bir oturum yapmak için kullanın.

    • Veya bind.execute()doğrudan SQL sorguları yazmak için SQLAlchemy'nin alt düzeyini kullanmak için kullanın. Bu, basit geçişler için kullanışlıdır.
  3. Modelleri ve oturumu uygulamanızda normalde yaptığınız gibi kullanı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')

Geçiş, ayrı modelleri tanımlar çünkü kodunuzdaki modeller veritabanının mevcut durumunu temsil ederken geçişler yoldaki adımları temsil eder . Veritabanınız bu yol boyunca herhangi bir durumda olabilir, bu nedenle modeller henüz veritabanıyla senkronize olmayabilir. Çok dikkatli olmadığınız sürece, gerçek modelleri doğrudan kullanmak eksik sütunlarda, geçersiz verilerde vb. Sorunlara neden olacaktır. Taşıma sırasında tam olarak hangi sütun ve modelleri kullanacağınızı açıkça belirtmek daha açıktır.


14

Doğrudan SQL'i de kullanabilirsiniz , aşağıdaki örnekte olduğu gibi ( Alembic İşlem Referansı ) bölümüne bakın :

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

Durumda hep Harici bir dosyadan bir SQL deyimi okumak istediğini ve daha sonra onu geçmek op.executeiçinde upgrade(), tarafından kullanılmak üzere varsayılan bir şablon sağlamak için bir yol yoktur alembic revisionkomutu (üretilen için varsayılan bir vücuda .pydosyası)?
Quentin

1
@Quentin bilmiyorum. Bu ilginç bir fikir.
Martlark

8

Resmi belgelerde ayrıntılı olarak açıklandığı gibi geçici bir tablo kullanarak SQLAlchemy çekirdek ifadelerini kullanmanızı öneririm , çünkü agnostik SQL ve pitonik yazının kullanımına izin verir ve aynı zamanda bağımsızdır. SQLAlchemy Core, geçiş betikleri için her iki dünyanın en iyisidir.

İşte konseptin bir örneği:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.