将 SQLAlchemy ORM 与 sql 核心表达式中的对象连接起来?

connect SQLAlchemy ORM with the objects from sql core expression?

我必须使用 SQLalchemy Core 表达式来获取对象,因为 ORM 做不到 "update and returning"。 (ORM中的更新没有returning

from sqlalchemy import update
class User(ORMBase): 
    ...
# pure sql expression, the object returned is not ORM object.
# the object is a RowProxy.
object = update(User)  \
    .values({'name': 'Wayne'})  \
    .where(User.id == subquery.as_scalar()) \
    .returning() \
    .fetchone()

db_session.add(object)

它报告 UnmappedInstanceError: Class 'sqlalchemy.engine.result.RowProxy' is not mapped

如何将 sql 表达式中的 RowProxy 对象放入 ORM 的恒等映射中 ?

我不确定是否有一种直接的方法来执行您所描述的操作,这主要是构建一个直接映射到数据库条目但不通过 ORM 执行查询的 ORM 对象。

我的直觉是,天真的方法(只需使用数据库中的值构建 ORM 对象)只会创建具有相同值的另一行(或由于唯一性约束而失败)。

更标准的方法是通过 ORM 首先查询行,然后从该 ORM 对象更新数据库。

user = User.query.filter(User.user_attribute == 'foo').one()
user.some_value = 'bar'
session.add(user)
session.commit()

我不确定您是否有一些限制阻止您使用该模式。该文档通过类似 examples

简单案例:

可能的快速解决方案:从 RowProxykwargs 构造对象,因为它们是类对象。

给定:

rowproxy = update(User)  \
    .values({'name': 'Wayne'})  \
    .where(User.id == subquery.as_scalar()) \
    .returning() \
    .fetchone()

我们或许可以做到:

user = User(**dict(rowproxy.items()))

rowproxy.items() returns tupleskey-valuedict(...)tuples 转换为实际的 key-value 对; User(...)kwargs 作为 model 属性名称。

更难的案例:

但是,如果您有一个 model,其中一个 attribute names 与 SQL table column name 不完全相同怎么办?例如。类似于:

class User(ORMBase):
    # etc...
    user_id = Column(name='id', etc)

当我们尝试将 rowproxy 解压到 User class 中时,我们可能会收到如下错误:TypeError: 'id' is an invalid keyword argument for User(因为它期待user_id 代替)。

现在它变脏了:我们应该围绕 mapper 来了解如何从 table 属性到 model 属性,反之亦然:

kw_map = {a.key: a.class_attribute.name for a in User.__mapper__.attrs}

这里,a.keymodel attribute(和kwarg),a.class_attribute.nametable attribute。这给了我们类似的东西:

{
    "user_id": "id"
}

好吧,我们实际上想提供我们从 rowproxy 返回的值,除了允许类似对象的访问外,还允许类似字典的访问:

kwargs = {a.key: rowproxy[a.class_attribute.name] for a in User.__mapper__.attrs}

现在我们可以做:

user = User(**kwargs)

勘误表:

  • 您可能希望在调用 update().returning() 后立即 session.commit() 以防止更改与永久存储在数据库中时的长时间延迟。稍后无需 session.add(user) - 您已经 updated() 并且只需要 commit() 该交易
  • object是Python中的关键词,尽量不要踩;这样做你会得到一些非常奇怪的行为;这就是为什么我重命名为 rowproxy.