Django REST Framework: Почему async представления не работают
Решение ошибки AssertionError при использовании async представлений в Django REST Framework. Узнайте, как правильно настроить асинхронные API с adrf, sync_to_async и Django Ninja.
Почему Django REST Framework не распознает асинхронное представление?
Я получаю ошибку AssertionError: Expected a 'Response', 'HttpResponse' or 'StreamingHttpResponse' to be returned from the view, but received a class coroutine при использовании асинхронного представления в Django REST Framework.
Вот мой код:
@api_view(['POST'])
@permission_classes([AllowAny])
async def create_table(request):
table_title = request.data.get['table_title']
cols = request.data.get['cols'].split(';')
rows = request.data.get['rows']
async with pool.DB_POOL.acquire() as connection:
create_statment = f'''CREATE TABLE IF NOT EXISTS {table_title}\n'''
for col in cols:
create_statment += f'{col} TEXT NOT NULL,\n'
await connection.execute(create_statment)
return Response(
{'proccess': 'done'},
status=status.HTTP_201_CREATED
)
'''определение промежуточного слоя DBPoolMiddleware для инициализации и закрытия пула подключений в соответствии с состоянием сервера'''
import os
from django.core.asgi import get_asgi_application
import django
from . import pool
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_asgi_application()
class DBPoolMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope['type'] == 'lifespan':
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
django.setup()
await pool.create_db_pool()
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
await pool.shutdown_pool()
await send({'type': 'lifespan.shutdown.complete'})
break
else:
break
else:
await self.app(scope, receive, send)
application = DBPoolMiddleware(application)
WSGI_APPLICATION = 'config.wsgi.application'
ASGI_APLICATION = 'config.asgi.aplication'
requirements.txt
asgiref==3.10.0
asyncpg==0.30.0
cffi==2.0.0
click==8.3.0
colorama==0.4.6
cryptography==46.0.3
Django==5.2.8
djangorestframework==3.16.1
h11==0.16.0
jwt==1.4.0
pycparser==2.23
sqlparse==0.5.3
tzdata==2025.2
uvicorn==0.38.0
Я запускаю сервер командой uvicorn config.asgi:application --host 0.0.0.0 --port 8000, после отправляю запрос через Postman и получаю ошибку.
Я думал, что современные версии Django должны поддерживать асинхронные представления без дополнительных декораторов.
P.S. Я знаю про инъекции, но пока просто экспериментирую.
Django REST Framework не распознает ваше асинхронное представление, потому что декоратор @api_view ожидает синхронную функцию, а не асинхронную. Даже в современных версиях Django и DRF требуется специальная обработка для асинхронных представлений.
Содержание
- Основная проблема и почему она возникает
- Решение 1: Использование стороннего пакета adrf
- Решение 2: Обертка sync_to_async
- Решение 3: Переход на Django Ninja
- Решение 4: Создание кастомного декоратора
- Настройка ASGI для производительности
- Заключение
Основная проблема и почему она возникает
Ваша проблема заключается в том, что стандартный декоратор @api_view в Django REST Framework предназначен для работы с синхронными функциями. Когда вы используете асинхронную функцию со стандартным декоратором DRF, он пытается вызвать её как обычную функцию, но получает корутину вместо результата.
@api_view(['POST']) # Этот декоратор ожидает синхронную функцию
async def create_table(request): # Но вы передаете асинхронную
# ...
Из документации Django видно, что Django поддерживает асинхронные представления, но DRF требует специальной обработки. Как объясняется в обсуждении на GitHub, для асинхронных представлений нужны либо специальные декораторы, либо использование сторонних решений.
Решение 1: Использование стороннего пакета adrf
Самый простой способ решить проблему — использовать пакет adrf, который предоставляет асинхронную поддержку для Django REST Framework.
pip install adrf
Затем измените ваш код:
from adrf.decorators.api_view import api_view
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status
@api_view(['POST'])
@permission_classes([AllowAny])
async def create_table(request):
table_title = request.data.get('table_title')
cols = request.data.get('cols').split(';')
rows = request.data.get('rows')
async with pool.DB_POOL.acquire() as connection:
create_statement = f'''CREATE TABLE IF NOT EXISTS {table_title}\n'''
for col in cols:
create_statement += f'{col} TEXT NOT NULL,\n'
await connection.execute(create_statement)
return Response(
{'process': 'done'},
status=status.HTTP_201_CREATED
)
Как показано в примере из документации adrf, для viewset все обработчики должны быть асинхронными. Пакет adrf полностью совместим с Django 5.2 и предоставляет ту же функциональность, что и стандартный DRF, но с поддержкой async/await.
Решение 2: Обертка sync_to_async
Если вы не хотите использовать сторонние пакеты, можете обернуть вашу асинхронную функцию с помощью sync_to_async:
from asgiref.sync import sync_to_async
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status
@api_view(['POST'])
@permission_classes([AllowAny])
def create_table(request):
# Создаем асинхронную обертку
async def async_create():
table_title = request.data.get('table_title')
cols = request.data.get('cols').split(';')
rows = request.data.get('rows')
async with pool.DB_POOL.acquire() as connection:
create_statement = f'''CREATE TABLE IF NOT EXISTS {table_title}\n'''
for col in cols:
create_statement += f'{col} TEXT NOT NULL,\n'
await connection.execute(create_statement)
return {'process': 'done'}
# Выполняем асинхронную функцию в синхронном контексте
return Response(
sync_to_async(async_create)(),
status=status.HTTP_201_CREATED
)
Этот подход использует функцию sync_to_async, которая позволяет вызывать асинхронный код из синхронного контекста. Однако этот метод менее эффективен, так как блокирует выполнение во время ожидания завершения асинхронной операции.
Решение 3: Переход на Django Ninja
Для современных асинхронных API рекомендуется использовать Django Ninja, который изначально разработан для работы с async/await:
pip install django-ninja
Пример использования:
from ninja import NinjaAPI
from ninja.security import APIKeyAuth
api = NinjaAPI()
@api.post("/create-table", auth=None)
async def create_table(request, table_title: str, cols: str, rows: int):
cols_list = cols.split(';')
async with pool.DB_POOL.acquire() as connection:
create_statement = f'''CREATE TABLE IF NOT EXISTS {table_title}\n'''
for col in cols_list:
create_statement += f'{col} TEXT NOT NULL,\n'
await connection.execute(create_statement)
return {'process': 'done'}
Django Ninja полностью асинхронный и предоставляет современный синтаксис для API. Он идеально подходит для высоконагруженных асинхронных приложений.
Решение 4: Создание кастомного декоратора
Вы можете создать собственный декоратор, который будет правильно обрабатывать асинхронные функции:
from functools import wraps
from asyncio import iscoroutinefunction
from rest_framework.response import Response
from rest_framework.decorators import api_view as original_api_view
def async_api_view(http_method_names):
def decorator(view_func):
if iscoroutinefunction(view_func):
@wraps(view_func)
def wrapped_view(*args, **kwargs):
# Запускаем асинхронную функцию и ожидаем результата
coroutine = view_func(*args, **kwargs)
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
result = loop.run_until_complete(coroutine)
return result
finally:
loop.close()
return original_api_view(http_method_names)(wrapped_view)
else:
return original_api_view(http_method_names)(view_func)
return decorator
Затем используйте его вместо стандартного декоратора:
@async_api_view(['POST'])
@permission_classes([AllowAny])
async def create_table(request):
# ваш код
Настройка ASGI для производительности
Убедитесь, что ваша ASGI-конфигурация оптимизирована для асинхронной работы. В вашем asgi.py есть правильная настройка, но можно улучшить производительность:
import os
from django.core.asgi import get_asgi_application
import django
from . import pool
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
application = get_asgi_application()
class DBPoolMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope['type'] == 'lifespan':
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
django.setup()
await pool.create_db_pool()
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
await pool.shutdown_pool()
await send({'type': 'lifespan.shutdown.complete'})
break
else:
break
else:
# Добавляем обработку ошибок
try:
await self.app(scope, receive, send)
except Exception as e:
await send({
'type': 'http.response.start',
'status': 500,
'headers': [(b'content-type', b'application/json')],
})
await send({
'type': 'http.response.body',
'body': b'{"error": "Internal server error"}',
})
application = DBPoolMiddleware(application)
Для запуска используйте uvicorn с правильными настройками:
uvicorn config.asgi:application --host 0.0.0.0 --port 8000 --workers 1 --loop asyncio
Важно использовать --workers 1 для асинхронного приложения, так как каждый воркер должен иметь свой event loop.
Заключение
Основная причина вашей проблемы — несовместимость стандартного декоратора @api_view с асинхронными функциями в Django REST Framework. Вот основные решения:
- Используйте
adrf— самый простой способ добавить асинхронную поддержку в существующий DRF-проект - Оберните с
sync_to_async— подходит для простых случаев, но менее эффективно - Перейдите на Django Ninja — современный фреймворк, изначально созданный для асинхронных API
- Создайте кастомный декоратор — для полного контроля над процессом
Для максимальной производительности в асинхронном режиме рекомендуется использовать Django Ninja или полностью перейти на adrf. В текущей версии вашего кода ошибка возникает потому, что DRF ожидает синхронный обработчик, а получает корутину.
Даже в Django 5.2 поддержка асинхронного ORM и других компонентов все еще находится в разработке, как упоминается в документации Django. Поэтому для полноценной асинхронной работы лучше использовать специализированные решения.