The SQL update does not seem to be being enacted upon, and no errors are being thrown either. Below is a simplified version of my code. For context, the "choice" field in the model is a Boolean Field with a default of False, and a user may (ideally) change this by sending a JSON package with the "CHOICE" event and "Yes" message.
consumers.py
import json
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from asgiref.sync import sync_to_async
from .models import Room
class Consumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_code = self.scope['url_route']['kwargs']['room_code']
#Websockets connection code
async def disconnect(self):
#Websockets disconnect code
async def receive(self, text_data):
response = json.loads(text_data)
event = response.get("event", None)
message = response.get("message", None)
if event == "CHOICE":
room_set = await sync_to_async(Room.objects.filter)(room_code=self.room_code)
room = await sync_to_async(room_set.first)()
if (not room.choice) and message["choice"] == 'Yes':
sync_to_async(room_set.update)(choice=True) #line seems to not be working
elif room.choice and message["choice"] == 'No':
sync_to_async(room_set.update)(choice=False)
#code to send message to group over Websockets
#code regarding other events
async def send_message(self, res):
#Websockets send message code
I've tried to only include the relevant code here, but if more is needed please let me know. Thanks in advance!
I fixed this issue by adding await before the sync_to_async(room.update)(choice=True) lines. It seems without an await it will move onto the next line of code before completing the SQL update, causing the update to not go through.
Related
I'm using a websocket to feed a chart with live data. As soon as the websocket is opened, I want to send the client historical data, so that the chart doesn't start loading only with the current values.
I want to do something like this, if it was possible:
from channels.db import database_sync_to_async
class StreamConsumer(AsyncConsumer):
async def websocket_connect(self, event):
# When the connection is first opened, also send the historical data
data = get_historical_data(1)
await self.send({
'type': 'websocket.accept',
'text': data # This doesn't seem possible
})
# This is what I use to send the messages with the live data
async def stream(self, event):
data = event["data"]
await self.send({
'type': 'websocket.send',
'text': data
})
#database_sync_to_async
def get_historical_data(length):
.... fetch data from the DB
What's the right way to do it?
First you need to accept the connection before sending data to the client. I assume you're using AsyncWebsocketConsumer(and you should) as the more low-level AsyncConsumer has not method websocket_connect
from channels.db import database_sync_to_async
class StreamConsumer(AsyncWebsocketConsumer):
async def websocket_connect(self, event):
# When the connection is first opened, also send the historical data
data = get_historical_data(1)
await self.accept()
await self.send(data)
# This is what I use to send the messages with the live data
async def stream(self, event):
data = event["data"]
await self.send(data)
I am using a combination of DRF 3.11.0 and Channels 2.4.0 to implement a backend, and it is hosted on Heroku on 1 dyno with a Redis resource attached. I have a socket on my React frontend that successfully sends/received from the backend server.
I am having an issues where any message sent back to the front end over the socket is being sent twice. I have confirmed through console.log that the front end is only pinging the back end once. I can confirm through print() inside of the API call that the function is only calling async_to_sync(channel_layer.group_send) once as well. The issue is coming from my consumer - when I use print(self.channel_name) inside of share_document_via_videocall(), I can see that two instances with different self.channel_names are being called (specific.AOQenhTn!fUybdYEsViaP and specific.AOQenhTn!NgtWxuiHtHBw. It seems like the consumer has connected to two separate channels, but I'm not sure why. When I put print() statements in my connect() I only see it go through the connect process once.
How do I ensure that I am only connected to one channel?
in settings.py:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
#"hosts": [('127.0.0.1', 6379)],
"hosts": [(REDIS_HOST)],
},
},
}
Consumer:
import json
from asgiref.sync import async_to_sync
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from .exceptions import ClientError
import datetime
from django.utils import timezone
class HeaderConsumer(AsyncWebsocketConsumer):
async def connect(self):
print("connecting")
await self.accept()
print("starting")
print(self.channel_name)
await self.send("request_for_token")
async def continue_connect(self):
print("continuing")
print(self.channel_name)
await self.get_user_from_token(self.scope['token'])
await self.channel_layer.group_add(
"u_%d" % self.user['id'],
self.channel_name,
)
#... more stuff
async def disconnect(self, code):
await self.channel_layer.group_discard(
"u_%d" % self.user['id'],
self.channel_name,
)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
if 'token' in text_data_json:
self.scope['token'] = text_data_json['token']
await self.continue_connect()
async def share_document_via_videocall(self, event):
# Send a message down to the client
print("share_document received")
print(event)
print(self.channel_name)
print(self.user['id'])
await self.send(text_data=json.dumps(
{
"type": event['type'],
"message": event["message"],
},
))
#database_sync_to_async
def get_user_from_token(self, t):
try:
print("trying token" + t)
token = Token.objects.get(key=t)
self.user = token.user.get_profile.json()
except Token.DoesNotExist:
print("failed")
self.user = AnonymousUser()
REST API call:
class ShareViaVideoChat(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
data = request.data
recipient_list = data['recipient_list']
channel_layer = get_channel_layer()
for u in recipient_list:
if u['id'] != None:
print("sending to:")
print('u_%d' % u['id'])
async_to_sync(channel_layer.group_send)(
'u_%d' % u['id'],
{'type': 'share_document_via_videocall',
'message': {
'document': {'data': {}},
'sender': {'name': 'some name'}
}
}
)
return Response()
with respect to you getting to calls with different channel names are you sure your frontend has not connected twice to the consumer? Check in the debug console in your browser.
i get same problem with nextjs as a frontend of Django channels WebSocket server.
and after searching i found the problem related with tow things:
1- react strict mode (the request sending twice) :
to disable react strict mode in next.js , go to module name "next.config.js" , and change the value for strict mode to false , as the following :
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
}
module.exports = nextConfig
2- in nextjs the code run twice (outside useEffect Hook) , one on server side and the second on the client side (which means each user will connect to websocket server twice, and got two channels name , and join to same group twice each time with different channel name . ) ,
so i changed my codes to connect with Django channels server only from client side , if you like see my full code / example , kindly visit the following URL , and note the checking code about "typeof window === "undefined":
frontend nextjs code :
https://stackoverflow.com/a/72288219/12662056
i don't know if my problem same your problem , but i hope that helpful.
Django Channels docs has following basic example of a Server Sent Events. AsyncHttpConsumer
from datetime import datetime
from channels.generic.http import AsyncHttpConsumer
class ServerSentEventsConsumer(AsyncHttpConsumer):
async def handle(self, body):
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
while True:
payload = "data: %s\n\n" % datetime.now().isoformat()
await self.send_body(payload.encode("utf-8"), more_body=True)
await asyncio.sleep(1)
I want to accept messages sent via channel_layer and send them as events.
I changed the handle method, so it subscribes the new channel to a group. And I'm planning to send messages to the channel layer via channel_layer.group_send
But I couldn't figure out how to get the messages sent to the group, within handle method. I tried awaiting for the channel_layer.receive, it doesn't seem to work.
class ServerSentEventsConsumer(AsyncHttpConsumer):
group_name = 'my_message_group'
async def myevent(self, event):
# according to the docs, this method will be called \
# when a group received a message with type 'myevent'
# I'm not sure how to get this event within `handle` method's while loop.
pass
async def handle(self, body):
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
while True:
payload = "data: %s\n\n" % datetime.now().isoformat()
result = await self.channel_receive()
payload = "data: %s\n\n" % 'received'
I'm sending the messages to channel_layer like below: ( from a management command)
def send_event(event_data):
group_name = 'my_message_group'
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
group_name,
{
'type': 'myevent',
'data': [event_data]
}
)
I had the same issue and I even went to dig into Django Channels code but without success.
... Until I found this answer in this (still opened) issue: https://github.com/django/channels/issues/1302#issuecomment-508896846
That should solve your issue.
In your case the code would be (or something quite similar):
class ServerSentEventsConsumer(AsyncHttpConsumer):
group_name = 'my_message_group'
async def http_request(self, message):
if "body" in message:
self.body.append(message["body"])
if not message.get("more_body"):
await self.handle(b"".join(self.body))
async def myevent(self, event):
# according to the docs, this method will be called \
# when a group received a message with type 'myevent'
# I'm not sure how to get this event within `handle` method's while loop.
pass
async def handle(self, body):
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
I am still pretty new to django-channels and directly starting with channels 2.0 so diverse examples are still a bit hard to find yet. I am trying to understand how can I create an asynchronous process in a channel and make a js client listen to it?
while connecting to my consumer, I am checking for a running stream on a thread and try to send back predictions on the channel. This process is asynchronous but I am not sure how to properly use an AsyncConsumer or AsyncJsonWebsocketConsumer.
so far I have a simple consumer like this:
consumers.py
import threading
from sklearn.externals import joblib
from channels.generic.websocket import JsonWebsocketConsumer
from .views import readStream
class My_Consumer(JsonWebsocketConsumer):
groups = ["broadcast"]
the_thread_name = 'TestThread'
def connect(self):
the_thread = None
self.accept()
# check if there is an active stream on a thread
for thread in threading.enumerate():
if thread.name == self.the_thread_name:
the_thread = thread
break
if the_thread == None:
xsLog.infoLog('No Stream found yet...')
self.close()
else:
the_stream = readStream()
#...
the_estimator = joblib.load('some_file','r')
the_array = np.zeros(shape=(1,93), dtype=float)
self.predict(the_thread,the_stream,the_array,the_estimator)
def disconnect(self,close_code):
pass
async def predict(self,the_thread,the_stream,the_array,the_estimator):
while the_thread.isAlive():
sample = await the_stream.read()
if sample != [0.0,0.0,0.0] and 'nan' not in str(sample):
the_array = np.roll(self.the_array,-3)
the_array[0][-3] = sample[0]
the_array[0][-2] = sample[1]
the_array[0][-1] = sample[2]
new_val = await '{},{}'.format(the_estimator.predict(the_array)[0][0],the_estimator.predict(the_array)[0][1])
await self.send_json(new_val)
my js client is pretty simple and tries to listen:
const webSocketBridge = new channels.WebSocketBridge();
webSocketBridge.connect('some_route');
webSocketBridge.socket.addEventListener('open', function() {
console.log("Connected to WebSocket");
});
webSocketBridge.listen(function(message, stream) {console.log(message);});
EDIT
from the above mentionned question I tried the following simplified solution with an AsyncJsonWebsocketConsumer:
import threading
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from channels.db import database_sync_to_async
class My_Consumer(AsyncJsonWebsocketConsumer):
groups = ["broadcast"]
the_thread = None
the_data = None
#
#database_sync_to_async
def getUserData(self,the_user):
return models.UserData.objects.filter(user=the_user,datatype='codebooks')
#
async def connect(self):
await self.accept()
for thread in threading.enumerate():
if thread.name == 'some_thread_name':
self.the_thread = thread
break
if self.the_thread == None:
xsLog.infoLog('No Stream found yet...')
await self.close()
else:
the_data = await self.getUserData(the_user)
#
async def receive(self,content=None,text_data=None):
await self.predict(self.the_thread,self.the_data)
#
async def predict(self,the_thread,the_data):
"""..."""
while the_thread.isAlive():
# doing something here
new_data = data_from_thread + the_data
self.send_json(new_data)
#
async def disconnect(self,close_code):
await self.close()
and then I am sending once a message by the JS client to init the receive method of the consumer:
webSocketBridge.socket.addEventListener('open', function() {
console.log("Connected to WebSocket, Launching Predictions");
webSocketBridge.send('');
});
The issue here is that while the predict coroutine is launched it is blocking the whole receive one and my js client cannot receive any message through:
webSocketBridge.listen(function(message, stream) {
console.log(message.stream);
});
the disconnect coroutine is then also not recognized either.
Scrapy application, but the question is really about the Python language - experts can probably answer this immediately without knowing the framework at all.
I've got a class called CrawlWorker that knows how to talk to so-called "spiders" - schedule their crawls, and manage their lifecycle.
There's a TwistedRabbitClient that has-one CrawlWorker. The client only knows how to talk to the queue and hand off messages to the worker - it gets completed work back from the worker asynchronously by using the worker method connect_to_scrape below to connect to a signal emitted by a running spider:
def connect_to_scrape(self, callback):
self._connect_to_signal(callback, signals.item_scraped)
def _connect_to_signal(self, callback, signal):
if signal is signals.item_scraped:
def _callback(item, response, sender, signal, spider):
scrape_config = response.meta['scrape_config']
delivery_tag = scrape_config.delivery_tag
callback(item.to_dict(), delivery_tag)
else:
_callback = callback
dispatcher.connect(_callback, signal=signal)
So the worker provides a layer of "work deserialization" for the Rabbit client, who doesn't know about spiders, responses, senders, signals, items (anything about the nature of the work itself) - only dicts that'll be published as JSON with their delivery tags.
So the callback below isn't registering properly (no errors either):
def publish(self, item, delivery_tag):
self.log('item_scraped={0} {1}'.format(item, delivery_tag))
publish_message = json.dumps(item)
self._channel.basic_publish(exchange=self.publish_exchange,
routing_key=self.publish_key,
body=publish_message)
self._channel.basic_ack(delivery_tag=delivery_tag)
But if I remove the if branch in _connect_to_signal and connect the callback directly (and modify publish to soak up all the unnecessary arguments), it works.
Anyone have any ideas why?
So, I figured out why this wasn't working, by re-stating it in a more general context:
import functools
from scrapy.signalmanager import SignalManager
SIGNAL = object()
class Sender(object):
def __init__(self):
self.signals = SignalManager(self)
def wrap_receive(self, receive):
#functools.wraps(receive)
def wrapped_receive(message, data):
message = message.replace('World', 'Victor')
value = data['key']
receive(message, value)
return wrapped_receive
def bind(self, receive):
_receive = self.wrap_receive(receive)
self.signals.connect(_receive, signal=SIGNAL,
sender=self, weak=False)
def send(self):
message = 'Hello, World!'
data = {'key': 'value'}
self.signals.send_catch_log(SIGNAL, message=message, data=data)
class Receiver(object):
def __init__(self, sender):
self.sender = sender
self.sender.bind(self.receive)
def receive(self, message, value):
"""Receive data from a Sender."""
print 'Receiver received: {0} {1}.'.format(message, value)
if __name__ == '__main__':
sender = Sender()
receiver = Receiver(sender)
sender.send()
This works if and only if weak=False.
The basic problem is that when connecting to the signal, weak=False needs to be specified. Hopefully someone smarter than me can expound on why that's needed.