From dc397062ad7d4f49c89a3332c6f9637a58d5ac0d Mon Sep 17 00:00:00 2001
From: Ivan Usov <ivan.usov@psi.ch>
Date: Thu, 2 Feb 2023 14:22:34 +0100
Subject: [PATCH] Switch to bokeh wrapper for app serving

---
 pyzebra/app/app_hooks.py        | 17 ++++++++
 pyzebra/app/cli.py              | 74 ++-------------------------------
 pyzebra/app/handler.py          | 31 --------------
 pyzebra/app/{app.py => main.py} | 39 ++++++++++++-----
 scripts/pyzebra-test.sh         |  2 +-
 scripts/pyzebra.sh              |  2 +-
 6 files changed, 51 insertions(+), 114 deletions(-)
 create mode 100644 pyzebra/app/app_hooks.py
 delete mode 100644 pyzebra/app/handler.py
 rename pyzebra/app/{app.py => main.py} (68%)

diff --git a/pyzebra/app/app_hooks.py b/pyzebra/app/app_hooks.py
new file mode 100644
index 0000000..5b44192
--- /dev/null
+++ b/pyzebra/app/app_hooks.py
@@ -0,0 +1,17 @@
+import logging
+import sys
+from io import StringIO
+
+
+def on_server_loaded(_server_context):
+    formatter = logging.Formatter(
+        fmt="%(asctime)s %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
+    )
+
+    sys.stdout = StringIO()
+
+    bokeh_handler = logging.StreamHandler(StringIO())
+    bokeh_handler.setFormatter(formatter)
+    bokeh_logger = logging.getLogger("bokeh")
+    bokeh_logger.setLevel(logging.WARNING)
+    bokeh_logger.addHandler(bokeh_handler)
diff --git a/pyzebra/app/cli.py b/pyzebra/app/cli.py
index d411ebd..6a1d56c 100644
--- a/pyzebra/app/cli.py
+++ b/pyzebra/app/cli.py
@@ -1,77 +1,11 @@
-import argparse
-import logging
 import os
-
-from bokeh.application.application import Application
-from bokeh.application.handlers import ScriptHandler
-from bokeh.server.server import Server
-
-from pyzebra import ANATRIC_PATH, SXTAL_REFGEN_PATH
-from pyzebra.app.handler import PyzebraHandler
-
-logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
-logger = logging.getLogger(__name__)
+import subprocess
+import sys
 
 
 def main():
-    """The pyzebra command line interface.
-
-    This is a wrapper around a bokeh server that provides an interface to launch the application,
-    bundled with the pyzebra package.
-    """
-    app_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app.py")
-
-    parser = argparse.ArgumentParser(
-        prog="pyzebra", formatter_class=argparse.ArgumentDefaultsHelpFormatter
-    )
-
-    parser.add_argument(
-        "--port", type=int, default=5006, help="port to listen on for HTTP requests"
-    )
-
-    parser.add_argument(
-        "--allow-websocket-origin",
-        metavar="HOST[:PORT]",
-        type=str,
-        action="append",
-        default=None,
-        help="hostname that can connect to the server websocket",
-    )
-
-    parser.add_argument(
-        "--anatric-path", type=str, default=ANATRIC_PATH, help="path to anatric executable"
-    )
-
-    parser.add_argument(
-        "--sxtal-refgen-path",
-        type=str,
-        default=SXTAL_REFGEN_PATH,
-        help="path to Sxtal_Refgen executable",
-    )
-
-    parser.add_argument("--spind-path", type=str, default=None, help="path to spind scripts folder")
-
-    parser.add_argument(
-        "--args",
-        nargs=argparse.REMAINDER,
-        default=[],
-        help="command line arguments for the pyzebra application",
-    )
-
-    args = parser.parse_args()
-
-    logger.info(app_path)
-
-    pyzebra_handler = PyzebraHandler(args.anatric_path, args.spind_path)
-    handler = ScriptHandler(filename=app_path, argv=args.args)
-    server = Server(
-        {"/": Application(pyzebra_handler, handler)},
-        port=args.port,
-        allow_websocket_origin=args.allow_websocket_origin,
-    )
-
-    server.start()
-    server.io_loop.start()
+    app_path = os.path.join(os.path.dirname(os.path.abspath(__file__)))
+    subprocess.run(["bokeh", "serve", app_path, *sys.argv[1:]], check=True)
 
 
 if __name__ == "__main__":
