pywwt/jupyter_server.py: fix file service in JupyterHub contexts

When we're serving up files in JupyterHub, we need to include the notebook
server's base_url in the URL we generate. This is obnoxious to determine from
inside a kernel, but I found some code that shows how to do it.

Also added some module-level documentation outlining what's going on and why.
This commit is contained in:
Peter Williams 2019-06-06 14:54:52 -04:00
Родитель ba1b28a9b0
Коммит 59994c61ad
1 изменённых файлов: 73 добавлений и 2 удалений

Просмотреть файл

@ -1,3 +1,22 @@
"""Routines for the pywwt notebook server extension.
In order to make files available to the WWT engine, we need to serve them over
HTTP. Here we extend the Notebook server to be able to server both static
pywwt HTML/JS assets and custom local files specified by the user.
The tricky part is that the Notebook server and the kernel are in separate
processes, and there is no super convenient way for them to communicate. Here,
we assume that they share a home directory, and enable communications by
writing JSON to a file in $HOME.
The recently added code in :func:`_compute_notebook_server_base_url`
demonstrates how the kernel can figure out a URL by which to communicate with
the server. So, in principle, we could replace this local-file communication
with REST API calls. I'm not aware of any better way for the kernel and server
to talk to each other. (Note that the notebook "comms" API is for the kernel
to talk to the JavaScript frontend, not the notebook server.)
"""
import os
import json
import mimetypes
@ -43,8 +62,60 @@ class WWTFileHandler(IPythonHandler):
self.finish(content)
def serve_file(path, extension=''):
def _compute_notebook_server_base_url():
"""Figure out the base_url of the current Jupyter notebook server.
Copied from
https://github.com/jupyter/notebook/issues/3156#issuecomment-401119433
with miniscule changes. This is gross, but appears to be the best
available option right now.
"""
import ipykernel
import json
from notebook.notebookapp import list_running_servers
import re
import requests
# First, find our ID.
kernel_id = re.search(
'kernel-(.*).json',
ipykernel.connect.get_connection_file()
).group(1)
# Now, check all of the running servers known on this machine. We have to
# talk to each server to figure out if it's ours or somebody else's.
for s in list_running_servers():
response = requests.get(
requests.compat.urljoin(s['url'], 'api/sessions'),
params = {'token': s.get('token', '')}
)
for n in json.loads(response.text):
if n['kernel']['id'] == kernel_id:
return s['base_url'] # Found it!
raise Exception('cannot locate our notebook server; is this code running in a Jupyter kernel?')
_server_base_url = None
def get_notebook_server_base_url():
"""Get the "base_url" of the current Jupyter notebook server.
"""
global _server_base_url
if _server_base_url is None:
_server_base_url = _compute_notebook_server_base_url()
return _server_base_url
def serve_file(path, extension=''):
"""Given a path to a file on local disk, instruct the notebook server
to serve it up over HTTP. Returns a relative URL that can be used to
access the file.
"""
if not os.path.exists(path):
raise ValueError("Path {0} does not exist".format(path))
@ -60,7 +131,7 @@ def serve_file(path, extension=''):
with open(CONFIG, 'w') as f:
json.dump(config, f)
return '/wwt/' + hash
return url_path_join(get_notebook_server_base_url(), '/wwt/' + hash)
def load_jupyter_server_extension(nb_server_app):