channels
channels copied to clipboard
Docs: Confusing example around background workers
I was looking at the docs for background workers:
https://channels.readthedocs.io/en/latest/topics/worker.html
These docs show a very useful example of how to configure the router for two worker channels:
application = ProtocolTypeRouter({
...
"channel": ChannelNameRouter({
"thumbnails-generate": consumers.GenerateConsumer,
"thumbnails-delete": consumers.DeleteConsumer,
}),
})
These channels refer to the channel_layer.send
command in the previous section of the doc.
However, the next paragraph deviates to a hypothetical example:
You’ll be specifying the type values of the individual events yourself when you send them, so decide what your names are going to be and write consumers to match. For example, here’s a basic consumer that expects to receive an event with type test.print, and a text value containing the text to print:
followed by a snippet for a PrintConsumer
class. While this is not un-useful, it's very confusing because it leaves out some important details. I'm left asking how the type
maps to the method name? Why did the .
become _
? How would the -
in the thumbnail example be translated to a method name? Perhaps instead of the PrintConsumer
example, a very simple stubbed-out implementation of the GenerateConsumer
or DeleteConsumer
could be written instead.
It's also unclear when I'd want to have multiple methods on a single consumer (using different type
s) versus having multiple consumers that handle fewer type
s each.
Last, the ChannelNameRouter
example on this page seems to conflict with the guidance in the Routing docs: https://channels.readthedocs.io/en/latest/topics/routing.html#channelnamerouter
Thanks in advance for you time and attention!
The . in the event type is transformed to a _ in the method name in channels.consumer.get_handler_name:
def get_handler_name(message):
"""
Looks at a message, checks it has a sensible type, and returns the
handler name for that type.
"""
# Check message looks OK
if "type" not in message:
raise ValueError("Incoming message has no 'type' attribute")
if message["type"].startswith("_"):
raise ValueError("Malformed type in message (leading underscore)")
# Extract type and replace . with _
return message["type"].replace(".", "_")
The '-' in the thumbnail example doesn't translate to a method name at all. 'thumbnails-generate' and 'thumbnails-delete' are group names, so you would send a message to that group and the worker will pick it up and handle it, by executing a method based on the event type.
Please assign me this task.
I also struggled with the type
-method_name
pair.
I followed the tutorial and find the brief explanation of the _ (underscore)
here
https://channels.readthedocs.io/en/stable/topics/consumers.html#basic-layout
from channels.consumer import SyncConsumer
class EchoConsumer(SyncConsumer):
def websocket_connect(self, event):
self.send({
"type": "websocket.accept",
})
def websocket_receive(self, event):
self.send({
"type": "websocket.send",
"text": event["text"],
})
Consumers are structured around a series of named methods corresponding to the
type
value of the messages they are going to receive, with any.
replaced by_
. The two handlers above are handlingwebsocket.connect
andwebsocket.receive
messages respectively.
However when I was really using it in my django app, the . (dot)
is not working (at times) and I have to resort to exact match (putting _
instead of .
in type
). I am really confused.
I agree that the docs should stress more about this rules. How can we improve the docs (and help newcomers like me)? Thanks.
I started researching because the example I was following gives me BackgroundTaskConsumer() takes no arguments
. I'm facing this exact confusion.
- I can't find the right combo of dots, underscores and method names for my consumer method to be called
- I'm not sure what to send
- I'm not sure what my routing should be
- I'm not sure what my runworker channel should be
I would love a complete example with send, routing, consumer and worker command. I'm not sure why the example leaps to test.print. If I discover the answer I'll leave it here.
This is what got working using channels 3.0.1, django 3.1.14 and daphne 3.0.2. In this example a message sent to the background-tasks channel gets routed to the method task_b in BackgroundTaskConsumer.
I send a message to my task like this
async_to_sync(channel_layer.send)('background-tasks', {'type': 'task_b', 'id': self.user.id})
the routing in my asgi.py looks like this. I was missing the parenthesis on BackgroundTaskConsumer()
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ChannelNameRouter,ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chat.settings")
django_asgi_app = get_asgi_application()
import core.routing
from task.consumers import BackgroundTaskConsumer
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(core.routing.websocket_urlpatterns))
),
'channel': ChannelNameRouter({
'background-tasks': BackgroundTaskConsumer(),
})
}
)
My consumer looks like this
from time import sleep
from channels.consumer import AsyncConsumer
class BackgroundTaskConsumer(AsyncConsumer):
async def task_a(self, message):
print('sleeping a' + "{}".format(message))
sleep(5)
print('waking a')
async def task_b(self, message):
print('sleeping b' + "{}".format(message))
sleep(5)
print('waking b')
I run my worker like this
python manage.py runworker background-tasks
When a message is sent, the worker outputs
sleeping b{'type': 'task_b', 'id': 1}
waking b