django / channels

Developer-friendly asynchrony for Django

Home Page:https://channels.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AsyncJsonWebsocketConsumer inheritor class can't be used in await expression

Mikael-Caetano opened this issue · comments

I am upgrading my django channels application, which was previously using channels 2.4.0 to channels 4.0.0. But I am receiving the following exception when trying to connect to my consumer through websocket:

ds-chat-chat-1   | System check identified no issues (0 silenced).
ds-chat-chat-1   | August 03, 2023 - 12:28:10
ds-chat-chat-1   | Django version 4.1.10, using settings 'chat.settings'
ds-chat-chat-1   | Starting ASGI/Daphne version 4.0.0 development server at http://0.0.0.0:8080/
ds-chat-chat-1   | Quit the server with CONTROL-C.
ds-chat-chat-1   | WebSocket HANDSHAKING /gws [172.23.0.1:51638]
ds-chat-chat-1   | Exception inside application: object ChatConsumer can't be used in 'await' expression
ds-chat-chat-1   | Traceback (most recent call last):
ds-chat-chat-1   |   File "/usr/local/lib/python3.10/site-packages/django/contrib/staticfiles/handlers.py", line 101, in __call__
ds-chat-chat-1   |     return await self.application(scope, receive, send)
ds-chat-chat-1   |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 62, in __call__
ds-chat-chat-1   |     return await application(scope, receive, send)
ds-chat-chat-1   |   File "/usr/local/lib/python3.10/site-packages/channels/sessions.py", line 47, in __call__
ds-chat-chat-1   |     return await self.inner(dict(scope, cookies=cookies), receive, send)
ds-chat-chat-1   |   File "/usr/local/lib/python3.10/site-packages/channels/sessions.py", line 263, in __call__
ds-chat-chat-1   |     return await self.inner(wrapper.scope, receive, wrapper.send)
ds-chat-chat-1   |   File "/usr/local/lib/python3.10/site-packages/channels/routing.py", line 116, in __call__
ds-chat-chat-1   |     return await application(
ds-chat-chat-1   | TypeError: object ChatConsumer can't be used in 'await' expression
ds-chat-chat-1   | WebSocket DISCONNECT /gws [172.23.0.1:51638]

What I did for updating was to obviously update the libraries in my requirements.txt:

boto3==1.27.0
channels==4.0.0
channels-redis==4.1.0
daphne==4.0.0
Django==4.1.10
django-redis==5.3.0
django-cors-headers==3.14.0
django-filter==23.2
djangorestframework==3.14.0
dj-database-url==2.0.0
django-storages==1.13.2
drf-yasg==1.21.6
Markdown==3.2.1
mutagen==1.45.1
mysqlclient==2.1.1
python-decouple
pytz
redis==4.6.0
sentry-sdk==1.27.0
uwsgi

Include daphne in my installed apps, right above "django.contrib.staticfiles".

And include the "http" key in my application:
routing.py

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.sessions import SessionMiddlewareStack
from django.core.asgi import get_asgi_application

from chat.apps.core import routing as core_routing

application = ProtocolTypeRouter(
    {
        "http": get_asgi_application(),
        "websocket": SessionMiddlewareStack(
            URLRouter(core_routing.websocket_urlpatterns)
        ),
    }
)

asgi.py:

"""
ASGI config for ChatAPI project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""

import os

import django
from channels.routing import get_default_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chat.settings")
django.setup()
application = get_default_application()

channels settings:

ASGI_APPLICATION = "chat.routing.application"
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [config("REDIS_URL")],
            "capacity": 1500,
            # "expiry": 10,
        },
    }
}

consumers.py:

from __future__ import unicode_literals

import datetime
import json
from urllib import parse

from channels.db import database_sync_to_async
from channels.generic.websocket import (
    AsyncJsonWebsocketConsumer,
    AsyncWebsocketConsumer,
)

from .models import UserCompany


# Returns user by validating Token
class TokenAuth(AsyncWebsocketConsumer):
    @database_sync_to_async
    def get_user(self):
        scope = self.scope
        try:
            # Parse the scope and gets the token
            query = parse.parse_qs(scope["query_string"].decode("utf-8"))[
                "token"
            ][0]
            if query:
                token = UserCompany.objects.get(key=query)
                # Returns token user
                return token.key
            return None

        except:
            return None


class ChatConsumer(AsyncJsonWebsocketConsumer, TokenAuth):
    async def connect(self):
        # connect room
        key_ = await self.get_user()
        self.group_name = str(key_)
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        try:
            await self.update_user_status(self.group_name, True)
        except:
            pass
        await self.accept()

    async def disconnect(self, _):
        # Leave room group
        try:
            await self.update_user_status(self.group_name, False)
        except:
            pass
        await self.channel_layer.group_discard(
            self.group_name, self.channel_name
        )

Any ideas?

If you couldn't solve your problem, check your routers:

# chat/routing.py
from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]

The as_asgi() method should be used.
https://channels.readthedocs.io/en/latest/tutorial/part_2.html