From b6d6d0c042c33758c00c0c9124c5bc1b8f393e79 Mon Sep 17 00:00:00 2001 From: Duc Trung Le Date: Wed, 2 Oct 2024 12:32:14 +0200 Subject: [PATCH] Simplify template --- packages/voila/src/plugins/widget/plugins.ts | 12 +- packages/voila/src/plugins/widget/tools.ts | 15 +++ .../templates/base/voila_setup.macro.html.j2 | 6 +- .../jupyter/voila/templates/lab/index.html.j2 | 7 -- voila/app.py | 20 ++-- voila/handler.py | 14 +-- voila/notebook_renderer.py | 113 ++++++++++-------- .../execution_request_handler.py | 0 voila/utils.py | 3 +- 9 files changed, 114 insertions(+), 76 deletions(-) rename voila/{ => tornado}/execution_request_handler.py (100%) diff --git a/packages/voila/src/plugins/widget/plugins.ts b/packages/voila/src/plugins/widget/plugins.ts index df1e7b7eb..8236e75c1 100644 --- a/packages/voila/src/plugins/widget/plugins.ts +++ b/packages/voila/src/plugins/widget/plugins.ts @@ -25,6 +25,7 @@ import { VoilaApp } from '../../app'; import { VoilaWidgetManager } from './manager'; import { RenderedCells } from './renderedcells'; import { + createSkeleton, getExecutionURL, handleExecutionResult, IExecutionMessage, @@ -173,7 +174,16 @@ export const renderOutputsProgressivelyPlugin: JupyterFrontEndPlugin = { app: JupyterFrontEnd, rendermime: IRenderMimeRegistry ): Promise => { - const widgetManager = (app as VoilaApp).widgetManager; + const progressiveRendering = + PageConfig.getOption('progressiveRendering') === 'true'; + if (!progressiveRendering) { + return; + } + + createSkeleton(); + + const widgetManager = await (app as VoilaApp).widgetManagerPromise.promise; + if (!widgetManager) { return; } diff --git a/packages/voila/src/plugins/widget/tools.ts b/packages/voila/src/plugins/widget/tools.ts index 2af92faae..aecc98727 100644 --- a/packages/voila/src/plugins/widget/tools.ts +++ b/packages/voila/src/plugins/widget/tools.ts @@ -141,3 +141,18 @@ export function createOutputArea({ Widget.attach(area, wrapper); return model; } + +export function createSkeleton(): void { + const innerHtml = `
+
+
+
+
`; + const elements = document.querySelectorAll('[cell-index]'); + elements.forEach((it) => { + const element = document.createElement('div'); + element.className = 'voila-skeleton-container'; + element.innerHTML = innerHtml; + it.appendChild(element); + }); +} diff --git a/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 b/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 index 191a4bc08..cc6afbdb1 100644 --- a/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 +++ b/share/jupyter/voila/templates/base/voila_setup.macro.html.j2 @@ -19,7 +19,11 @@ spinner.style.display="flex"; } var el = document.getElementById("loading_text"); - innterText = text ?? `Reading ${cell_index} of ${cell_count}` + let defaultText = `Executing ${cell_index} of ${cell_count}` + if("{{ progressive_rendering | default("False", true) }}" === "True"){ + defaultText = `Reading ${cell_index} of ${cell_count}` + } + innterText = text ?? defaultText if(el){ el.innerHTML = innterText; } diff --git a/share/jupyter/voila/templates/lab/index.html.j2 b/share/jupyter/voila/templates/lab/index.html.j2 index ad513f67d..d000b2615 100644 --- a/share/jupyter/voila/templates/lab/index.html.j2 +++ b/share/jupyter/voila/templates/lab/index.html.j2 @@ -110,13 +110,6 @@ -
-
-
-
-
-
-
{{ super() }} {%- endblock any_cell -%} diff --git a/voila/app.py b/voila/app.py index 5601d450f..ca75c2f0c 100644 --- a/voila/app.py +++ b/voila/app.py @@ -22,7 +22,7 @@ from .tornado.kernelwebsockethandler import VoilaKernelWebsocketHandler -from .execution_request_handler import ExecutionRequestHandler +from .tornado.execution_request_handler import ExecutionRequestHandler from .tornado.contentshandler import VoilaContentsHandler @@ -676,7 +676,6 @@ def init_settings(self) -> Dict: login_url=url_path_join(self.base_url, "/login"), mathjax_config=self.mathjax_config, mathjax_url=self.mathjax_url, - progressive_rendering=progressive_rendering, ) settings[self.name] = self # Why??? @@ -726,7 +725,7 @@ def init_handlers(self) -> List: ] ) handlers.extend(self.identity_provider.get_handlers()) - if self.voila_configuration.preheat_kernel or True: + if self.voila_configuration.preheat_kernel: handlers.append( ( url_path_join( @@ -735,14 +734,15 @@ def init_handlers(self) -> List: RequestInfoSocketHandler, ) ) - handlers.append( - ( - url_path_join( - self.server_url, r"/voila/execution/%s" % _kernel_id_regex - ), - ExecutionRequestHandler, + if self.voila_configuration.progressive_rendering: + handlers.append( + ( + url_path_join( + self.server_url, r"/voila/execution/%s" % _kernel_id_regex + ), + ExecutionRequestHandler, + ) ) - ) # Serving JupyterLab extensions handlers.append( ( diff --git a/voila/handler.py b/voila/handler.py index 2b6fec472..059e23ca8 100644 --- a/voila/handler.py +++ b/voila/handler.py @@ -19,7 +19,7 @@ from tornado.httputil import split_host_and_port from traitlets.traitlets import Bool -from voila.execution_request_handler import ExecutionRequestHandler +from voila.tornado.execution_request_handler import ExecutionRequestHandler from .configuration import VoilaConfiguration @@ -238,12 +238,12 @@ def time_out(): ) kernel_future = self.kernel_manager.get_kernel(kernel_id) queue = asyncio.Queue() - - ExecutionRequestHandler._execution_data[kernel_id] = { - "nb": gen.notebook, - "config": self.traitlet_config, - "show_tracebacks": self.voila_configuration.show_tracebacks, - } + if self.voila_configuration.progressive_rendering: + ExecutionRequestHandler._execution_data[kernel_id] = { + "nb": gen.notebook, + "config": self.traitlet_config, + "show_tracebacks": self.voila_configuration.show_tracebacks, + } async def put_html(): async for html_snippet, _ in gen.generate_content_generator( diff --git a/voila/notebook_renderer.py b/voila/notebook_renderer.py index a2d39d4c8..1bb6fc34f 100644 --- a/voila/notebook_renderer.py +++ b/voila/notebook_renderer.py @@ -9,20 +9,23 @@ import os +import sys +import traceback from functools import partial from copy import deepcopy -from typing import Generator, List, Tuple, Union +from typing import AsyncGenerator, Generator, List, Tuple, Union import nbformat import tornado.web from jupyter_core.utils import ensure_async from jupyter_server.config_manager import recursive_update +from nbclient.exceptions import CellExecutionError from nbconvert.preprocessors.clearoutput import ClearOutputPreprocessor from traitlets.config.configurable import LoggingConfigurable from voila.configuration import VoilaConfiguration -from .execute import VoilaExecutor +from .execute import VoilaExecutor, strip_code_cell_warnings from .exporter import VoilaExporter from .paths import collect_template_paths from .utils import ENV_VARIABLE @@ -153,7 +156,7 @@ def generate_content_generator( self, kernel_id: Union[str, None] = None, kernel_future=None, - ) -> Generator: + ) -> AsyncGenerator: inner_kernel_start = partial( self._jinja_kernel_start, kernel_id=kernel_id, kernel_future=kernel_future ) @@ -168,9 +171,16 @@ def generate_content_generator( "frontend": "voila", "main_js": "voila.js", "kernel_start": inner_kernel_start, - "cell_generator": self._jinja_cell_generator, "notebook_execute": self._jinja_notebook_execute, + "progressive_rendering": self.voila_configuration.progressive_rendering, } + if self.voila_configuration.progressive_rendering: + extra_context["cell_generator"] = ( + self._jinja_cell_generator_without_execution + ) + else: + extra_context["cell_generator"] = self._jinja_cell_generator + # render notebook in snippets, then return an iterator so we can flush # them out to the browser progressively. return self.exporter.generate_from_notebook_node( @@ -248,56 +258,63 @@ async def _jinja_notebook_execute(self, nb, kernel_id): await self._cleanup_resources() + async def _jinja_cell_generator_without_execution(self, nb, kernel_id): + nb, _ = ClearOutputPreprocessor().preprocess( + nb, {"metadata": {"path": self.cwd}} + ) + for input_cell in nb.cells: + output = input_cell.copy() + yield output + await self._cleanup_resources() + async def _jinja_cell_generator(self, nb, kernel_id): """Generator that will execute a single notebook cell at a time""" nb, _ = ClearOutputPreprocessor().preprocess( nb, {"metadata": {"path": self.cwd}} ) for cell_idx, input_cell in enumerate(nb.cells): - output = input_cell.copy() - yield output - # try: - # output_cell = await self.executor.execute_cell( - # input_cell, None, cell_idx, store_history=False - # ) - # except TimeoutError: - # output_cell = input_cell - # break - # except CellExecutionError: - # self.log.exception( - # "Error at server while executing cell: %r", input_cell - # ) - # if self.executor.should_strip_error(): - # strip_code_cell_warnings(input_cell) - # self.executor.strip_code_cell_errors(input_cell) - # output_cell = input_cell - # break - # except Exception as e: - # self.log.exception( - # "Error at server while executing cell: %r", input_cell - # ) - # output_cell = nbformat.v4.new_code_cell() - # if self.executor.should_strip_error(): - # output_cell.outputs = [ - # { - # "output_type": "stream", - # "name": "stderr", - # "text": "An exception occurred at the server (not the notebook). {}".format( - # self.executor.cell_error_instruction - # ), - # } - # ] - # else: - # output_cell.outputs = [ - # { - # "output_type": "error", - # "ename": type(e).__name__, - # "evalue": str(e), - # "traceback": traceback.format_exception(*sys.exc_info()), - # } - # ] - # finally: - # yield output_cell + try: + output_cell = await self.executor.execute_cell( + input_cell, None, cell_idx, store_history=False + ) + except TimeoutError: + output_cell = input_cell + break + except CellExecutionError: + self.log.exception( + "Error at server while executing cell: %r", input_cell + ) + if self.executor.should_strip_error(): + strip_code_cell_warnings(input_cell) + self.executor.strip_code_cell_errors(input_cell) + output_cell = input_cell + break + except Exception as e: + self.log.exception( + "Error at server while executing cell: %r", input_cell + ) + output_cell = nbformat.v4.new_code_cell() + if self.executor.should_strip_error(): + output_cell.outputs = [ + { + "output_type": "stream", + "name": "stderr", + "text": "An exception occurred at the server (not the notebook). {}".format( + self.executor.cell_error_instruction + ), + } + ] + else: + output_cell.outputs = [ + { + "output_type": "error", + "ename": type(e).__name__, + "evalue": str(e), + "traceback": traceback.format_exception(*sys.exc_info()), + } + ] + finally: + yield output_cell await self._cleanup_resources() diff --git a/voila/execution_request_handler.py b/voila/tornado/execution_request_handler.py similarity index 100% rename from voila/execution_request_handler.py rename to voila/tornado/execution_request_handler.py diff --git a/voila/utils.py b/voila/utils.py index b3f0c299c..073ed50a1 100644 --- a/voila/utils.py +++ b/voila/utils.py @@ -90,7 +90,6 @@ async def _get_request_info(ws_url: str) -> Awaitable: def get_page_config(base_url, settings, log, voila_configuration: VoilaConfiguration): - progressive_rendering = settings.get("progressive_rendering", False) page_config = { "appVersion": __version__, "appUrl": "voila/", @@ -100,7 +99,7 @@ def get_page_config(base_url, settings, log, voila_configuration: VoilaConfigura "fullStaticUrl": url_path_join(base_url, "voila/static"), "fullLabextensionsUrl": url_path_join(base_url, "voila/labextensions"), "extensionConfig": voila_configuration.extension_config, - "progressiveRendering": progressive_rendering, + "progressiveRendering": voila_configuration.progressive_rendering, } mathjax_config = settings.get("mathjax_config", "TeX-AMS_CHTML-full,Safe") mathjax_url = settings.get(