Как создать асинхронную функцию-представление (view) в Django

Ответ

Начиная с Django 3.1, можно создавать асинхронные представления (views), используя синтаксис async def. Это позволяет эффективно обрабатывать I/O-bound операции (например, HTTP-запросы к внешним API, работа с WebSocket) без блокировки потока.

Пример простого асинхронного представления:

import asyncio
from django.http import JsonResponse

async def my_async_view(request):
    # Имитация неблокирующей операции, например, запроса к API
    await asyncio.sleep(1)
    return JsonResponse({'status': 'ok', 'message': 'Completed async task'})

Ограничения и работа с ORM

Основное ограничение заключается в том, что ORM Django до версии 4.1 был полностью синхронным. Вызов синхронного кода ORM (например, User.objects.all()) внутри async def заблокирует event loop.

Для решения этой проблемы используется адаптер asgiref.sync.sync_to_async.

Пример асинхронного view с синхронным вызовом ORM:

from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
from django.http import JsonResponse

# Оборачиваем синхронный вызов ORM в асинхронную функцию
@sync_to_async
def get_user_count():
    return User.objects.count()

async def user_count_view(request):
    # Асинхронно ожидаем результат выполнения синхронного кода
    count = await get_user_count()
    return JsonResponse({'user_count': count})

Примечание: Начиная с Django 4.1, ORM получил частичную нативную поддержку асинхронных операций (например, aget, acreate, acount), что упрощает написание такого кода.

Ответ 18+ 🔞

Да ты посмотри, что эти Django-разработчики вытворяют! С версии 3.1, блядь, они нам подсунули асинхронные вьюхи через async def. Ну, в принципе, логично — если у тебя там запросы к каким-нибудь внешним API, которые тормозят как черепаха в патруле, или WebSocket'ы, то это самое оно. Не будет твой сервер, сука, тупить впустую, пока ждёт ответа.

Вот, смотри, элементарный пример:

import asyncio
from django.http import JsonResponse

async def my_async_view(request):
    # Представь, что тут мы ждём ответ от какого-нибудь медленного сервиса
    await asyncio.sleep(1)
    return JsonResponse({'status': 'ok', 'message': 'Completed async task'})

Но есть, конечно, подводный ебучий камень

Вся соль в ORM, ёпта! До версии 4.1 эта штука была синхронной в корне. Если ты внутри своего красивого async def попробуешь сделать User.objects.all() — всё, приехали. Event loop встанет колом, как будто ему в рот чих-пых.

Что делать? Обёртывать, блядь! Есть такой спасательный круг — asgiref.sync.sync_to_async. Им и пользуемся.

Смотри, как это выглядит в деле:

from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
from django.http import JsonResponse

# Берём синхронную функцию и делаем из неё асинхронную обёртку
@sync_to_async
def get_user_count():
    return User.objects.count()

async def user_count_view(request):
    # Теперь можно спокойно ждать
    count = await get_user_count()
    return JsonResponse({'user_count': count})

Важное замечание, чтобы не облажаться: Начиная с Django 4.1, там уже понапихали нативных асинхронных методов в ORM — aget, acreate, acount и прочие. Так что теперь можно не городить эти обёртки, а писать почти как обычно, только с приставкой 'a'. Но если ты, как и я, ещё сидишь на старом проекте — учись оборачивать, хитрая жопа.