From b34f18ba98268d980a96570c6a545b3ee8186e69 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 26 Oct 2021 16:45:02 +0200 Subject: [PATCH] Support tree handler --- fps_plugins/voila/fps_voila/routes.py | 55 +++++++++++++++-- voila/app.py | 2 + voila/treehandler.py | 85 +++++++++++++++------------ 3 files changed, 100 insertions(+), 42 deletions(-) diff --git a/fps_plugins/voila/fps_voila/routes.py b/fps_plugins/voila/fps_voila/routes.py index 3a253aa99..b7c540b1f 100644 --- a/fps_plugins/voila/fps_voila/routes.py +++ b/fps_plugins/voila/fps_voila/routes.py @@ -5,16 +5,32 @@ from typing import Optional from voila.handler import _VoilaHandler, _get +from voila.treehandler import _VoilaTreeHandler, _get as _get_tree from mimetypes import guess_type from fastapi import APIRouter, Depends -from fastapi.responses import RedirectResponse, StreamingResponse, Response +from fastapi.responses import RedirectResponse, StreamingResponse, HTMLResponse, Response from fastapi.staticfiles import StaticFiles from fps.hooks import register_router # type: ignore from .config import get_voila_config +class FPSVoilaTreeHandler(_VoilaTreeHandler): + is_fps = True + + def redirect(self, url): + return RedirectResponse(url) + + def write(self, html): + return HTMLResponse(html) + + def render_template(self, name, **kwargs): + kwargs["base_url"] = self.base_url + template = self.jinja2_env.get_template(name) + return template.render(**kwargs) + + class FPSVoilaHandler(_VoilaHandler): is_fps = True fps_arguments = {} @@ -46,8 +62,11 @@ def init_voila_handler( server_root_dir, config_manager, static_paths, + settings, + log, ): - global fps_voila_handler + global fps_voila_handler, fps_voila_tree_handler + fps_voila_handler = FPSVoilaHandler() fps_voila_handler.initialize( notebook_path=notebook_path, @@ -68,15 +87,43 @@ def init_voila_handler( fps_voila_handler.config_manager = config_manager fps_voila_handler.static_paths = static_paths + fps_voila_tree_handler = FPSVoilaTreeHandler() + fps_voila_tree_handler.initialize( + voila_configuration=voila_configuration, + ) + fps_voila_tree_handler.contents_manager = contents_manager + fps_voila_tree_handler.base_url = base_url + fps_voila_tree_handler.voila_jinja2_env = voila_jinja2_env + fps_voila_tree_handler.jinja2_env = jinja2_env + fps_voila_tree_handler.settings = settings + fps_voila_tree_handler.log = log + settings["contents_manager"] = contents_manager + router = APIRouter() +@router.get("/notebooks/{path:path}") +async def get_root(path, voila_template: Optional[str] = None, voila_theme: Optional[str] = None, voila_config=Depends(get_voila_config)): + return StreamingResponse(_get(fps_voila_handler, path)) + + @router.get("/") async def get_root(voila_template: Optional[str] = None, voila_theme: Optional[str] = None, voila_config=Depends(get_voila_config)): fps_voila_handler.fps_arguments["voila-template"] = voila_template fps_voila_handler.fps_arguments["voila-theme"] = voila_theme - path = "" #voila_config.notebook_path or "/" - return StreamingResponse(_get(fps_voila_handler, path)) + path = voila_config.notebook_path or "/" + if path == "/": + return _get_tree(fps_voila_tree_handler, "/") + else: + return StreamingResponse(_get(fps_voila_handler, "")) + +@router.get("/voila/render/{name}") +async def get_path(name): + return _get_tree(fps_voila_tree_handler, name) + +@router.get("/voila/tree{path:path}") +async def get_tree(path): + return _get_tree(fps_voila_tree_handler, path) @router.get("/voila/static/{path}") def get_file1(path): diff --git a/voila/app.py b/voila/app.py index da87bf257..2a2cbe8a9 100644 --- a/voila/app.py +++ b/voila/app.py @@ -484,6 +484,8 @@ def start(self): '/', self.config_manager, self.static_paths, + self.tornado_settings, + self.log, ) fps_app() else: diff --git a/voila/treehandler.py b/voila/treehandler.py index 53013c83d..b614a8018 100644 --- a/voila/treehandler.py +++ b/voila/treehandler.py @@ -16,7 +16,50 @@ from .utils import get_server_root_dir -class VoilaTreeHandler(JupyterHandler): +def _get(self, path=''): + cm = self.contents_manager + + if cm.dir_exists(path=path): + if cm.is_hidden(path) and not cm.allow_hidden: + self.log.info("Refusing to serve hidden directory, via 404 Error") + raise web.HTTPError(404) + breadcrumbs = self.generate_breadcrumbs(path) + page_title = self.generate_page_title(path) + contents = cm.get(path) + + def allowed_content(content): + if content['type'] in ['directory', 'notebook']: + return True + __, ext = os.path.splitext(content.get('path')) + return ext in self.allowed_extensions + + contents['content'] = sorted(contents['content'], key=lambda i: i['name']) + contents['content'] = filter(allowed_content, contents['content']) + return self.write(self.render_template('tree.html', + page_title=page_title, + notebook_path=path, + breadcrumbs=breadcrumbs, + contents=contents, + terminals_available=False, + server_root=get_server_root_dir(self.settings))) + elif cm.file_exists(path): + # it's not a directory, we have redirecting to do + model = cm.get(path, content=False) + # redirect to /api/notebooks if it's a notebook, otherwise /api/files + service = 'notebooks' if model['type'] == 'notebook' else 'files' + url = url_path_join( + self.base_url, service, url_escape(path), + ) + if not self.is_fps: + self.log.debug("Redirecting %s to %s", self.request.path, url) + return self.redirect(url) + else: + raise web.HTTPError(404) + + +class _VoilaTreeHandler: + is_fps = False + def initialize(self, **kwargs): self.voila_configuration = kwargs['voila_configuration'] self.allowed_extensions = list(self.voila_configuration.extension_language_mapping.keys()) + ['.ipynb'] @@ -46,42 +89,8 @@ def generate_page_title(self, path): else: return 'VoilĂ  Home' + +class VoilaTreeHandler(_VoilaTreeHandler, JupyterHandler): @web.authenticated def get(self, path=''): - cm = self.contents_manager - - if cm.dir_exists(path=path): - if cm.is_hidden(path) and not cm.allow_hidden: - self.log.info("Refusing to serve hidden directory, via 404 Error") - raise web.HTTPError(404) - breadcrumbs = self.generate_breadcrumbs(path) - page_title = self.generate_page_title(path) - contents = cm.get(path) - - def allowed_content(content): - if content['type'] in ['directory', 'notebook']: - return True - __, ext = os.path.splitext(content.get('path')) - return ext in self.allowed_extensions - - contents['content'] = sorted(contents['content'], key=lambda i: i['name']) - contents['content'] = filter(allowed_content, contents['content']) - self.write(self.render_template('tree.html', - page_title=page_title, - notebook_path=path, - breadcrumbs=breadcrumbs, - contents=contents, - terminals_available=False, - server_root=get_server_root_dir(self.settings))) - elif cm.file_exists(path): - # it's not a directory, we have redirecting to do - model = cm.get(path, content=False) - # redirect to /api/notebooks if it's a notebook, otherwise /api/files - service = 'notebooks' if model['type'] == 'notebook' else 'files' - url = url_path_join( - self.base_url, service, url_escape(path), - ) - self.log.debug("Redirecting %s to %s", self.request.path, url) - self.redirect(url) - else: - raise web.HTTPError(404) + return _get(self, path=path)