Skip to content

Commit

Permalink
Implement FPS-based Voila server
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Sep 30, 2021
1 parent 8a488cf commit 7c59c1f
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 199 deletions.
14 changes: 14 additions & 0 deletions fps_plugins/voila/fps_voila/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from fps.config import PluginModel, get_config # type: ignore
from fps.hooks import register_config, register_plugin_name # type: ignore


class VoilaConfig(PluginModel):
notebook_path: str = ""


def get_voila_config():
return get_config(VoilaConfig)


c = register_config(VoilaConfig)
n = register_plugin_name("Voila")
103 changes: 103 additions & 0 deletions fps_plugins/voila/fps_voila/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import sys
import os
import uuid
from pathlib import Path
from typing import Optional

from voila.handler import _VoilaHandler, _get

from mimetypes import guess_type
from fastapi import APIRouter, Depends
from fastapi.responses import RedirectResponse, HTMLResponse, Response
from fastapi.staticfiles import StaticFiles
from fps.hooks import register_router # type: ignore
from fps_kernels.kernel_server.server import KernelServer, kernels # type: ignore
from kernel_driver import KernelDriver
from kernel_driver.driver import receive_message

from .config import get_voila_config


class FPSVoilaHandler(_VoilaHandler):
is_fps = True
_arguments = {}
html = []

def redirect(self, url):
return RedirectResponse(url)

def write(self, html):
self.html += [html]

def flush(self):
pass

def return_html(self):
return HTMLResponse("".join(self.html))

def get_argument(self, name, default):
if self._arguments[name] is None:
return default
return self._arguments[name]


def init_voila_handler(
notebook_path,
template_paths,
config,
voila_configuration,
contents_manager,
base_url,
kernel_manager,
kernel_spec_manager,
allow_remote_access,
autoreload,
voila_jinja2_env,
jinja2_env,
static_path,
server_root_dir,
config_manager,
static_paths,
):
global fps_voila_handler
fps_voila_handler = FPSVoilaHandler()
fps_voila_handler.initialize(
notebook_path=notebook_path,
template_paths=template_paths,
traitlet_config=config,
voila_configuration=voila_configuration,
)
fps_voila_handler.contents_manager = contents_manager
fps_voila_handler.base_url = base_url
fps_voila_handler.kernel_manager = kernel_manager
fps_voila_handler.kernel_spec_manager = kernel_spec_manager
fps_voila_handler.allow_remote_access = allow_remote_access
fps_voila_handler.autoreload = autoreload
fps_voila_handler.voila_jinja2_env = voila_jinja2_env
fps_voila_handler.jinja2_env = jinja2_env
fps_voila_handler.static_path = static_path
fps_voila_handler.server_root_dir = server_root_dir
fps_voila_handler.config_manager = config_manager
fps_voila_handler.static_paths = static_paths


router = APIRouter()

@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._arguments["voila-template"] = voila_template
fps_voila_handler._arguments["voila-theme"] = voila_theme
path = "" #voila_config.notebook_path or "/"
return await _get(fps_voila_handler, path)

@router.get("/voila/templates/lab/static/{path:path}")
async def get_file(path):
for i, static_path in enumerate(fps_voila_handler.static_paths):
file_path = Path(static_path) / path
if os.path.exists(file_path):
with open(file_path) as f:
content = f.read()
content_type, _ = guess_type(file_path)
return Response(content, media_type=content_type)

r = register_router(router)
12 changes: 12 additions & 0 deletions fps_plugins/voila/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from setuptools import setup, find_packages # type: ignore

setup(
name="fps_voila",
version="0.0.1",
packages=find_packages(),
install_requires=["fps", "fps-kernels", "aiofiles"],
entry_points={
"fps_router": ["fps-voila = fps_voila.routes"],
"fps_config": ["fps-voila = fps_voila.config"],
},
)
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ test =
pytest
pytest-tornasync

fps =
fps[uvicorn]

