Skip to content

Commit

Permalink
Towards a binder
Browse files Browse the repository at this point in the history
  • Loading branch information
SylvainCorlay committed Oct 5, 2018
1 parent 0eac37a commit 5dd3fbb
Show file tree
Hide file tree
Showing 15 changed files with 113 additions and 96 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Voila
=====

[![Binder](https://img.shields.io/badge/launch-binder-brightgreen.svg)](https://mybinder.org/v2/gh/QuantStack/voila/master?urlpath=voila/tree/notebooks)

Rendering of live Jupyter notebooks with interactive widgets.

Introduction
Expand All @@ -20,7 +22,6 @@ callbacks to changes in Jupyter interactive widgets.
When using these default settings, the code powering the Jupyter notebook is
never sent to the front-end.


Usage
-----

Expand All @@ -30,14 +31,12 @@ To render the `bqplot` example notebook as a Voila app, run
voila bqplot.ipynb
```


Related projects
----------------

Voila depends on the [nbconvert](https://github.com/jupyter/nbconvert) and
[jupyter_server](https://github.com/jupyter/jupyter_server/).


License
-------

Expand Down
7 changes: 7 additions & 0 deletions environment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: voila
dependencies:
- ipyvolume
- bqplot
- scipy
- pip:
- https://github.com/QuantStack/voila/master.zip
7 changes: 7 additions & 0 deletions etc/jupyter/jupyter_notebook_config.d/voila.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"NotebookApp": {
"nbserver_extensions": {
"voila.server_extension": true
}
}
}
1 change: 0 additions & 1 deletion js/WidgetApplication.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { Kernel, ServerConnection, KernelMessage } from '@jupyterlab/services'

import { WidgetManager } from './manager'
Expand Down
5 changes: 2 additions & 3 deletions js/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,15 @@ var loaders = [
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, use: 'url-loader?limit=10000&mimetype=image/svg+xml' }
]

var distRoot = path.resolve(__dirname, '..', 'voila', 'static', 'dist')
var distRoot = path.resolve(__dirname, '..', 'voila', 'static')

module.exports = [
{
entry: ['babel-polyfill', './index.js'],
output: {
filename: 'libwidgets.js',
path: distRoot,
libraryTarget: 'amd',
publicPath: '/dist/'
libraryTarget: 'amd'
},
module: { loaders: loaders },
devtool: 'source-map'
Expand Down
11 changes: 9 additions & 2 deletions notebooks/ipyvolume.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
"outputs": [],
"source": [
"import ipyvolume as ipv\n",
"ipv.example_ylm();"
"ipv.examples.example_ylm();"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand All @@ -27,7 +34,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.6"
"version": "3.6.5"
}
},
"nbformat": 4,
Expand Down
12 changes: 7 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class NPM(Command):

node_modules = os.path.join(node_root, 'node_modules')

lib_root = os.path.join(here, 'voila', 'static', 'dist')
lib_root = os.path.join(here, 'voila', 'static')
targets = [
os.path.join(lib_root, 'libwidgets.js')
]
Expand Down Expand Up @@ -137,7 +137,10 @@ def run(self):
'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'])],
'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'])
],
'cmdclass': {
'build_py': js_prerelease(build_py),
'egg_info': js_prerelease(egg_info),
Expand All @@ -147,8 +150,7 @@ def run(self):
'package_data': {
'voila': [
'templates/*',
'static/*',
'static/dist/*',
'static/*'
]
},
'entry_points': {
Expand All @@ -159,7 +161,7 @@ def run(self):
'install_requires': [
'jupyter_server',
'nbconvert>=5.4,<6',
'whatchdog'
'watchdog'
],
'author': 'QuantStack',
'author_email': '[email protected]',
Expand Down
66 changes: 36 additions & 30 deletions voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from jupyter_server.services.kernels.handlers import KernelHandler, ZMQChannelsHandler
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 .handler import VoilaHandler
Expand Down Expand Up @@ -110,22 +111,43 @@ def start(self):
]
)

handlers = [
(r'/api/kernels/%s' % _kernel_id_regex, KernelHandler),
(r'/api/kernels/%s/channels' % _kernel_id_regex, ZMQChannelsHandler),
jenv_opt = {"autoescape": True} # we might want extra options via cmd line like notebook server
env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(TEMPLATE_ROOT)), extensions=['jinja2.ext.i18n'], **jenv_opt)
nbui = gettext.translation('nbui', localedir=str(ROOT / 'i18n'), fallback=True)
env.install_gettext_translations(nbui, newstyle=False)
contents_manager = LargeFileManager() # TODO: make this configurable like notebook

webapp = tornado.web.Application(
kernel_manager=kernel_manager,
allow_remote_access=True,
autoreload=self.autoreload,
voila_jinja2_env=env,
jinja2_env=env,
static_path='/',
server_root_dir='/',
contents_manager=contents_manager
)

base_url = webapp.settings.get('base_url', '/')

handlers = []

handlers.extend([
(url_path_join(base_url, r'/api/kernels/%s' % _kernel_id_regex), KernelHandler),
(url_path_join(base_url, r'/api/kernels/%s/channels' % _kernel_id_regex), ZMQChannelsHandler),
(
r"/voila/static/(.*)",
url_path_join(base_url, r'/voila/static/(.*)'),
tornado.web.StaticFileHandler,
{
'path': self.static_root,
'default_filename': 'index.html'
}
)
]
])

if self.notebook_path:
handlers.append((
r'/',
url_path_join(base_url, r'/'),
VoilaHandler,
{
'notebook_path': self.notebook_path,
Expand All @@ -134,34 +156,18 @@ def start(self):
))
else:
handlers.extend([
('/', VoilaTreeHandler),
('/voila/tree' + path_regex, VoilaTreeHandler),
('/voila/render' + path_regex, VoilaHandler, {'strip_sources': self.strip_sources}),
(base_url, VoilaTreeHandler),
(url_path_join(base_url, r'/voila/tree' + path_regex), VoilaTreeHandler),
(url_path_join(base_url, r'/voila/render' + path_regex), VoilaHandler, {'strip_sources': self.strip_sources}),
])
if self.autoreload:
handlers.append(('/voila/watchdog' + path_regex, WatchDogHandler))

jenv_opt = {"autoescape": True} # we might want extra options via cmd line like notebook server
env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(TEMPLATE_ROOT)), extensions=['jinja2.ext.i18n'], **jenv_opt)
nbui = gettext.translation('nbui', localedir=str(ROOT / 'i18n'), fallback=True)
env.install_gettext_translations(nbui, newstyle=False)

contents_manager = LargeFileManager() # TODO: make this configurable like notebook

handlers.append(
(url_path_join(base_url, r'/voila/watchdog' + path_regex), WatchDogHandler)
)

app = tornado.web.Application(
handlers,
kernel_manager=kernel_manager,
allow_remote_access=True,
autoreload=self.autoreload,
voila_jinja2_env=env,
jinja2_env=env,
static_path='/',
server_root_dir='/',
contents_manager=contents_manager
)
webapp.add_handlers('.*$', handlers)

app.listen(self.port)
webapp.listen(self.port)
self.log.info(f'Voila listening on port {self.port}.')

try:
Expand Down
4 changes: 2 additions & 2 deletions voila/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def initialize(self, notebook_path=None, strip_sources=True):
@tornado.gen.coroutine
def get(self, path=None):
if path:
path = path.strip('/') # remove leading /
path = path.strip(self.base_url)
path += '.ipynb' # when used as a jupyter server extension, we don't use the extension
# if the handler got a notebook_path argument, always serve that
notebook_path = self.notebook_path or path
Expand All @@ -37,7 +37,7 @@ def get(self, path=None):
result = executenb(notebook, km=km)

# render notebook to html
resources = dict(kernel_id=kernel_id)
resources = dict(kernel_id=kernel_id, base_url=self.base_url)
html, resources = HTMLExporter(template_file=str(TEMPLATE_ROOT / 'voila.tpl'), exclude_input=self.strip_sources,
exclude_output_prompt=self.strip_sources, exclude_input_prompt=self.strip_sources
).from_notebook_node(result, resources=resources)
Expand Down
18 changes: 8 additions & 10 deletions voila/server_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
from .paths import ROOT, TEMPLATE_ROOT, STATIC_ROOT
from .handler import VoilaHandler
from .treehandler import VoilaTreeHandler
from .watchdog import WatchDogHandler

# from .watchdog import WatchDogHandler

def load_jupyter_server_extension(server_app):
web_app = server_app.web_app
Expand All @@ -26,12 +25,11 @@ def load_jupyter_server_extension(server_app):
env.install_gettext_translations(nbui, newstyle=False)

host_pattern = '.*$'
base_url = url_path_join(web_app.settings['base_url'])
web_app.add_handlers(host_pattern, [
(url_path_join(web_app.settings['base_url'], '/voila/render' + path_regex), VoilaHandler),
(url_path_join(web_app.settings['base_url'], '/voila/watchdog' + path_regex), WatchDogHandler),
(url_path_join(web_app.settings['base_url'], '/voila'), VoilaTreeHandler),
(url_path_join(web_app.settings['base_url'], '/voila/tree' + path_regex), VoilaTreeHandler),
(url_path_join(web_app.settings['base_url'], '/voila/static/(.*)'), tornado.web.StaticFileHandler,
{'path': str(STATIC_ROOT)})

])
(url_path_join(base_url, '/voila/render' + path_regex), VoilaHandler),
# (url_path_join(base_url, '/voila/watchdog' + path_regex), WatchDogHandler),
(url_path_join(base_url, '/voila'), VoilaTreeHandler),
(url_path_join(base_url, '/voila/tree' + path_regex), VoilaTreeHandler),
(url_path_join(base_url, '/voila/static/(.*)'), tornado.web.StaticFileHandler, {'path': str(STATIC_ROOT)})
])
23 changes: 8 additions & 15 deletions voila/static/main.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@

// Initialize requirejs (for dynamically loading widgets)
// and render widgets on page.

// Render widgets on page.
var kernel_id = null;
var scripts = document.getElementsByTagName('script');
Array.prototype.forEach.call(scripts, (script) => {
kernel_id = script.getAttribute('data-jupyter-kernel-id') || kernel_id;
})

requirejs.config({
baseUrl: '/voila/static/dist'
})

require(['libwidgets'], function(lib) {
var BASEURL = window.location.href
var BASEURL = window.location.href;

var WSURL;
if (window.location.protocol.startsWith('https')) {
WSURL = 'wss://' + window.location.host
WSURL = 'wss://' + window.location.host;
}
else {
WSURL = 'ws://' + window.location.host
else {;
WSURL = 'ws://' + window.location.host;
}

var widgetApp = new lib.WidgetApplication(BASEURL, WSURL, lib.requireLoader, kernel_id);

var path = window.location.pathname.substr(14);
var wsWatchdog = new WebSocket(WSURL + '/voila/watchdog/' + path);
wsWatchdog.onmessage = (evt) => {
var msg = JSON.parse(evt.data)
console.log('msg', msg)
var msg = JSON.parse(evt.data);
if(msg.type == 'reload') {
var timeout = 0;
if(msg.delay == 'long')
if(msg.delay == 'long') {
timeout = 1000;
}
setTimeout(() => {
location.href = location.href;
}, timeout)
Expand Down
8 changes: 3 additions & 5 deletions voila/templates/tree.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends "page.html" %}


{% block title %}{{page_title}}{% endblock %}


Expand Down Expand Up @@ -33,17 +34,14 @@

{% for content in contents.content %}
{% if content.type == 'notebook' %}
<li><a href="/voila/render/{{content.path.rpartition('.')[0]}}"><i class="fa fa-book"></i>{{content.name.rpartition('.')[0]}}</a></li>
<li><a href="{{base_url}}voila/render/{{content.path.rpartition('.')[0]}}"><i class="fa fa-book"></i>{{content.name.rpartition('.')[0]}}</a></li>
{% endif %}
{% if content.type == 'directory' %}
<li><a href="/voila/tree/{{content.path}}"><i class="fa fa-folder"></i>{{content.name}}</a></li>
<li><a href="{{base_url}}voila/tree/{{content.path}}"><i class="fa fa-folder"></i>{{content.name}}</a></li>
{% endif %}
{% endfor %}
</ul>




{% endblock %}

{% block script %}
Expand Down
Loading

0 comments on commit 5dd3fbb

Please sign in to comment.