diff --git a/pyzebra/app/handler.py b/pyzebra/app/handler.py
deleted file mode 100644
index aa2ec05..0000000
--- a/pyzebra/app/handler.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from bokeh.application.handlers import Handler
-
-
-class PyzebraHandler(Handler):
-    """Provides a mechanism for generic bokeh applications to build up new streamvis documents."""
-
-    def __init__(self, anatric_path, spind_path):
-        """Initialize a pyzebra handler for bokeh applications.
-
-        Args:
-            args (Namespace): Command line parsed arguments.
-        """
-        super().__init__()  # no-op
-
-        self.anatric_path = anatric_path
-        self.spind_path = spind_path
-
-    def modify_document(self, doc):
-        """Modify an application document with pyzebra specific features.
-
-        Args:
-            doc (Document) : A bokeh Document to update in-place
-
-        Returns:
-            Document
-        """
-        doc.title = "pyzebra"
-        doc.anatric_path = self.anatric_path
-        doc.spind_path = self.spind_path
-
-        return doc
diff --git a/pyzebra/app/app.py b/pyzebra/app/main.py
similarity index 68%
rename from pyzebra/app/app.py
rename to pyzebra/app/main.py
index f143db6..b9afa73 100644
--- a/pyzebra/app/app.py
+++ b/pyzebra/app/main.py
@@ -1,6 +1,6 @@
+import argparse
 import logging
 import sys
-from io import StringIO
 
 from bokeh.io import curdoc
 from bokeh.layouts import column, row
@@ -20,16 +20,33 @@ from pyzebra.app import (
 )
 
 doc = curdoc()
+doc.title = "pyzebra"
 
-sys.stdout = StringIO()
-stdout_textareainput = TextAreaInput(title="print output:")
+parser = argparse.ArgumentParser()
 
-bokeh_stream = StringIO()
-bokeh_handler = logging.StreamHandler(bokeh_stream)
-bokeh_handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
-bokeh_logger = logging.getLogger("bokeh")
-bokeh_logger.setLevel(logging.WARNING)
-bokeh_logger.addHandler(bokeh_handler)
+parser.add_argument(
+    "--anatric-path", type=str, default=pyzebra.ANATRIC_PATH, help="path to anatric executable"
+)
+
+parser.add_argument(
+    "--sxtal-refgen-path",
+    type=str,
+    default=pyzebra.SXTAL_REFGEN_PATH,
+    help="path to Sxtal_Refgen executable",
+)
+
+parser.add_argument("--spind-path", type=str, default=None, help="path to spind scripts folder")
+
+args = parser.parse_args()
+
+doc.anatric_path = args.anatric_path
+doc.spind_path = args.spind_path
+doc.sxtal_refgen_path = args.sxtal_refgen_path
+
+# In app_hooks.py a StreamHandler was added to "bokeh" logger
+bokeh_stream = logging.getLogger("bokeh").handlers[0].stream
+
+log_textareainput = TextAreaInput(title="logging output:")
 bokeh_log_textareainput = TextAreaInput(title="server output:")
 
 
@@ -77,13 +94,13 @@ doc.add_root(
                 panel_spind.create(),
             ]
         ),
-        row(stdout_textareainput, bokeh_log_textareainput, sizing_mode="scale_both"),
+        row(log_textareainput, bokeh_log_textareainput, sizing_mode="scale_both"),
     )
 )
 
 
 def update_stdout():
-    stdout_textareainput.value = sys.stdout.getvalue()
+    log_textareainput.value = sys.stdout.getvalue()
     bokeh_log_textareainput.value = bokeh_stream.getvalue()
 
 
diff --git a/scripts/pyzebra-test.sh b/scripts/pyzebra-test.sh
index 30ade45..dea0aae 100644
--- a/scripts/pyzebra-test.sh
+++ b/scripts/pyzebra-test.sh
@@ -1,4 +1,4 @@
 source /opt/miniconda3/etc/profile.d/conda.sh
 
 conda activate test
-python /opt/pyzebra/pyzebra/app/cli.py --port=5010 --allow-websocket-origin=pyzebra.psi.ch:5010 --spind-path=/opt/spind
+python /opt/pyzebra/pyzebra/app/cli.py --port=5010 --allow-websocket-origin=pyzebra.psi.ch:5010 --args --spind-path=/opt/spind
diff --git a/scripts/pyzebra.sh b/scripts/pyzebra.sh
index d98acaa..d0a3bcc 100644
--- a/scripts/pyzebra.sh
+++ b/scripts/pyzebra.sh
@@ -1,4 +1,4 @@
 source /opt/miniconda3/etc/profile.d/conda.sh
 
 conda activate prod
-pyzebra --port=80 --allow-websocket-origin=pyzebra.psi.ch:80 --spind-path=/opt/spind
+pyzebra --port=80 --allow-websocket-origin=pyzebra.psi.ch:80 --args --spind-path=/opt/spind
-- 
GitLab