Repository: dask/distributed

Issue:
Display informative message if attempting to use the dashboard and bokeh isn't installed
From https://github.com/dask/distributed/issues/3741#issuecomment-635396442:

> Perhaps we should include a vanilla route under `/status` (the common
entry point for Bokeh users) that says "please install bokeh" with some
conda/pip install lines if Bokeh isn't installed.  The relevant code is
probably in distributed/http/scheduler/info.py .  I suspect that someone
even very mildly familiar with web programming could accomplish this by
copying examples that are already there.


Discussion:
I would like to work on this, but I'm unsure of how to implement the suggested approach. Perhaps I'm missing something obvious, but if we add a route under `/status` to the Tornado server in `distributed/http/scheduler/info.py`, won't that conflict with the `/status` route on the Bokeh app? e.g, if Bokeh is installed, and the user navigates to `/status`, won't they be served by the vanilla route rather than the current status endpoint?
Good thought.  So we would probably only want to install that route if
bokeh wasn't installed.  We might accomplish this with a try-except
ImportError block.

On Sun, May 31, 2020 at 1:02 PM joshreback <notifications@github.com> wrote:

> I would like to work on this, but I'm unsure of how to implement the
> suggested approach. Perhaps I'm missing something obvious, but if we add a
> route under /status to the Tornado server in
> distributed/http/scheduler/info.py, won't that conflict with the /status
> route on the Bokeh app? e.g, if Bokeh is installed, and the user navigates
> to /status, won't they be served by the vanilla route rather than the
> current status endpoint?
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/distributed/issues/3836#issuecomment-636521397>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACKZTEHJOWQZKJTAYLSAGLRUKZW3ANCNFSM4NNFNH4Q>
> .
>


Resolving patch:
diff --git a/distributed/http/scheduler/missing_bokeh.py b/distributed/http/scheduler/missing_bokeh.py
new file mode 100644
--- /dev/null
+++ b/distributed/http/scheduler/missing_bokeh.py
@@ -0,0 +1,15 @@
+from ..utils import RequestHandler, redirect
+from ...utils import log_errors
+
+
+class MissingBokeh(RequestHandler):
+    def get(self):
+        with log_errors():
+            self.write(
+                "<p>Dask needs bokeh >= 0.13.0 for the dashboard.</p>"
+                "<p>Install with conda: conda install bokeh>=0.13.0</p>"
+                "<p>Install with pip: pip install bokeh>=0.13.0</p>"
+            )
+
+
+routes = [(r"/", redirect("status"), {}), (r"status", MissingBokeh, {})]
diff --git a/distributed/scheduler.py b/distributed/scheduler.py
--- a/distributed/scheduler.py
+++ b/distributed/scheduler.py
@@ -1124,22 +1124,24 @@ def __init__(
             default_port=self.default_port,
         )
 
-        routes = get_handlers(
-            server=self,
-            modules=dask.config.get("distributed.scheduler.http.routes"),
-            prefix=http_prefix,
-        )
-        self.start_http_server(routes, dashboard_address, default_port=8787)
-
-        if dashboard or (dashboard is None and dashboard_address):
+        http_server_modules = dask.config.get("distributed.scheduler.http.routes")
+        show_dashboard = dashboard or (dashboard is None and dashboard_address)
+        missing_bokeh = False
+        # install vanilla route if show_dashboard but bokeh is not installed
+        if show_dashboard:
             try:
                 import distributed.dashboard.scheduler
             except ImportError:
-                logger.debug("To start diagnostics web server please install Bokeh")
-            else:
-                distributed.dashboard.scheduler.connect(
-                    self.http_application, self.http_server, self, prefix=http_prefix
-                )
+                missing_bokeh = True
+                http_server_modules.append("distributed.http.scheduler.missing_bokeh")
+        routes = get_handlers(
+            server=self, modules=http_server_modules, prefix=http_prefix,
+        )
+        self.start_http_server(routes, dashboard_address, default_port=8787)
+        if show_dashboard and not missing_bokeh:
+            distributed.dashboard.scheduler.connect(
+                self.http_application, self.http_server, self, prefix=http_prefix
+            )
 
         # Communication state
         self.loop = loop or IOLoop.current()


Test patch: