Skip to content

Commit

Permalink
Move to flexible lightweight template system (#49)
Browse files Browse the repository at this point in the history
* move to flexible lightweight template system

* Typos
  • Loading branch information
maartenbreddels authored and SylvainCorlay committed Jan 24, 2019
1 parent 4649a36 commit 340cd65
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 23 deletions.
16 changes: 10 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,23 @@ def run(self):
with open(os.path.join(here, 'voila', '_version.py')) as f:
exec(f.read(), {}, version_ns)

data_files = [
('etc/jupyter/jupyter_server_config.d', ['etc/jupyter/jupyter_server_config.d/voila.json']),
('etc/jupyter/jupyter_notebook_config.d', ['etc/jupyter/jupyter_notebook_config.d/voila.json'])
]

# Add all the templates
for (dirpath, dirnames, filenames) in os.walk('share/jupyter/voila/template/'):
if filenames:
data_files.append((dirpath, [os.path.join(dirpath, filename) for filename in filenames]))

setup_args = {
'name': 'voila',
'version': version_ns['__version__'],
'description': 'Serving read-only live Jupyter notebooks',
'packages': find_packages(),
'zip_safe': False,
'data_files': [
('etc/jupyter/jupyter_server_config.d', ['etc/jupyter/jupyter_server_config.d/voila.json']),
('etc/jupyter/jupyter_notebook_config.d', ['etc/jupyter/jupyter_notebook_config.d/voila.json'])
],
'data_files': data_files,
'cmdclass': {
'build_py': js_prerelease(build_py),
'egg_info': js_prerelease(egg_info),
Expand All @@ -149,8 +155,6 @@ def run(self):
},
'package_data': {
'voila': [
'templates/*',
'nbconvert_templates/*',
'static/*'
]
},
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
69 changes: 60 additions & 9 deletions voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@
from jupyter_server.base.handlers import path_regex
from jupyter_server.services.contents.largefilemanager import LargeFileManager
from jupyter_server.utils import url_path_join

from .paths import ROOT, STATIC_ROOT, TEMPLATE_ROOT
from jupyter_core.paths import jupyter_path
from .paths import ROOT, STATIC_ROOT
from .handler import VoilaHandler
from .treehandler import VoilaTreeHandler
from ._version import __version__
from .static_file_handler import MultiStaticFileHandler

_kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"

Expand Down Expand Up @@ -77,7 +78,7 @@ class Voila(Application):
'static': 'Voila.static_root',
'strip_sources': 'Voila.strip_sources',
'autoreload': 'Voila.autoreload',
'custom_template_path': 'Voila.custom_template_path'
'template': 'Voila.template'
}
connection_dir_root = Unicode(
config=True,
Expand All @@ -88,11 +89,12 @@ class Voila(Application):
)
connection_dir = Unicode()

custom_template_path = Unicode(
template = Unicode(
'default',
config=True,
allow_none=True,
help=(
'Custom path for nbconvert templates used by voila.'
'template name to be used by voila.'
)
)

Expand All @@ -110,6 +112,55 @@ def _default_log_level(self):
def parse_command_line(self, argv=None):
super(Voila, self).parse_command_line(argv)
self.notebook_path = self.extra_args[0] if len(self.extra_args) == 1 else None
self.nbconvert_template_paths = []
self.template_paths = []
self.static_paths = [self.static_root]
if self.template:
self._collect_template_paths(self.template)
self.log.debug('using template: %s', self.template)
self.log.debug('nbconvert template paths: %s', self.nbconvert_template_paths)
self.log.debug('template paths: %s', self.template_paths)
self.log.debug('static paths: %s', self.static_paths)

def _collect_template_paths(self, template_name):
# we look at the usual jupyter locations, and for development purposes also
# relative to the package directory (with highest precedence)
template_directories = \
[os.path.abspath(os.path.join(ROOT, '..', 'share', 'jupyter', 'voila', 'template', template_name))] +\
jupyter_path('voila', 'template', template_name)
for dirname in template_directories:
if os.path.exists(dirname):
conf = {}
conf_file = os.path.join(dirname, 'conf.json')
if os.path.exists(conf_file):
with open(conf_file) as f:
conf = json.load(f)
# for templates that are not named default, we assume the default base_template is 'default'
# that means that even the default template could have a base_template when explicitly given
if template_name != 'default' or 'base_template' in conf:
self._collect_template_paths(conf.get('base_template', 'default'))

extra_nbconvert_path = os.path.join(dirname, 'nbconvert_templates')
if not os.path.exists(extra_nbconvert_path):
self.log.warning('template named %s found at path %r, but %s does not exist', self.template,
dirname, extra_nbconvert_path)
self.nbconvert_template_paths.insert(0, extra_nbconvert_path)

extra_static_path = os.path.join(dirname, 'static')
if not os.path.exists(extra_static_path):
self.log.warning('template named %s found at path %r, but %s does not exist', self.template,
dirname, extra_static_path)
self.static_paths.insert(0, extra_static_path)

extra_template_path = os.path.join(dirname, 'templates')
if not os.path.exists(extra_template_path):
self.log.warning('template named %s found at path %r, but %s does not exist', self.template,
dirname, extra_template_path)
self.template_paths.insert(0, extra_template_path)

# we don't look at multiple directories, once a directory with a given name is found at a particular
# location (for instance the user dir) don't look further (for instance sys.prefix)
break

def start(self):
connection_dir = tempfile.mkdtemp(
Expand All @@ -130,7 +181,7 @@ def start(self):
)

jenv_opt = {"autoescape": True} # we might want extra options via cmd line like notebook server
env = jinja2.Environment(loader=jinja2.FileSystemLoader(TEMPLATE_ROOT), extensions=['jinja2.ext.i18n'], **jenv_opt)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(self.template_paths), extensions=['jinja2.ext.i18n'], **jenv_opt)
nbui = gettext.translation('nbui', localedir=os.path.join(ROOT, 'i18n'), fallback=True)
env.install_gettext_translations(nbui, newstyle=False)
contents_manager = LargeFileManager() # TODO: make this configurable like notebook
Expand All @@ -155,9 +206,9 @@ def start(self):
(url_path_join(base_url, r'/api/kernels/%s/channels' % _kernel_id_regex), ZMQChannelsHandler),
(
url_path_join(base_url, r'/voila/static/(.*)'),
tornado.web.StaticFileHandler,
MultiStaticFileHandler,
{
'path': self.static_root,
'paths': self.static_paths,
'default_filename': 'index.html'
}
)
Expand All @@ -170,7 +221,7 @@ def start(self):
{
'notebook_path': self.notebook_path,
'strip_sources': self.strip_sources,
'custom_template_path': self.custom_template_path,
'nbconvert_template_paths': self.nbconvert_template_paths,
'config': self.config
}
))
Expand Down
8 changes: 2 additions & 6 deletions voila/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,13 @@
from nbconvert.preprocessors.execute import executenb
from nbconvert import HTMLExporter

