Skip to content

Commit

Permalink
Voila as an ExtensionApp (was voila-dashboards#270)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Mar 24, 2020
1 parent 112163d commit 41d9efa
Show file tree
Hide file tree
Showing 38 changed files with 385 additions and 753 deletions.
2 changes: 1 addition & 1 deletion etc/jupyter/jupyter_server_config.d/voila.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"ServerApp": {
"jpserver_extensions": {
"voila.server_extension": true
"voila": true
}
}
}
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,12 +371,13 @@ def get_data_files():
},
'entry_points': {
'console_scripts': [
'jupyter-voila = voila.app:main',
'voila = voila.app:main'
]
},
'install_requires': [
'async_generator',
'jupyter_server>=0.1.0,<0.2.0',
'jupyter_server>=0.2.1',
'nbconvert>=5.5.0,<6',
'jupyterlab_pygments>=0.1.0,<0.2',
'pygments>=2.4.1,<3' # Explicitly requiring pygments which is a second-order dependency.
Expand All @@ -385,8 +386,8 @@ def get_data_files():
'extras_require': {
'test': [
'mock',
'pytest<4',
'pytest-tornado',
'pytest==5.3.2',
'pytest-tornasync',
'matplotlib',
'ipywidgets'
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

{%- block html_head_js -%}
<script
src="{{resources.base_url}}voila/static/require.min.js"
src="{{resources.base_url}}static/voila/require.min.js"
integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA="
crossorigin="anonymous">
</script>
Expand Down Expand Up @@ -47,12 +47,15 @@
{% block footer %}
{% block footer_js %}
<script>
requirejs.config({ baseUrl: '{{resources.base_url}}voila/', waitSeconds: 30})
requirejs.config({
baseUrl: '{{resources.base_url}}static/voila/',
waitSeconds: 30
})
requirejs(
[
"static/main",
"main",
{% for ext in resources.nbextensions -%}
"{{resources.base_url}}voila/nbextensions/{{ ext }}.js",
"{{resources.base_url}}static/voila/nbextensions/{{ ext }}.js",
{% endfor %}
]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
{%- block html_head_css -%}
{{ super() }}

<link rel="stylesheet" type="text/css" href="{{resources.base_url}}voila/static/index.css">
<link rel="stylesheet" type="text/css" href="{{resources.base_url}}static/voila/index.css"></link>

{% if resources.theme == 'dark' %}
<link rel="stylesheet" type="text/css" href="{{resources.base_url}}voila/static/theme-dark.css">
<link rel="stylesheet" type="text/css" href="{{resources.base_url}}static/voila/theme-dark.css"></link>
{% else %}
<link rel="stylesheet" type="text/css" href="{{resources.base_url}}voila/static/theme-light.css">
<link rel="stylesheet" type="text/css" href="{{resources.base_url}}static/voila/theme-light.css"></link>
{% endif %}

{% for css in resources.inlining.css %}
Expand Down
2 changes: 1 addition & 1 deletion share/jupyter/voila/templates/default/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
****************************************************************************/

// NOTE: this file is not transpiled, async/await is the only modern feature we use here
require(['static/voila'], function(voila) {
require(['voila'], function(voila) {
// requirejs doesn't like to be passed an async function, so create one inside
(async function() {
var kernel = await voila.connectKernel()
Expand Down
21 changes: 6 additions & 15 deletions tests/app/config_paths_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,17 @@

@pytest.fixture
def voila_config_file_paths_arg():
path = os.path.join(BASE_DIR, '..', 'configs', 'general')
return '--VoilaTest.config_file_paths=[%r]' % path
config_path = os.path.join(BASE_DIR, '..', 'configs', 'general')
return '--Voila.config_file_paths=[%r]' % config_path


def test_config_app(voila_app):
assert voila_app.voila_configuration.template == 'test_template'
assert voila_app.voila_configuration.enable_nbextensions is True
assert voila_app.template == 'test_template'
assert voila_app.enable_nbextensions is True


def test_config_kernel_manager(voila_app):
assert voila_app.kernel_manager.cull_interval == 10


def test_config_contents_manager(voila_app):
assert voila_app.contents_manager.use_atomic_writing is False


@pytest.mark.gen_test
def test_template(http_client, base_url):
response = yield http_client.fetch(base_url)
async def test_template(fetch, token):
response = await fetch('voila', params={'token': token}, method='GET')
assert response.code == 200
assert 'test_template.css' in response.body.decode('utf-8')
assert 'Hi Voila' in response.body.decode('utf-8')
71 changes: 53 additions & 18 deletions tests/app/conftest.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import os

import urllib
import pytest

import voila.app
from voila.app import Voila


BASE_DIR = os.path.dirname(__file__)


class VoilaTest(voila.app.Voila):
def listen(self):
pass # the ioloop is taken care of by the pytest-tornado framework
@pytest.fixture
def voila_config(notebook_directory):
return dict(root_dir=notebook_directory)


@pytest.fixture
def voila_config():
return lambda app: None
def server_config(notebook_directory):
return dict(root_dir=notebook_directory, default_url="voila", log_level="DEBUG")


@pytest.fixture
Expand All @@ -25,26 +26,60 @@ def voila_args_extra():
@pytest.fixture
def voila_config_file_paths_arg():
# we don't want the tests to use any configuration on the system
return '--VoilaTest.config_file_paths=[]'
return '--Voila.config_file_paths=[]'


@pytest.fixture
def voila_args(voila_notebook, voila_args_extra, voila_config_file_paths_arg):
debug_args = ['--VoilaTest.log_level=DEBUG'] if os.environ.get('VOILA_TEST_DEBUG', False) else []
return [voila_notebook, voila_config_file_paths_arg] + voila_args_extra + debug_args
return [voila_notebook, voila_config_file_paths_arg] + voila_args_extra


@pytest.fixture
def voila_app(voila_args, voila_config):
voila_app = VoilaTest.instance()
voila_app.initialize(voila_args + ['--no-browser'])
voila_config(voila_app)
voila_app.start()
def voila_app(server_config, voila_config, voila_args):
# Get an instance of Voila
voila_app = Voila(**voila_config)
# Get an instance of the underlying server. This is
# configured automatically when launched by command line.
serverapp = voila_app.initialize_server(voila_args, **server_config)
# silence the start method for pytests.
serverapp.start = lambda self: None
voila_app.initialize(serverapp, argv=voila_args)
yield voila_app
voila_app.stop()
voila_app.clear_instance()
serverapp.cleanup_kernels()
serverapp.clear_instance()


@pytest.fixture
def token(voila_app):
return voila_app.serverapp.token


@pytest.fixture
def app(voila_app):
return voila_app.app
return voila_app.serverapp.web_app


@pytest.fixture
def add_token(voila_app):
"""Add a token to any url."""

def add_token_to_url(url):
parts = list(urllib.parse.urlparse(url))
if parts[4] == "":
query = "token={}".format(voila_app.serverapp.token)
else:
query = "{q}&token={token}".format(
q=parts[4], token=voila_app.serverapp.token
)
parts[4] = query
new_url = urllib.parse.urlunparse(parts)
return new_url

return add_token_to_url


@pytest.fixture
def default_url(base_url, add_token):
print('base_url')
print(base_url)
return add_token(base_url)
20 changes: 11 additions & 9 deletions tests/app/cwd_subdir_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
import pytest


@pytest.fixture
def cwd_subdir_notebook_url(base_url):
return base_url + "/voila/render/subdir/cwd_subdir.ipynb"
#@pytest.fixture
#def cwd_subdir_notebook_url(base_url):
# return base_url + "/voila/render/subdir/cwd_subdir.ipynb"


@pytest.fixture
def voila_args(notebook_directory, voila_args_extra):
return ['--VoilaTest.root_dir=%r' % notebook_directory, '--VoilaTest.log_level=DEBUG'] + voila_args_extra
return [
"--ServerApp.root_dir=%r" % notebook_directory,
"--Voila.log_level=DEBUG",
] + voila_args_extra


@pytest.mark.gen_test
def test_hello_world(http_client, cwd_subdir_notebook_url):
response = yield http_client.fetch(cwd_subdir_notebook_url)
html_text = response.body.decode('utf-8')
assert 'check for the cwd' in html_text
async def test_hello_world(fetch, token):
response = await fetch('voila', 'render', 'subdir', 'cwd_subdir.ipynb', params={'token': token}, method='GET')
html_text = response.body.decode("utf-8")
assert "check for the cwd" in html_text
6 changes: 2 additions & 4 deletions tests/app/cwd_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# tests the --template argument of voila
import pytest

import os


Expand All @@ -9,8 +8,7 @@ def voila_notebook(notebook_directory):
return os.path.join(notebook_directory, 'cwd.ipynb')


@pytest.mark.gen_test
def test_template_cwd(http_client, base_url, notebook_directory):
response = yield http_client.fetch(base_url)
async def test_template_cwd(fetch, token):
response = await fetch('voila', params={'token': token}, method='GET')
html_text = response.body.decode('utf-8')
assert 'check for the cwd' in html_text
15 changes: 7 additions & 8 deletions tests/app/execute_cpp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@
TEST_XEUS_CLING = os.environ.get('VOILA_TEST_XEUS_CLING', '') == '1'


@pytest.fixture
def cpp_file_url(base_url):
return base_url + "/voila/render/print.xcpp"
#@pytest.fixture
#def cpp_file_url(base_url, add_token):
# return add_token(base_url + "/voila/render/print.xcpp")


@pytest.fixture
def voila_args_extra():
return ['--VoilaConfiguration.extension_language_mapping={".xcpp": "C++11"}']
return ['--Voila.extension_language_mapping={".xcpp": "C++11"}']


@pytest.fixture
def voila_args(notebook_directory, voila_args_extra):
return ['--VoilaTest.root_dir=%r' % notebook_directory] + voila_args_extra
return ['--Voila.root_dir=%r' % notebook_directory] + voila_args_extra


@pytest.mark.skipif(not TEST_XEUS_CLING, reason='opt in to avoid having to install xeus-cling')
@pytest.mark.gen_test
def test_non_existing_kernel(http_client, cpp_file_url):
response = yield http_client.fetch(cpp_file_url)
async def test_non_existing_kernel(fetch, token):
response = await fetch('voila', 'render', 'print.xcpp', params={'token': token}, method='GET')
assert response.code == 200
assert 'Hello voila, from c++' in response.body.decode('utf-8')
27 changes: 11 additions & 16 deletions tests/app/execute_test.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
# test basics of voila running a notebook
import pytest

import tornado.web
import tornado.gen

import re
import json
import asyncio

try:
from unittest import mock
except ImportError:
import mock


@pytest.mark.gen_test
def test_hello_world(http_client, base_url):
response = yield http_client.fetch(base_url)
async def test_hello_world(fetch, token):
response = await fetch('voila', params={'token': token}, method='GET')
assert response.code == 200
html_text = response.body.decode('utf-8')
assert 'Hi Voila' in html_text
assert 'print(' not in html_text, 'by default the source code should be stripped'
assert 'test_template.css' not in html_text, "test_template should not be the default"


@pytest.mark.gen_test
def test_no_execute_allowed(voila_app, app, http_client, base_url):
assert voila_app.app is app
response = (yield http_client.fetch(base_url)).body.decode('utf-8')
async def test_no_execute_allowed(fetch, token, voila_app, add_token, base_url):
response = (await fetch('voila', params={'token': token}, method='GET')).body.decode('utf-8')
pattern = r"""kernelId": ["']([0-9a-zA-Z-]+)["']"""
groups = re.findall(pattern, response)
kernel_id = groups[0]
print(kernel_id, base_url)
session_id = '445edd75-c6f5-45d2-8b58-5fe8f84a7123'
url = '{base_url}/api/kernels/{kernel_id}/channels?session_id={session_id}'.format(
kernel_id=kernel_id, base_url=base_url, session_id=session_id
kernel_id=kernel_id, base_url=voila_app.serverapp.connection_url[:-6], session_id=session_id
).replace('http://', 'ws://')
conn = yield tornado.websocket.websocket_connect(url)
url = add_token(url)
conn = await tornado.websocket.websocket_connect(url)

msg = {
"header": {
Expand All @@ -58,9 +53,9 @@ def test_no_execute_allowed(voila_app, app, http_client, base_url):
"parent_header": {},
"channel": "shell",
}
with mock.patch.object(voila_app.log, 'warning') as mock_warning:
yield conn.write_message(json.dumps(msg))
with mock.patch.object(voila_app.serverapp.log, 'warning') as mock_warning:
await conn.write_message(json.dumps(msg))
# make sure the warning method is called
while not mock_warning.called:
yield tornado.gen.sleep(0.1)
await asyncio.sleep(0.1)
mock_warning.assert_called_with('Received message of type "execute_request", which is not allowed. Ignoring.')
7 changes: 2 additions & 5 deletions tests/app/image_inlining_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# tests the --template argument of voila
import pytest

import base64

import os


Expand All @@ -11,9 +9,8 @@ def voila_notebook(notebook_directory):
return os.path.join(notebook_directory, 'images.ipynb')


@pytest.mark.gen_test
def test_image_inlining(http_client, base_url, notebook_directory):
response = yield http_client.fetch(base_url)
async def test_image_inlining(fetch, token, notebook_directory):
response = await fetch('voila', params={'token': token}, method='GET')
html_text = response.body.decode('utf-8')

assert 'data:image/svg+xml;base64,' in html_text
Expand Down
5 changes: 2 additions & 3 deletions tests/app/many_iopub_messages_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ def _close():
return client


@pytest.mark.gen_test
def test_template_cwd(http_client, base_url, notebook_directory):
response = yield http_client.fetch(base_url)
async def test_template_cwd(fetch, token, notebook_directory):
response = await fetch('voila', params={'token': token}, method='GET')
html_text = response.body.decode('utf-8')
assert 'you should see me' in html_text
Loading

0 comments on commit 41d9efa

Please sign in to comment.