Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live-Updating: ValueError: No route found for path 'dpd/ws/channel' #376

Open
JulianOrteil opened this issue Jan 11, 2022 · 6 comments
Open

Comments

@JulianOrteil
Copy link
Contributor

JulianOrteil commented Jan 11, 2022

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:
image

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

@JulianOrteil
Copy link
Contributor Author

JulianOrteil commented Jan 12, 2022

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,
}

@JulianOrteil
Copy link
Contributor Author

JulianOrteil commented Jan 12, 2022

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.

@GibbsConsulting
Copy link
Owner

GibbsConsulting commented Jan 13, 2022

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.

@JulianOrteil
Copy link
Contributor Author

@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.

@mpkasp-pison
Copy link

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.

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.

@delsim
Copy link
Contributor

delsim commented May 3, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants