异步 SQLalchemy:访问急切加载的空关系会触发新的延迟加载,引发错误

问题描述 投票:0回答:3

我正在使用 sqlalchemy + asyncpg,并“选择”急切加载。

我的个人项目与好友具有一对多关系。

我将一个人插入我的数据库,但没有相关的朋友条目。如果在同一个会话中我尝试从数据库中获取该 Person,我可以很好地访问他们的静态(非关系)列,但无法访问

friends
关系。

我认为尝试访问

person.friends
会触发延迟加载,尽管它之前是作为
selectin
负载强制执行的。为什么是这样?怎样才能避免呢?

# Create the ORM model
class Person(Base):
    __tablename__ = 'items'
    id_ = Column(POSTGRES_UUID(as_uuid=True), primary_key=True)
    name = Column(String(32))
    friends = relationship('Friend', lazy='selectin')

# Create an instance
person_id = uuid4()
person = Person(id_=person_id, name='Alice') # Note that this Person's friends are not set

# Add to database
async with AsyncSession(engine, expire_on_commit=False) as session:
    try:
        session.begin()
        session.add(person)
        await session.commit()
    except:
        await session.rollback()
        raise
    # Get the added person from the database
    created_person = await session.get(person, person_id)
    print(created_person.id_) # Works fine
    print(created_person.friends) # Raises error

错误:

sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_() here.
Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/14/xd2s)
asynchronous sqlalchemy python-asyncio asyncpg
3个回答
5
投票

解决方案是使用

populate_existing
中的
get
参数:

populate_existing – 使该方法无条件发出 SQL 查询并使用新加载的数据刷新对象,无论对象是否已存在。

更换

created_person = await session.get(person, person_id)

created_person = await session.get(person, person_id, populate_existing=True)

session.get 文档

另请参阅:https://github.com/sqlalchemy/sqlalchemy/issues/7176


4
投票

@theo-brown 的回答开门见山,但想在这里添加一些有趣的信息。

添加有关延迟加载和异步 SQLAlchemy 的额外上下文:

当您使用异步 SqlAlchemy 获取数据时,每个被查询的模型都会生成一个协程。如果您不急于加载关系,您最终会得到部分填充的模型。

想象一下我正在处理的这个用例:我有一个batch_job对象,它与batch_file和batch_job条目相关,所有这些都是我的数据库中的表。当我不急于加载它们时,看看调试器中会发生什么:

从端点返回对象时得到的回溯是这样的:

greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place? (Background on this error at: https://sqlalche.me/e/14/xd2s)

原因是我没有等待这些值,这就是急切加载在异步 sqlalchemy 中为您所做的事情。

但是,如果您在应用程序范围内工作并且稍后需要使用这些值,则可能不必急切加载,因此您可以等待它们。

对于那些使用 ORM 的人,您可以使用旧的加载选项来做到这一点:

results = await db_session.execute(select(YourModel).options(joinedload(YourModel.relationshipcolumn)).all()

0
投票

更新(几乎)2024 年,因为这是“SQLalchemy 异步延迟加载”的第一个 Google 结果

使用 sqlalchemy 2.0,在模型或基类中添加 AsyncAttrs mixin,可以轻松地通过

awaitable_attrs
命名空间异步延迟加载集合:

class Person(Base):
    __tablename__ = 'items'
    id_ = Column(POSTGRES_UUID(as_uuid=True), primary_key=True)
    name = Column(String(32))
    friends = relationship('Friend', lazy='select')

...

async with AsyncSession(engine, expire_on_commit=False) as session:
    created_person = await session.get(Person, person_id)
    
    # do things, then when you are ready for person's friends
    friends = await created_person.awaitable_attrs.friends
© www.soinside.com 2019 - 2024. All rights reserved.