Coverage for src/metador_core/widget/jupyter/standalone.py: 0%
49 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-02 09:33 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-02 09:33 +0000
1"""Ad-hoc standalone dashboard/widget server for use within Jupyter notebooks.
3It runs everything needed to see a dashboard or widget in threads.
5This is mostly intended for convenient local use (e.g. by a researcher),
6or could be adapted for a containerized (in the Docker-sense) environment, e.g.
7where the user has metador libraries available and can inspect containers.
9**Do not use this to deploy a widget server backing the widgets on a website.**
10"""
11import logging
12import socket
13from threading import Thread
14from typing import List, Optional
16import panel as pn
17from flask import Flask
19from ...container.provider import SimpleContainerProvider
20from ..server import WidgetServer
23def get_free_port():
24 # get a free port and use it (no way to retrieve it, when letting flask choose)
25 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
26 sock.bind((host, 0))
27 port = sock.getsockname()[1]
28 sock.close()
29 return port
32def silence_flask():
33 """Disable HTTP request log (for use inside jupyter)."""
34 log = logging.getLogger("werkzeug")
35 log.setLevel(logging.ERROR)
38# ----
39# Use this Python module as singleton instance for server.
40# As this is only used ad-hoc e.g. by a researcher playing in a notebook,
41# this should be not a problem ("serious" servers are implemented elsewhere!).
43DEFAULT_PANEL_EXTS = ["ace", "tabulator", "mathjax"]
45host: str = "127.0.0.1"
46port: int = -1
48_known_containers: SimpleContainerProvider[str] = SimpleContainerProvider[str]()
49_widget_server: WidgetServer = WidgetServer(_known_containers)
52def widget_server() -> WidgetServer:
53 return _widget_server
56def container_provider() -> SimpleContainerProvider[str]:
57 return _known_containers
60def running() -> bool:
61 return port > 0
64def run(*, debug: bool = False, pn_exts: Optional[List[str]] = None):
65 """Run ad-hoc standalone server to use widgets and dashboards in a Jupyter notebook."""
66 global _widget_server, _known_containers, port
68 if not debug:
69 silence_flask()
70 pn.extension(
71 *(pn_exts or DEFAULT_PANEL_EXTS), inline=True
72 ) # required for panel within jupyter
74 port = get_free_port()
75 flask_base = f"http://{host}:{port}"
77 # prepare bokeh server
78 bokeh_port = get_free_port()
79 bokeh_base = f"http://{host}:{bokeh_port}"
81 def run_bokeh():
82 _widget_server.run(
83 host=host, port=bokeh_port, allowed_websocket_origin=[f"{host}:{port}"]
84 )
86 # prepare flask server
87 flask_app = Flask(__name__)
88 _widget_server.bokeh_endpoint = bokeh_base
89 _widget_server.flask_endpoint = flask_base
90 flask_bokeh = _widget_server.get_flask_blueprint("widget-api", __name__)
91 flask_app.register_blueprint(flask_bokeh)
93 def run_flask():
94 flask_app.run(host=host, port=port)
96 # launch
97 t_flask = Thread(target=run_flask, daemon=True)
98 t_bokeh = Thread(target=run_bokeh, daemon=True)
99 t_flask.start()
100 t_bokeh.start()