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

1"""Ad-hoc standalone dashboard/widget server for use within Jupyter notebooks. 

2 

3It runs everything needed to see a dashboard or widget in threads. 

4 

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. 

8 

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 

15 

16import panel as pn 

17from flask import Flask 

18 

19from ...container.provider import SimpleContainerProvider 

20from ..server import WidgetServer 

21 

22 

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 

30 

31 

32def silence_flask(): 

33 """Disable HTTP request log (for use inside jupyter).""" 

34 log = logging.getLogger("werkzeug") 

35 log.setLevel(logging.ERROR) 

36 

37 

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!). 

42 

43DEFAULT_PANEL_EXTS = ["ace", "tabulator", "mathjax"] 

44 

45host: str = "127.0.0.1" 

46port: int = -1 

47 

48_known_containers: SimpleContainerProvider[str] = SimpleContainerProvider[str]() 

49_widget_server: WidgetServer = WidgetServer(_known_containers) 

50 

51 

52def widget_server() -> WidgetServer: 

53 return _widget_server 

54 

55 

56def container_provider() -> SimpleContainerProvider[str]: 

57 return _known_containers 

58 

59 

60def running() -> bool: 

61 return port > 0 

62 

63 

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 

67 

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 

73 

74 port = get_free_port() 

75 flask_base = f"http://{host}:{port}" 

76 

77 # prepare bokeh server 

78 bokeh_port = get_free_port() 

79 bokeh_base = f"http://{host}:{bokeh_port}" 

80 

81 def run_bokeh(): 

82 _widget_server.run( 

83 host=host, port=bokeh_port, allowed_websocket_origin=[f"{host}:{port}"] 

84 ) 

85 

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) 

92 

93 def run_flask(): 

94 flask_app.run(host=host, port=port) 

95 

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()