Python 在Alembic迁移中使用SQLAlchemy ORM:我该怎么做?
我目前有一个包含HTML标记的列。在这个标记中,有一个时间戳,我想存储在一个新列中(这样我就可以对它进行查询)。我的想法是在一次迁移中执行以下操作:Python 在Alembic迁移中使用SQLAlchemy ORM:我该怎么做?,python,sqlalchemy,alembic,Python,Sqlalchemy,Alembic,我目前有一个包含HTML标记的列。在这个标记中,有一个时间戳,我想存储在一个新列中(这样我就可以对它进行查询)。我的想法是在一次迁移中执行以下操作: 为数据创建一个新的、可为空的列 使用ORM提取需要解析的HTML 每行 解析HTML以提取时间戳 更新ORM对象 但当我尝试运行迁移时,它似乎陷入了一个无限循环。以下是到目前为止我得到的信息: def _extract_publication_date(html): root = html5lib.parse(html, treebuil
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')
从评论中继续,您可以尝试以下操作:
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')
对我有效的方法是通过执行以下操作来获得会话:
connection = op.get_bind()
Session = sa.orm.sessionmaker()
session = Session(bind=connection)
在使用@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异常,它们会按预期回滚。您如何知道它卡在无限循环中?如果
Tip.query
与op
使用的会话不同,则将有2个事务,使用选择一个卡在等待ALTER表提交的one。无论如何,我认为将ORM部分移动到自己的脚本中会更干净,在alembic升级后手动运行@X-Istence我不知道它陷入了无限循环。我知道命令永远不会返回。@sayap我已经考虑过了,但这意味着我不能在同一个地方跟踪所有数据库升级逻辑。此外,如果ORM逻辑起作用,那么我可以向迁移中添加另一个结构步骤,使新列不为NULL
。如果我能弄清楚如何让ORM使用seamSession
作为op.foo
方法,我会更乐意。我也努力让逻辑保持在同一个位置,但只有在我可以用SQL实现的情况下。Alembic不建议迁移脚本和应用程序代码之间紧密耦合。虽然我明白你的观点。您可能希望首先尝试执行查询,并构建一个id:publication\u date
的dict,然后使用op.execute()
对其进行更新。如果您的ORM会话具有autocommit=True
,我认为第一个事务将在查询后立即关闭,因此不会有重叠事务。这一种对我来说很有效,虽然有错误说我的模型已经绑定到会话。但值得注意的是,如果您的模型经常更改,这种技术可能是不明智的。当模型更改时,它可能会打破假定模型具有特定形状的旧迁移。我不会说如果模型正在更改,这种技术是不明智的,只是在迁移中定义模型可能比导入更安全。是的,制作模型的副本以供在较旧的迁移中使用是解决此问题的合法技术。然后,这些变化就可以在不打破旧东西的情况下发生。然后,主要的问题是,这是否会产生足够多的额外工作,从而成为一种负担。那要看情况了,依我看。可能没问题!我编辑了答案,建议在迁移中定义模型。请注意,在某些特殊情况下,在迁移中定义模型是必要的,例如从模型中删除列,但在删除之前也将该列用作迁移的一部分。