django-plotly-dash
django-plotly-dash copied to clipboard
Live-Updating: `ValueError: No route found for path 'dpd/ws/channel'`
I am attempting to implement live-updating according to the documentation. I encountered the issue described in #368 and, following the suggestion in #369 to copy websocketbridge.js to my static folder, I am now encountering the above error on the server.
This error is printed every few seconds.
Here is the full stack trace (note: custom formatting):
2022-01-11 16:10:11.473 | ERROR | daphne.server:application_checker:290 - Exception inside application: No route found for path 'dpd/ws/channel'.
Traceback (most recent call last):
> File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/staticfiles.py", line 44, in __call__
return await self.application(scope, receive, send)
│ │ │ │ └ functools.partial(<bound method Server.handle_reply of <daphne.server.Server object at 0x7fbc3518ca60>>, <WebSocketProtocol c...
│ │ │ └ <bound method Queue.get of <Queue at 0x7fbc25cf32e0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
│ │ └ {'type': 'websocket', 'path': '/dpd/ws/channel', 'raw_path': b'/dpd/ws/channel', 'headers': [(b'host', b'127.0.0.1:8000'), (b...
│ └ <channels.routing.ProtocolTypeRouter object at 0x7fbc42648ca0>
└ <channels.staticfiles.StaticFilesWrapper object at 0x7fbc36373ca0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/routing.py", line 71, in __call__
return await application(scope, receive, send)
│ │ │ └ functools.partial(<bound method Server.handle_reply of <daphne.server.Server object at 0x7fbc3518ca60>>, <WebSocketProtocol c...
│ │ └ <bound method Queue.get of <Queue at 0x7fbc25cf32e0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
│ └ {'type': 'websocket', 'path': '/dpd/ws/channel', 'raw_path': b'/dpd/ws/channel', 'headers': [(b'host', b'127.0.0.1:8000'), (b...
└ <channels.sessions.CookieMiddleware object at 0x7fbc42648af0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/sessions.py", line 47, in __call__
return await self.inner(dict(scope, cookies=cookies), receive, send)
│ │ │ │ │ └ functools.partial(<bound method Server.handle_reply of <daphne.server.Server object at 0x7fbc3518ca60>>, <WebSocketProtocol c...
│ │ │ │ └ <bound method Queue.get of <Queue at 0x7fbc25cf32e0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
│ │ │ └ {'csrftoken': 'ksFsl6mpPkmQrJeeq2kedERpL4Vd9JuRoORFC976y5NQANbzxgrag27QcUyoDx75', 'sessionid': 'phyh1f1jkryztx0c16ndjy3e7wnx0...
│ │ └ {'type': 'websocket', 'path': '/dpd/ws/channel', 'raw_path': b'/dpd/ws/channel', 'headers': [(b'host', b'127.0.0.1:8000'), (b...
│ └ <channels.sessions.SessionMiddleware object at 0x7fbc426486d0>
└ <channels.sessions.CookieMiddleware object at 0x7fbc42648af0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/sessions.py", line 263, in __call__
return await self.inner(wrapper.scope, receive, wrapper.send)
│ │ │ │ │ │ └ <function InstanceSessionWrapper.send at 0x7fbc350f4af0>
│ │ │ │ │ └ <channels.sessions.InstanceSessionWrapper object at 0x7fbc34309580>
│ │ │ │ └ <bound method Queue.get of <Queue at 0x7fbc25cf32e0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
│ │ │ └ {'type': 'websocket', 'path': '/dpd/ws/channel', 'raw_path': b'/dpd/ws/channel', 'headers': [(b'host', b'127.0.0.1:8000'), (b...
│ │ └ <channels.sessions.InstanceSessionWrapper object at 0x7fbc34309580>
│ └ <channels.auth.AuthMiddleware object at 0x7fbc426485e0>
└ <channels.sessions.SessionMiddleware object at 0x7fbc426486d0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/auth.py", line 185, in __call__
return await super().__call__(scope, receive, send)
│ │ └ <bound method InstanceSessionWrapper.send of <channels.sessions.InstanceSessionWrapper object at 0x7fbc34309580>>
│ └ <bound method Queue.get of <Queue at 0x7fbc25cf32e0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
└ {'type': 'websocket', 'path': '/dpd/ws/channel', 'raw_path': b'/dpd/ws/channel', 'headers': [(b'host', b'127.0.0.1:8000'), (b...
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/middleware.py", line 26, in __call__
return await self.inner(scope, receive, send)
│ │ │ │ └ <bound method InstanceSessionWrapper.send of <channels.sessions.InstanceSessionWrapper object at 0x7fbc34309580>>
│ │ │ └ <bound method Queue.get of <Queue at 0x7fbc25cf32e0 maxsize=0 _queue=[{'type': 'websocket.connect'}] tasks=1>>
│ │ └ {'type': 'websocket', 'path': '/dpd/ws/channel', 'raw_path': b'/dpd/ws/channel', 'headers': [(b'host', b'127.0.0.1:8000'), (b...
│ └ <channels.routing.URLRouter object at 0x7fbc42690430>
└ <channels.auth.AuthMiddleware object at 0x7fbc426485e0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/routing.py", line 168, in __call__
raise ValueError("No route found for path %r." % path)
└ 'dpd/ws/channel'
ValueError: No route found for path 'dpd/ws/channel'.
My urls.py:
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("", include("apps.index.urls")),
path("admin/", admin.site.urls),
path("circuits/", include("apps.circuits.urls")),
path("django_plotly_dash/", include("django_plotly_dash.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Also, possibly related to this, I encounter these errors on the client when the tag {% plotly_message_pipe %} is added to my template:

I am directly inserting my app into the template using {% plotly_direct %} with the required {% plotly_header %} and {% plotly_footer %} tags as described in the docs.
Thanks for your time, Jules
Further data for the bug report, my PLOTLY_DASH in my settings.py:
PLOTLY_DASH = {
# Route used for the message pipe websocket connection
"ws_route": "dpd/ws/channel",
# Route used for direct http insertion of pipe messages
"http_route": "dpd/views",
# Flag controlling existince of http poke endpoint
"http_poke_enabled": True,
# Insert data for the demo when migrating
"insert_demo_migrations": False,
# Timeout for caching of initial arguments in seconds
"cache_timeout_initial_arguments": 60,
# Name of view wrapping function
"view_decorator": None,
# Flag to control location of initial argument storage
"cache_arguments": True,
# Flag controlling local serving of assets
"serve_locally": True,
}
Fixed by manually implementing the route from django_plotly_dash.routing.
If you have a routing.py or asgi.py (I have asgi.py), you may need to implement something like the following:
from django_plotly_dash.consumers import MessageConsumer
from django_plotly_dash.util import pipe_ws_endpoint_name
asgi_app = get_asgi_application()
application = ProtocolTypeRouter(
dict(
http=asgi_app,
websocket=AuthMiddlewareStack(
URLRouter(
[
re_path(r"^ws/my_webpage/$", MyConsumer.as_asgi()),
re_path(pipe_ws_endpoint_name(), MessageConsumer.as_asgi()),
]
),
),
)
)
@GibbsConsulting I don't see any documentation about this requirement if this behavior is intended.
My secondary error with {% plotly_message_pipe %} is also resolved with the above routing modification.
The relevant documentation is here - it is certainly sparse and any contributions to improve it are most welcome.
It is not clear to me why you've had to explicitly add the routes like this - if you look at the demo app in the codebase the configuration of these routes is handled by importing the asgi application directly from the django-plotly-dash package.
@GibbsConsulting Aye. I attempted to follow the demo's example, but that unfortunately didn't work. It results in this TypeError:
2022-01-13 08:09:43.393 | ERROR | daphne.server:application_checker:290 - Exception inside application: __init__() takes 1 positional argument but 2 were given
Traceback (most recent call last):
> File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/staticfiles.py", line 44, in __call__
return await self.application(scope, receive, send)
│ │ │ │ └ functools.partial(<bound method Server.handle_reply of <daphne.server.Server object at 0x7fd959b8d160>>, <WebRequest at 0x7fd...
│ │ │ └ <bound method Queue.get of <Queue at 0x7fd959a437f0 maxsize=0 _queue=[{'type': 'http.request', 'body': b'', 'more_body': Fals...
│ │ └ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
│ └ <channels.routing.ProtocolTypeRouter object at 0x7fd959b6fc40>
└ <channels.staticfiles.StaticFilesWrapper object at 0x7fd95a5887c0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/routing.py", line 71, in __call__
return await application(scope, receive, send)
│ │ │ └ functools.partial(<bound method Server.handle_reply of <daphne.server.Server object at 0x7fd959b8d160>>, <WebRequest at 0x7fd...
│ │ └ <bound method Queue.get of <Queue at 0x7fd959a437f0 maxsize=0 _queue=[{'type': 'http.request', 'body': b'', 'more_body': Fals...
│ └ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
└ <channels.sessions.CookieMiddleware object at 0x7fd959b6fbe0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/sessions.py", line 47, in __call__
return await self.inner(dict(scope, cookies=cookies), receive, send)
│ │ │ │ │ └ functools.partial(<bound method Server.handle_reply of <daphne.server.Server object at 0x7fd959b8d160>>, <WebRequest at 0x7fd...
│ │ │ │ └ <bound method Queue.get of <Queue at 0x7fd959a437f0 maxsize=0 _queue=[{'type': 'http.request', 'body': b'', 'more_body': Fals...
│ │ │ └ {'csrftoken': 'FelAA8uVFBiosVk7e0pwYSCGE9W1qWMVIqSC2lfBhQE2szSqoOEfULWSjasdBpbQ', 'sessionid': 'ruk7zncwpfn0obeljb0hq70dgoaa0...
│ │ └ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
│ └ <channels.sessions.SessionMiddleware object at 0x7fd959b6fb80>
└ <channels.sessions.CookieMiddleware object at 0x7fd959b6fbe0>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/sessions.py", line 263, in __call__
return await self.inner(wrapper.scope, receive, wrapper.send)
│ │ │ │ │ │ └ <function InstanceSessionWrapper.send at 0x7fd959b6b310>
│ │ │ │ │ └ <channels.sessions.InstanceSessionWrapper object at 0x7fd959a43910>
│ │ │ │ └ <bound method Queue.get of <Queue at 0x7fd959a437f0 maxsize=0 _queue=[{'type': 'http.request', 'body': b'', 'more_body': Fals...
│ │ │ └ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
│ │ └ <channels.sessions.InstanceSessionWrapper object at 0x7fd959a43910>
│ └ <channels.auth.AuthMiddleware object at 0x7fd959b6fb20>
└ <channels.sessions.SessionMiddleware object at 0x7fd959b6fb80>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/auth.py", line 185, in __call__
return await super().__call__(scope, receive, send)
│ │ └ <bound method InstanceSessionWrapper.send of <channels.sessions.InstanceSessionWrapper object at 0x7fd959a43910>>
│ └ <bound method Queue.get of <Queue at 0x7fd959a437f0 maxsize=0 _queue=[{'type': 'http.request', 'body': b'', 'more_body': Fals...
└ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/middleware.py", line 26, in __call__
return await self.inner(scope, receive, send)
│ │ │ │ └ <bound method InstanceSessionWrapper.send of <channels.sessions.InstanceSessionWrapper object at 0x7fd959a43910>>
│ │ │ └ <bound method Queue.get of <Queue at 0x7fd959a437f0 maxsize=0 _queue=[{'type': 'http.request', 'body': b'', 'more_body': Fals...
│ │ └ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
│ └ <channels.routing.URLRouter object at 0x7fd959b6fac0>
└ <channels.auth.AuthMiddleware object at 0x7fd959b6fb20>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/channels/routing.py", line 150, in __call__
return await application(
└ <function double_to_single_callable.<locals>.new_application at 0x7fd959a4e940>
File "/home/dur10420/miniconda3/envs/dtautomation/lib/python3.9/site-packages/asgiref/compatibility.py", line 33, in new_application
instance = application(scope)
│ └ {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/favicon.ico', 'raw_path': b'/favicon.ico', 'root_path': ''...
└ <class 'channels.http.AsgiHandler'>
TypeError: __init__() takes 1 positional argument but 2 were given
The code (asgi.py):
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import re_path
from django_plotly_dash.consumers import MessageConsumer
from django_plotly_dash.util import pipe_ws_endpoint_name
from apps.circuits.consumers import UpdateDeviceStatus
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dtautomation.settings")
# asgi_app = get_asgi_application()
# application = ProtocolTypeRouter(
# dict(
# http=asgi_app,
# websocket=AuthMiddlewareStack(
# URLRouter(
# [
# re_path(
# r"^ws/circuits/(?P<pk>\d+)/$", UpdateDeviceStatus.as_asgi()
# ),
# re_path(pipe_ws_endpoint_name(), MessageConsumer.as_asgi()),
# ]
# ),
# ),
# )
# )
from django_plotly_dash.routing import application # noqa
Not to mention, beyond this error, any custom WebSockets that I have set up will not work since they aren't defined in the websocket portion of the router.
I have no issue contributing to the documentation or code, but I also want to only document intended behavior. The server I have is likely more complex than most other users, hence my being a problem-child lol, so if what I am encountering are issues revolving around clashes between the complexity of my server and dpd, then let's figure those out first.
Fixed by manually implementing the route from
django_plotly_dash.routing.If you have a
routing.pyorasgi.py(I haveasgi.py), you may need to implement something like the following:from django_plotly_dash.consumers import MessageConsumer from django_plotly_dash.util import pipe_ws_endpoint_name asgi_app = get_asgi_application() application = ProtocolTypeRouter( dict( http=asgi_app, websocket=AuthMiddlewareStack( URLRouter( [ re_path(r"^ws/my_webpage/$", MyConsumer.as_asgi()), re_path(pipe_ws_endpoint_name(), MessageConsumer.as_asgi()), ] ), ), ) )@GibbsConsulting I don't see any documentation about this requirement if this behavior is intended.
My secondary error with
{% plotly_message_pipe %}is also resolved with the above routing modification.
Thank you so much for this, I'm up and running now. There's definitely a need to update documentation to get channels working between this, and the websockets javascript.
There is now a new version of routing.py in v2.2.0 as part of the move to supporting Django 4.0 However, for sure the approach that is essentially imposed by channels is not the easiest to extend. We should set up an easier way for a user to have their own routing file and just include our subset of paths.