20

現在、HTML マークアップを含む列があります。そのマークアップ内には、新しい列に保存したいタイムスタンプがあります (それに対してクエリを実行できるようにするため)。私の考えは、1 回の移行で次のことを行うことでした。

  1. データの新しい null 許容列を作成する
  2. ORM を使用して、解析する必要がある HTML を取得します
  3. 行ごとに
    1. HTML を解析してタイムスタンプを取得する
    2. ORM オブジェクトを更新する

しかし、移行を実行しようとすると、無限ループに陥っているように見えます。ここに私がこれまでに持っているものがあります:

def _extract_publication_date(html):
    root = html5lib.parse(html, treebuilder='lxml', namespaceHTMLElements=False)
    publication_date_string = root.xpath("//a/@data-datetime")[0]
    return parse_date(publication_date)


def _update_tip(tip):
    tip.publication_date = _extract_publication_date(tip.rendered_html)
    tip.save()


def upgrade():
    op.add_column('tip', sa.Column('publication_date', sa.DateTime(timezone=True)))
    tips = Tip.query.all()
    map(tips, _update_tip)


def downgrade():
    op.drop_column('tip', 'publication_date')
4

4 に答える 4

31

@velochy の回答を使用して少し実験した後、Alembic 内で SqlAlchemy を使用するための次のパターンのようなものに落ち着きました。これは私にとってはうまく機能し、おそらくOPの質問に対する一般的な解決策として役立つ可能性があります:

from sqlalchemy.orm.session import Session
from alembic import op

# Copy the model definitions into the migration script if
# you want the migration script to be robust against later
# changes to the models. Also, if your migration includes
# deleting an existing column that you want to access as 
# part of the migration, then you'll want to leave that 
# column defined in the model copies here.
class Model1(Base): ...
class Model2(Base): ...

def upgrade():
    # Attach a sqlalchemy Session to the env connection
    session = Session(bind=op.get_bind())

    # Perform arbitrarily-complex ORM logic
    instance1 = Model1(foo='bar')
    instance2 = Model2(monkey='banana')

    # Add models to Session so they're tracked
    session.add(instance1)
    session.add(instance2)

    # Apply a transform to existing data
    m1s = session.query(Model1).all()
    for m1 in m1s:
        m1.foo = transform(m1.foo)
    session.commit()

def downgrade():
    # Attach a sqlalchemy Session to the env connection
    session = Session(bind=op.get_bind())

    # Perform ORM logic in downgrade (e.g. clear tables)
    session.query(Model2).delete()
    session.query(Model1).delete()

    # Revert transform of existing data
    m1s = session.query(Model1).all()
    for m1 in m1s:
        m1.foo = un_transform(m1.foo)
    session.commit()

このアプローチは、トランザクションを適切に処理しているように見えます。これに取り組んでいる間、頻繁に DB 例外が生成され、期待どおりにロールバックされました。

于 2016-03-18T14:36:11.277 に答える
7

私にとってうまくいったのは、次のようにしてセッションを取得することです。

connection = op.get_bind()
Session = sa.orm.sessionmaker()
session = Session(bind=connection)
于 2015-03-12T12:43:37.967 に答える
1

コメントから続けて、次のようなことを試すことができます:

import sqlalchemy as sa


tip = sa.sql.table(
    'tip',
    sa.sql.column('id', sa.Integer),
    sa.sql.column('publication_date', sa.DateTime(timezone=True)),
)


def upgrade():
    mappings = [
        (x.id, _extract_publication_date(x.rendered_html))
        for x in Tip.query
    ]

    op.add_column('tip', sa.Column('publication_date', sa.DateTime(timezone=True)))

    exp = sa.sql.case(value=tip.c.id, whens=(
        (op.inline_literal(id), op.inline_literal(publication_date))
        for id, publication_date in mappings.iteritems()
    ))

    op.execute(tip.update().values({'publication_date': exp}))


def downgrade():
    op.drop_column('tip', 'publication_date')
于 2012-12-03T14:04:06.477 に答える