Использование 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.