SQLAlchemy:lazy='raise' 抱怨字段,即使它们已加载
SQLAlchemy: lazy='raise' complaining about fields even though they are loaded
我们使用 sqlalchemy 的异步版本,我们需要预先加载每个关系(延迟加载不适用于异步)。因此,对于我们模型中的每个关系,我们都设置了 lazy='raise'
。问题是它似乎在引发错误方面过于激进。考虑以下单元测试:
async def test_user_group_self_allowed(self):
privilege = await self.db.get(Privilege, 1, [joinedload(Privilege.role)])
options = [joinedload(Item.privileges).joinedload(Privilege.role), joinedload(Item.item_group)]
item = await self.db.get(Item, 1, options)
item.privileges.append(privilege)
await (self.db.commit())
options = [joinedload(User.user_groups).joinedload(UserGroup.privileges), joinedload(User.privileges)]
user = await self.db.get(User, 1, options)
user.privileges = []
item = await self.db.get(Item, 1, [joinedload(Item.privileges).joinedload(Privilege.role), joinedload(Item.item_group)])
user_group = await self.db.get(UserGroup, 1, [joinedload(UserGroup.organization)])
print('why?????', user_group.organization)
self.assertTrue(await self.helper.is_authorized(self.db, user, 'edit', user_group))
注意打印,它会导致以下错误:
Traceback (most recent call last):
File "/usr/lib/python3.9/unittest/async_case.py", line 65, in _callTestMethod
self._callMaybeAsync(method)
File "/usr/lib/python3.9/unittest/async_case.py", line 88, in _callMaybeAsync
return self._asyncioTestLoop.run_until_complete(fut)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/usr/lib/python3.9/unittest/async_case.py", line 102, in _asyncioLoopRunner
ret = await awaitable
File "/src/backend-core/backend_core/tests/authorization.py", line 206, in test_user_group_self_allowed
print('why?????', user_group.organization)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/attributes.py", line 481, in __get__
return self.impl.get(state, dict_)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/attributes.py", line 926, in get
value = self._fire_loader_callables(state, key, passive)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/attributes.py", line 962, in _fire_loader_callables
return self.callable_(state, passive)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/strategies.py", line 836, in _load_for_state
self._invoke_raise_load(state, passive, "raise")
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/strategies.py", line 795, in _invoke_raise_load
raise sa_exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: 'UserGroup.organization' is not available due to lazy='raise'
如您所见,它抱怨组织没有被急切加载,而我明确地将其包含在带有 joinedload
的选项中。现在我们可以通过将用户查询的选项更改为:
来消除此错误
options = [joinedload(User.user_groups).joinedload(UserGroup.privileges), joinedload(User.privileges), joinedload(User.user_groups).joinedload(UserGroup.organization)]
(和之前一样的选项,只是我们添加了一个joinedload for User -> UserGroups -> Organization)
这会使错误消失,一切都恢复正常。现在我的问题是,为什么它一开始就抱怨这个?我访问 user_group.organization
而不是 user.user_groups[x].organization
.. 我不知道这些查询到底是如何工作的,但我不仅必须以这种方式编写太多的连接负载,我认为这也会导致不用问了。
事实证明,.get
缓存的内容比我预期的要多。不仅是主要实体(在本例中为 usergroup
),还有通过 joinedload
(user.user_group.organization
) 加载的内容。因此,这意味着直接获取组织不会覆盖来自 user.user_group.organization
或 usergroup.organization
的缓存。但是可以这样做db.get(Organization, 1, populate_existing=True)
这将再次检索实体并更新缓存。
来自docs:
如果给定的主键标识符存在于本地标识映射中,则直接从该集合返回对象并且不会发出 SQL,除非该对象已被标记为完全过期....
...
populate_existing – 使方法无条件发出 SQL 查询并使用新加载的数据刷新对象,无论对象是否已经存在。
正如它所说,另一种方法是使对象过期,阅读更多相关信息 here
我们使用 sqlalchemy 的异步版本,我们需要预先加载每个关系(延迟加载不适用于异步)。因此,对于我们模型中的每个关系,我们都设置了 lazy='raise'
。问题是它似乎在引发错误方面过于激进。考虑以下单元测试:
async def test_user_group_self_allowed(self):
privilege = await self.db.get(Privilege, 1, [joinedload(Privilege.role)])
options = [joinedload(Item.privileges).joinedload(Privilege.role), joinedload(Item.item_group)]
item = await self.db.get(Item, 1, options)
item.privileges.append(privilege)
await (self.db.commit())
options = [joinedload(User.user_groups).joinedload(UserGroup.privileges), joinedload(User.privileges)]
user = await self.db.get(User, 1, options)
user.privileges = []
item = await self.db.get(Item, 1, [joinedload(Item.privileges).joinedload(Privilege.role), joinedload(Item.item_group)])
user_group = await self.db.get(UserGroup, 1, [joinedload(UserGroup.organization)])
print('why?????', user_group.organization)
self.assertTrue(await self.helper.is_authorized(self.db, user, 'edit', user_group))
注意打印,它会导致以下错误:
Traceback (most recent call last):
File "/usr/lib/python3.9/unittest/async_case.py", line 65, in _callTestMethod
self._callMaybeAsync(method)
File "/usr/lib/python3.9/unittest/async_case.py", line 88, in _callMaybeAsync
return self._asyncioTestLoop.run_until_complete(fut)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/usr/lib/python3.9/unittest/async_case.py", line 102, in _asyncioLoopRunner
ret = await awaitable
File "/src/backend-core/backend_core/tests/authorization.py", line 206, in test_user_group_self_allowed
print('why?????', user_group.organization)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/attributes.py", line 481, in __get__
return self.impl.get(state, dict_)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/attributes.py", line 926, in get
value = self._fire_loader_callables(state, key, passive)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/attributes.py", line 962, in _fire_loader_callables
return self.callable_(state, passive)
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/strategies.py", line 836, in _load_for_state
self._invoke_raise_load(state, passive, "raise")
File "/usr/local/lib/python3.9/dist-packages/sqlalchemy/orm/strategies.py", line 795, in _invoke_raise_load
raise sa_exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: 'UserGroup.organization' is not available due to lazy='raise'
如您所见,它抱怨组织没有被急切加载,而我明确地将其包含在带有 joinedload
的选项中。现在我们可以通过将用户查询的选项更改为:
options = [joinedload(User.user_groups).joinedload(UserGroup.privileges), joinedload(User.privileges), joinedload(User.user_groups).joinedload(UserGroup.organization)]
(和之前一样的选项,只是我们添加了一个joinedload for User -> UserGroups -> Organization)
这会使错误消失,一切都恢复正常。现在我的问题是,为什么它一开始就抱怨这个?我访问 user_group.organization
而不是 user.user_groups[x].organization
.. 我不知道这些查询到底是如何工作的,但我不仅必须以这种方式编写太多的连接负载,我认为这也会导致不用问了。
事实证明,.get
缓存的内容比我预期的要多。不仅是主要实体(在本例中为 usergroup
),还有通过 joinedload
(user.user_group.organization
) 加载的内容。因此,这意味着直接获取组织不会覆盖来自 user.user_group.organization
或 usergroup.organization
的缓存。但是可以这样做db.get(Organization, 1, populate_existing=True)
这将再次检索实体并更新缓存。
来自docs:
如果给定的主键标识符存在于本地标识映射中,则直接从该集合返回对象并且不会发出 SQL,除非该对象已被标记为完全过期....
...
populate_existing – 使方法无条件发出 SQL 查询并使用新加载的数据刷新对象,无论对象是否已经存在。
正如它所说,另一种方法是使对象过期,阅读更多相关信息 here