Search code examples
pythondjangodjango-channels

Django channels Async Websocket throwing Error while trying to use database query


I don't get it. Even though I've converted all the required fields in async await. But still I'm getting the following error:

HTTP GET /chat/8/ 200 [0.02, 127.0.0.1:51354]
WebSocket HANDSHAKING /ws/chat/8/ [127.0.0.1:51356]
Exception inside application: You cannot call this from an async context - use a thread or sync_to_async.
Traceback (most recent call last):
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/channels/sessions.py", line 183, in __call__
    return await self.inner(receive, self.send)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/channels/middleware.py", line 41, in coroutine_call
    await inner_instance(receive, send)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/channels/consumer.py", line 58, in __call__
    await await_many_dispatch(
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/channels/utils.py", line 51, in await_many_dispatch
    await dispatch(result)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/channels/consumer.py", line 73, in dispatch
    await handler(message)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/channels/generic/websocket.py", line 175, in websocket_connect
    await self.connect()
  File "/media/fahadmdkamal/WORK/B-DOPS/api/chat/consumers.py", line 23, in connect
    other_user = await sync_to_async(User.objects.get(id=others_id))
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/db/models/query.py", line 411, in get
    num = len(clone)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/db/models/query.py", line 258, in __len__
    self._fetch_all()
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/db/models/query.py", line 1261, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/db/models/query.py", line 57, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1150, in execute_sql
    cursor = self.connection.cursor()
  File "/media/fahadmdkamal/WORK/B-DOPS/api/env/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
    raise SynchronousOnlyOperation(message)
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
WebSocket DISCONNECT /ws/chat/8/ [127.0.0.1:51356]

The error says I need to call sync_to_async. Even though I called the function I still get this error. I wanted to print other user which was fatched from the database. But it's not even going to that statement which means my database calling might be not in proper way. I've seen some code structures of others which seems exactly same to mine. What am I missing ?

My Consumer Code:

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):

        # Get message sender information
        self.me = self.scope['user']
        # print(self.me)

        # Get other user's ID from the url
        others_id = self.scope['url_route']['kwargs']['user_id']
        # Get Other user object using the collected object.
        other_user = await   sync_to_async(User.objects.get(id=others_id))
        print(other_user)

        # Get or create personal thread for the conversation
        self.thread_obj = await sync_to_async(
            Thread.objects.get_or_create_personal_thread(
                self.me,
                other_user)
        )

        # Creating room with the thread object id.
        self.room_name = f'presonal_thread_{self.thread_obj.id}'

        # Adding room and channel name to to the channel layer group
        await self.channel_layer.group_add(
            self.room_name,
            self.channel_name
        )

        # Accecpting the connection
        await self.accept()

        print(f'[{self.channel_name}] - You are connected')

    # Codes that will be run while disconnecting the websocket
    async def disconnect(self, close_code):
        print(f'[{self.channel_name}] - Disonnected')

        # remove room and channel name from the channel layer
        await self.channel_layer.group_discard(
            self.room_name,
            self.channel_name
        )

    # Receive message from WebSocket
    async def receive(self, text_data):
        # Decode json raw data that was sent from user
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        print(f'[{self.channel_name}] - Recieved message - {message}')

        # Store the received data
        await self.store_message(message=message)

        # Send message to room group so that it can send to the all users
        await self.channel_layer.group_send(
            self.room_name,
            {
                'type': 'chat_message',
                'message': message,
            }
        )

    # Receive message from room group
    async def chat_message(self, event):
        print(f'[{self.channel_name}] - Message sent - {event["message"]}')

        message = event['message']

        # Send message to WebSocket
        await self.send(text_data=json.dumps({
            'message': message,
            'user_id': self.scope['user'].id,
            'username': self.scope['user'].username
        }))

    @database_sync_to_async
    def store_message(self, message):
        Message.objects.create(sender=self.me,
                               thread=self.thread_obj,
                               message=message)


My Routing Code:

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path('ws/chat/<int:user_id>/', consumers.ChatConsumer)
        ])
    ),
})

Since I'm new with django channels it would be even more helpful if you could say is the whole AsyncWebsocketConsumer structure is ok ?


Solution

  • Ohh.. Solved it..

    Actually I was constructing the await sync_to_async call improperly. The function should be called seperately the params should be called in separate parenthesis.

    Old and wrong Structure:

    other_user = await   sync_to_async(User.objects.get(id=others_id))
    

    Updated and corrent structure:

    other_user = await sync_to_async(User.objects.get)(id=others_id)
    

    Here I'm calling the sync_to_async method to get user object in first parenthesis and in second parenthesis I'm providing the query params.