[options.entry_points]
console_scripts =
voila = voila.app:main
voila = voila.app:main
210 changes: 125 additions & 85 deletions voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
from jupyter_server.utils import url_path_join, run_sync
from jupyter_server.services.config import ConfigManager

from fps_uvicorn.cli import app as fps_app
from fps_voila.routes import init_voila_handler

from jupyter_client.kernelspec import KernelSpecManager

from jupyter_core.paths import jupyter_config_path, jupyter_path
Expand Down Expand Up @@ -81,7 +84,8 @@ class Voila(Application):
},
_("Set the log level to logging.DEBUG, and show exception tracebacks in output.")
),
'no-browser': ({'Voila': {'open_browser': False}}, _('Don\'t open the notebook in a browser after startup.'))
'no-browser': ({'Voila': {'open_browser': False}}, _('Don\'t open the notebook in a browser after startup.')),
'fps': ({'Voila': {'fps': True}}, _('Use FPS instead of Jupyter Server.')),
}

description = Unicode(
Expand Down Expand Up @@ -198,6 +202,11 @@ class Voila(Application):
ip = Unicode('localhost', config=True,
help=_("The IP address the notebook server will listen on."))

fps = Bool(False, config=True,
help=_("""Whether to user FPS for the server,
instead of Jupyter Server.
"""))

open_browser = Bool(True, config=True,
help=_("""Whether to open in a browser after starting.
The specific browser used is platform dependent and
Expand Down Expand Up @@ -437,98 +446,129 @@ def start(self):
# default server_url to base_url
self.server_url = self.server_url or self.base_url

self.app = tornado.web.Application(
base_url=self.base_url,
server_url=self.server_url or self.base_url,
kernel_manager=self.kernel_manager,
kernel_spec_manager=self.kernel_spec_manager,
allow_remote_access=True,
autoreload=self.autoreload,
voila_jinja2_env=env,
jinja2_env=env,
static_path='/',
server_root_dir='/',
contents_manager=self.contents_manager,
config_manager=self.config_manager
)
if self.fps:
# pass options to FPS app
options = sys.argv[1:]
sys.argv = sys.argv[:1]
fps_options = [f"--fps.root_path={self.server_url}", f"--port={self.port}"]
for path in options:
if not path.startswith("--"):
break
else:
path = "/"
sys.argv += fps_options + [f"--Voila.notebook_path={path}", "--authenticator.mode=noauth"]
init_voila_handler(
self.notebook_path,
self.template_paths,
self.config,
self.voila_configuration,
self.contents_manager,
self.base_url,
self.kernel_manager,#MultiKernelManager(),
self.kernel_spec_manager,
True,
self.autoreload,
env,
env,
'/',
'/',
self.config_manager,
self.static_paths,
)
fps_app()
else:
self.app = tornado.web.Application(
base_url=self.base_url,
server_url=self.server_url or self.base_url,
kernel_manager=self.kernel_manager,
kernel_spec_manager=self.kernel_spec_manager,
allow_remote_access=True,
autoreload=self.autoreload,
voila_jinja2_env=env,
jinja2_env=env,
static_path='/',
server_root_dir='/',
contents_manager=self.contents_manager,
config_manager=self.config_manager
)

self.app.settings.update(self.tornado_settings)

self.app.settings.update(self.tornado_settings)

handlers = []

handlers.extend([
(url_path_join(self.server_url, r'/api/kernels/%s' % _kernel_id_regex), KernelHandler),
(url_path_join(self.server_url, r'/api/kernels/%s/channels' % _kernel_id_regex), ZMQChannelsHandler),
(
url_path_join(self.server_url, r'/voila/templates/(.*)'),
TemplateStaticFileHandler
),
(
url_path_join(self.server_url, r'/voila/static/(.*)'),
MultiStaticFileHandler,
{
'paths': self.static_paths,
'default_filename': 'index.html'
},
),
(url_path_join(self.server_url, r'/voila/api/shutdown/(.*)'), VoilaShutdownKernelHandler)
])

# Serving notebook extensions
if self.voila_configuration.enable_nbextensions:
handlers = []

handlers.extend([
(url_path_join(self.server_url, r'/api/kernels/%s' % _kernel_id_regex), KernelHandler),
(url_path_join(self.server_url, r'/api/kernels/%s/channels' % _kernel_id_regex), ZMQChannelsHandler),
(
url_path_join(self.server_url, r'/voila/templates/(.*)'),
TemplateStaticFileHandler
),
(
url_path_join(self.server_url, r'/voila/static/(.*)'),
MultiStaticFileHandler,
{
'paths': self.static_paths,
'default_filename': 'index.html'
},
),
(url_path_join(self.server_url, r'/voila/api/shutdown/(.*)'), VoilaShutdownKernelHandler)
])

# Serving notebook extensions
if self.voila_configuration.enable_nbextensions:
handlers.append(
(
url_path_join(self.server_url, r'/voila/nbextensions/(.*)'),
FileFindHandler,
{
'path': self.nbextensions_path,
'no_cache_paths': ['/'], # don't cache anything in nbextensions
},
)
)
handlers.append(
(
url_path_join(self.server_url, r'/voila/nbextensions/(.*)'),
FileFindHandler,
url_path_join(self.server_url, r'/voila/files/(.*)'),
WhiteListFileHandler,
{
'path': self.nbextensions_path,
'no_cache_paths': ['/'], # don't cache anything in nbextensions
'whitelist': self.voila_configuration.file_whitelist,
'blacklist': self.voila_configuration.file_blacklist,
'path': self.root_dir,
},
)
)
handlers.append(
(
url_path_join(self.server_url, r'/voila/files/(.*)'),
WhiteListFileHandler,
{
'whitelist': self.voila_configuration.file_whitelist,
'blacklist': self.voila_configuration.file_blacklist,
'path': self.root_dir,
},
)
)

tree_handler_conf = {
'voila_configuration': self.voila_configuration
}
if self.notebook_path:
handlers.append((
url_path_join(self.server_url, r'/(.*)'),
VoilaHandler,
{
'notebook_path': os.path.relpath(self.notebook_path, self.root_dir),
'template_paths': self.template_paths,
'config': self.config,
'voila_configuration': self.voila_configuration
}
))
else:
self.log.debug('serving directory: %r', self.root_dir)
handlers.extend([
(self.server_url, VoilaTreeHandler, tree_handler_conf),
(url_path_join(self.server_url, r'/voila/tree' + path_regex),
VoilaTreeHandler, tree_handler_conf),
(url_path_join(self.server_url, r'/voila/render/(.*)'),
VoilaHandler,
{
'template_paths': self.template_paths,
'config': self.config,
'voila_configuration': self.voila_configuration
}),
])

self.app.add_handlers('.*$', handlers)
self.listen()
tree_handler_conf = {
'voila_configuration': self.voila_configuration
}
if self.notebook_path:
handlers.append((
url_path_join(self.server_url, r'/(.*)'),
VoilaHandler,
{
'notebook_path': os.path.relpath(self.notebook_path, self.root_dir),
'template_paths': self.template_paths,
'config': self.config,
'voila_configuration': self.voila_configuration
}
))
else:
self.log.debug('serving directory: %r', self.root_dir)
handlers.extend([
(self.server_url, VoilaTreeHandler, tree_handler_conf),
(url_path_join(self.server_url, r'/voila/tree' + path_regex),
VoilaTreeHandler, tree_handler_conf),
(url_path_join(self.server_url, r'/voila/render/(.*)'),
VoilaHandler,
{
'template_paths': self.template_paths,
'config': self.config,
'voila_configuration': self.voila_configuration
}),
])

self.app.add_handlers('.*$', handlers)
self.listen()

def stop(self):
shutil.rmtree(self.connection_dir)
Expand Down
Loading

0 comments on commit 7c59c1f

Please sign in to comment.