Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/328.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 在Alembic迁移中使用SQLAlchemy ORM:我该怎么做?_Python_Sqlalchemy_Alembic - Fatal编程技术网

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

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

我目前有一个包含HTML标记的列。在这个标记中,有一个时间戳,我想存储在一个新列中(这样我就可以对它进行查询)。我的想法是在一次迁移中执行以下操作:

  • 为数据创建一个新的、可为空的列
  • 使用ORM提取需要解析的HTML
  • 每行
  • 解析HTML以提取时间戳
  • 更新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')
    

    从评论中继续,您可以尝试以下操作:

    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使用seam
    Session
    作为
    op.foo
    方法,我会更乐意。我也努力让逻辑保持在同一个位置,但只有在我可以用SQL实现的情况下。Alembic不建议迁移脚本和应用程序代码之间紧密耦合。虽然我明白你的观点。您可能希望首先尝试执行查询,并构建一个
    id:publication\u date
    的dict,然后使用
    op.execute()
    对其进行更新。如果您的ORM会话具有
    autocommit=True
    ,我认为第一个事务将在查询后立即关闭,因此不会有重叠事务。这一种对我来说很有效,虽然有错误说我的模型已经绑定到会话。但值得注意的是,如果您的模型经常更改,这种技术可能是不明智的。当模型更改时,它可能会打破假定模型具有特定形状的旧迁移。我不会说如果模型正在更改,这种技术是不明智的,只是在迁移中定义模型可能比导入更安全。是的,制作模型的副本以供在较旧的迁移中使用是解决此问题的合法技术。然后,这些变化就可以在不打破旧东西的情况下发生。然后,主要的问题是,这是否会产生足够多的额外工作,从而成为一种负担。那要看情况了,依我看。可能没问题!我编辑了答案,建议在迁移中定义模型。请注意,在某些特殊情况下,在迁移中定义模型是必要的,例如从模型中删除列,但在删除之前也将该列用作迁移的一部分。