from .paths import NBCONVERT_TEMPLATE_ROOT


class VoilaHandler(JupyterHandler):
def initialize(self, notebook_path=None, strip_sources=True, custom_template_path=None, config=None):
def initialize(self, notebook_path=None, strip_sources=True, nbconvert_template_paths=None, config=None):
self.notebook_path = notebook_path
self.strip_sources = strip_sources
self.template_path = [NBCONVERT_TEMPLATE_ROOT]
self.template_path = nbconvert_template_paths
self.exporter_config = config
if custom_template_path:
self.template_path.insert(0, custom_template_path)

@tornado.web.authenticated
@tornado.gen.coroutine
Expand Down
2 changes: 0 additions & 2 deletions voila/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@

ROOT = os.path.dirname(__file__)
STATIC_ROOT = os.path.join(ROOT, 'static')
TEMPLATE_ROOT = os.path.join(ROOT, 'templates')
NBCONVERT_TEMPLATE_ROOT = os.path.join(ROOT, 'nbconvert_templates')

31 changes: 31 additions & 0 deletions voila/static_file_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os

import tornado.web


class MultiStaticFileHandler(tornado.web.StaticFileHandler):
"""A static file handler that 'merges' a list of directories
If initialized like this::
application = web.Application([
(r"/content/(.*)", web.MultiStaticFileHandler, {"paths": ["/var/1", "/var/2"]}),
])
A file will be looked up in /var/1 first, then in /var/2.
"""

def initialize(self, paths, default_filename=None):
self.roots = paths
super(MultiStaticFileHandler, self).initialize(path=paths[0], default_filename=default_filename)

def get_absolute_path(self, root, path):
# find the first absolute path that exists
self.root = self.roots[0]
for root in self.roots:
abspath = os.path.abspath(os.path.join(root, path))
if os.path.exists(abspath):
self.root = root # make sure all the other methods in the base class know how to find the file
break
return abspath

0 comments on commit 340cd65

Please sign in to comment.