Использование GenericForeignKey в Django с Async Graphene Subscription

Я реализовал graphql_ws для подписки на обновления из Notification модели, которая использует несколько GenericForeignKeys.

Моя установка работает хорошо, за исключением тех случаев, когда я пытаюсь запросить информацию из этих внешних объектов. Тогда я получаю следующую ошибку:

graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.

Насколько я понимаю, это происходит потому, что некоторые операции запроса к базе данных выполняются вне асинхронного контекста get_notifications (то есть в функции resolve_actor). Но я действительно не понимаю, как я могу вытащить операции из resolve_actor
в асинхронный контекст

Я безуспешно пытался использовать prefetch_related (плюс я не уверен, что это будет работать с несколькими типами содержимого согласно документации Django ).

Вот код

models.py


class Notification(TimeStampedModel):
    # ...
    actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
    actor_object_id = models.CharField(max_length=255)
    actor = GenericForeignKey('actor_content_type', 'actor_object_id')
    # ...

schema.py

class ActorTypeUnion(graphene.Union):
    """
    All possible types for Actors
    (The object that performed the activity.)
    """
    class Meta:
        types = (UserType,) # here's there's only one type, but other fields have multiple


class NotificationType(DjangoObjectType):
    actor = graphene.Field(ActorTypeUnion)
    
    def resolve_actor(self, args):
        if self.actor   is not None:
            model_name = self.actor._meta.model_name
            app_label = self.actor._meta.app_label
            model = ContentType.objects.get(app_label=app_label, model=model_name)
            return model.get_object_for_this_type(pk=self.actor_object_id)
        return None
    # ...
    class Meta:
        model = Notification

class Subscription(graphene.ObjectType):
    unread_notifications = graphene.List(NotificationType)

    async def resolve_unread_notifications(self, info, **kwargs):
        user = info.context['user']
        if user.is_anonymous:
            raise Exception('Not logged in!')
        
        @database_sync_to_async
        def get_notifications(user):
            notifications = Notification.objects.filter(
                recipient=user,
                read=False,
                organization=user.active_organization,
            )
            return [notifications]

        while True:
            await asyncio.sleep(1)
            yield await get_notifications(user)

Запрос (все работает хорошо, кроме случаев, когда я запрашиваю поля на actor)

subscription {
   unreadNotifications {
      id,
      read,
      actor {
        ... on UserType {
          __typename,
          id

        }
      }
    }
  }

Полное отслеживание

Traceback (most recent call last):
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executor.py", line 452, in resolve_or_error
    return executor.execute(resolve_fn, source, info, **args)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executors/asyncio.py", line 74, in execute
    result = fn(*args, **kwargs)
  File "/Users/benjaminsoukiassian/Projects/logbook-back/logbook/notifications/schema.py", line 52, in resolve_actor
    model = ContentType.objects.get(app_label=app_label, model=model_name)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 431, in get
    num = len(clone)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__
    self._fetch_all()
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
    cursor = self.connection.cursor()
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
    raise SynchronousOnlyOperation(message)
graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.
Вернуться на верх