同步转异步:Django ORM查询集外键属性

3
看似简单的情况: Django模型有外键:
class Invite(models.Model):
    inviter = models.ForeignKey(User, on_delete=models.CASCADE)
    ...

在异步上下文中,我执行:
# get invite with sync_to_async decorator, then
print(invite.inviter)

获取async的最喜欢的错误:

您无法从异步上下文中调用此函数 - 请使用线程或sync_to_async

print(sync_to_async(invite.inviter)) # -> throws the same error

当然,我可以做:

@sync_to_async
def get_inviter(self, invite):
    return invite.inviter

但是,如果我必须为每个queryset属性调用都这样做,那就太老套了。

有没有一种明智的方法来处理这个问题?

也许,有一种方法可以一次性处理所有这样的调用?


你能分享一下你是如何查询invite的吗? - Brian Destura
@sync_to_async def get_all_unaccepted_invites(self): return Invite.objects.filter(invitee=None) - Lex Podgorny
你读过 https://dev59.com/2FIG5IYBdhLWcg3w_nJR 吗? - Paul Cornelius
@LexPodgorny 你会接受一个答案吗? - aaron
3个回答

3

使用select_related来解决额外字段的问题:

# Good: pick the foreign_key fields using select_related
user = await Invite.objects.select_related('user').aget(key=key).user

您的其他字符串和整数属性等非外键应该已经存在于模型中。

这样做是行不通的(尽管它们感觉应该可以)。

# Error django.core.exceptions.SynchronousOnlyOperation ... use sync_to_async
user = await Model.objects.aget(key=key).user
# Error (The field is actually missing from the `_state` fields cache.
user = await sync_to_async(Invite.objects.get)(key=key).user

研究的其他示例

一个标准的aget,接着是一个外键检查,会导致SynchronousOnlyOperation错误。

我有一个字符串key,和一个指向标准用户模型的ForeignKeyuser

class Invite(models.Model):
    user = fields.user_fk()
    key = fields.str_uuid()

一个示例,其中大多数替代方案都不起作用:

Invite = get_model('invites.Invite')
User = get_user_model()

def _get_invite(key):
    return Invite.objects.get(key=key)


async def invite_get(self, key):
    # (a) works, the related field is populated on response.
    user = await Invite.objects.select_related('user').aget(key=key).user
   

async def intermediate_examples(self, key):
    # works, but is clunky.
    user_id = await Invite.objects.aget(key=key).user_id
    # The `user_id` (any `_id` key) exists for a FK
    user = await User.objects.aget(id=user_id)


async def failure_examples(self, key):
    # (b) does not work. 
    user = await sync_to_async(Invite.objects.get)(key=key).user
    invite = await sync_to_async(Invite.objects.get)(key=key)

    # (c) these are not valid, although the error may say so.
    user = await invite.user
    user = await sync_to_async(invite.user)

    # same as the example (b)
    get_invite = sync_to_async(_get_invite, thread_sensitive=True)
    invite = get_invite(key)
    user = invite.user # Error 
    
    # (d) Does not populate the additional model
    user = await Invite.objects.aget(key=key).user # Error

1
print(sync_to_async(invite.inviter))  # -> throws the same error

这是因为它等同于:

i = invite.inviter  # -> throws the error here
af = sync_to_async(i)
print(af)

正确的用法是:

f = lambda: invite.inviter
af = sync_to_async(f)
i = await af()
print(i)

# As a one-liner
print(await sync_to_async(lambda: invite.inviter)())

有没有一个明智的方法来处理这个问题?也许,有一种方法可以一次性处理所有这样的调用?
(免责声明:未在生产环境中测试。)
使用 nest_asyncio,您可以这样做:
def do(f):
    import nest_asyncio
    nest_asyncio.apply()
    return asyncio.run(sync_to_async(f)())


print(do(lambda: invite.inviter))

或者更进一步:

class SynchronousOnlyAttributeHandler:
    def __getattribute__(self, item):
        from django.core.exceptions import SynchronousOnlyOperation
        try:
            return super().__getattribute__(item)
        except SynchronousOnlyOperation:
            from asgiref.sync import sync_to_async
            import asyncio
            import nest_asyncio
            nest_asyncio.apply()
            return asyncio.run(sync_to_async(lambda: self.__getattribute__(item))())

class Invite(models.Model, AsyncUnsafeAttributeHandler):
    inviter = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    ...

# Do this even in async context
print(invite.inviter)

0

这样的代码是否可行?你可以使用 await async_resolve_attributes(invite, "inviter") 代替 invite.inviter

@sync_to_async
def async_resolve_attributes(instance, *attributes):
    current_instance = instance
    for attribute in attributes:
        current_instance = getattr(current_instance, attribute)
    resolved_attribute = current_instance
    return resolved_attribute

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接