在Alembic迁移中使用SQLAlchemy ORM:我该怎么做?

Han*_*Gay 12 python sqlalchemy alembic

我目前有一个包含HTML标记的列.在该标记内,有一个我想要存储在新列中的时间戳(因此我可以查询它).我的想法是在一次迁移中执行以下操作:

  1. 为数据创建一个新的可为空的列
  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')
Run Code Online (Sandbox Code Playgroud)

kil*_*ush 13

在使用@velochy的答案进行了一些实验之后,我决定在Alembic中使用SqlAlchemy的以下模式.这对我来说很有用,可能可以作为OP问题的一般解决方案:

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

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)

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()
Run Code Online (Sandbox Code Playgroud)

这种方法似乎可以正确处理交易.经常在处理这个问题时,我会生成数据库异常,他们会按预期回滚.

  • 值得注意的是,如果您的模型经常变化,这种技术可能是不明智的.当模型发生变化时,它可能会破坏假设模型具有某种形状的旧迁移. (5认同)
  • 如果模型发生变化,我不会说这种技术是不明智的,只是在迁移中定义模型而不是导入可能更安全。 (4认同)
  • 是的,制作模型副本以在较旧的迁移中使用是解决此问题的合法技术。然后,更改可以发生*而不*破坏旧的东西。那么主要的问题就变成了这是否会产生足够多的额外工作来增加负担。这取决于,海事组织。可能没问题! (2认同)
  • 我编辑了答案以建议在迁移中定义模型。请注意,在某些特殊情况下,需要在迁移中定义模型,例如从模型中删除列,但又在删除之前将该列用作迁移的一部分。 (2认同)

Rai*_*ivi 8

您可以使用automap 扩展自动创建数据库在迁移期间存在的 ORM 模型,而无需将它们复制到代码中:

import sqlalchemy as sa
from alembic import op
from sqlalchemy.ext.automap import automap_base

Base = automap_base()

def upgrade():
    # Add the new column
    op.add_column('tip', sa.Column('publication_date', sa.DateTime(timezone=True)))

    # Reflect ORM models from the database
    # Note that this needs to be done *after* all needed schema migrations.
    bind = op.get_bind()
    Base.prepare(autoload_with=bind)  # SQLAlchemy 1.4 and later
    # Base.prepare(bind, reflect=True)  # SQLAlchemy before version 1.4
    Tip = Base.classes.tip  # "tip" is the table name

    # Query/modify the data as it exists during the time of the migration
    session = Session(bind=bind)
    tips = session.query(Tip).all()
    for tip in tips:
        # arbitrary update logic
        ...


def downgrade():
    op.drop_column('tip', 'publication_date')
Run Code Online (Sandbox Code Playgroud)


vel*_*chy 6

对我有用的是通过执行以下操作来获得会话:

connection = op.get_bind()
Session = sa.orm.sessionmaker()
session = Session(bind=connection)
Run Code Online (Sandbox Code Playgroud)


say*_*yap 3

继续评论,你可以尝试这样的事情:

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')
Run Code Online (Sandbox Code Playgroud)