Hi all, I see this kind of question has been asked a lot already on Stack Overflow and GitHub etc., but I haven’t been able to find a workable answer for my needs so far. I’m in JupyterLab 3.0.14 and using this as a template for a server extension: extension-examples/server-extension at master · jupyterlab/extension-examples · GitHub. I’m trying to use the code from P. Toccaceli’s answer here to get the name and path of the currently active notebook in JupyterLab so I can convert it to a .py file. This will be triggered by a button added to the toolbar. Is this the right approach for what I’m trying to do, or should it be something more like this using INotebookTracker?
P. Toccaceli’s original code:
import jupyterlab
if jupyterlab.__version__.split(".")[0] == "3":
from jupyter_server import serverapp as app
key_srv_directory = 'root_dir'
else :
from notebook import notebookapp as app
key_srv_directory = 'notebook_dir'
import urllib
import json
import os
import ipykernel
def notebook_path(key_srv_directory, ):
"""Returns the absolute path of the Notebook or None if it cannot be determined
NOTE: works only when the security is token-based or there is also no password
"""
connection_file = os.path.basename(ipykernel.get_connection_file())
kernel_id = connection_file.split('-', 1)[1].split('.')[0]
for srv in app.list_running_servers():
try:
if srv['token']=='' and not srv['password']: # No token and no password, ahem...
req = urllib.request.urlopen(srv['url']+'api/sessions')
else:
req = urllib.request.urlopen(srv['url']+'api/sessions?token='+srv['token'])
sessions = json.load(req)
for sess in sessions:
if sess['kernel']['id'] == kernel_id:
return os.path.join(srv[key_srv_directory],sess['notebook']['path'])
except:
pass # There may be stale entries in the runtime directory
return None
In my handlers.py file I’m trying to do something like this in the get method, with the above code stored in a file called get_nbpath.py:
import os
import json
import subprocess
from notebook.base.handlers import APIHandler
from notebook.utils import url_path_join
import tornado
from tornado.web import StaticFileHandler
from get_nbpath import notebook_path
class RouteHandler(APIHandler):
# The following decorator should be present on all verb methods (head, get, post,
# patch, put, delete, options) to ensure only authorized user can request the
# Jupyter server
@tornado.web.authenticated
def get(self):
curr_nb_path = notebook_path('root_dir')
subprocess.run(["jupyter", "nbconvert", curr_nb_path, "--to", "script"])
self.finish(json.dumps({"data": "At the /jlab-ext-example/hello endpoint; finding and converting notebook"}))
@tornado.web.authenticated
def post(self):
# input_data is a dictionary with a key "name"
input_data = self.get_json_body()
data = {"greetings": "Hello {}, enjoying JupyterLab?".format(input_data["name"])}
self.finish(json.dumps(data))
The code to get the notebook path works fine when I run it (or the related package ipynbname
with nb_path = ipynbname.path()
) directly in a notebook itself, but when I try to call it from my handlers.py file the get and post requests break with SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
. I tried modifying the code slightly to remove the error handling and only output the paths for notebooks and not other things like consoles; when running it directly from the Python console I initially received a Key Error: 'notebook' at return os.path.join(srv[key_srv_directory],sess['notebook']['path'])
. My changes fixed this and the desired path was output in the console, but my post and get requests in handler.py are still breaking with the same syntax error as before. Here’s my revised version:
import jupyterlab
if jupyterlab.__version__.split(".")[0] == "3":
from jupyter_server import serverapp as app
key_srv_directory = 'root_dir'
else :
from notebook import notebookapp as app
key_srv_directory = 'notebook_dir'
import urllib
import json
import os
import ipykernel
def notebook_path(key_srv_directory, ):
"""Returns the absolute path of the Notebook or None if it cannot be determined
NOTE: works only when the security is token-based or there is also no password
"""
connection_file = os.path.basename(ipykernel.get_connection_file())
kernel_id = connection_file.split('-', 1)[1].split('.')[0]
for srv in app.list_running_servers():
if srv['token']=='' and not srv['password']: # No token and no password, ahem...
req = urllib.request.urlopen(srv['url']+'api/sessions')
print('no token or password')
else:
req = urllib.request.urlopen(srv['url']+'api/sessions?token='+srv['token'])
sessions = json.load(req)
for sess in sessions:
if (sess['type'] == 'notebook'):
return str(os.path.join(srv[key_srv_directory],sess['notebook']['path']))
break
else:
return None
There’s the issue of course of having multiple notebooks open at once (right now it just returns the first one it finds), but I was thinking I could find the most recent one by finding the kernel in sessions
with the most recent last_activity
field. Before I jump into that though I want to make sure this is the right thing to pursue.
Printing out sessions
I get something like this:
sessions: [{'id': '8c8ab32a-eb77-4f6a-8fa0-a8a95f1e7174',
'path': 'jlab_ext_example/console-3-917a6089-e9e5-44ed-94f0-3ea92c150ded',
'name': 'Console 3',
'type': 'console',
'kernel':
{'id': '40f06252-fd4a-46af-88ea-13bd0140140d',
'name': 'python3',
'last_activity': '2021-06-18T16:33:28.897486Z',
'execution_state': 'busy',
'connections': 1
}
},
{'id': 'e22f3fe8-b987-4a6d-93d1-8b3afbcc1b6d',
'path': 'nbtest.ipynb',
'name': 'nbtest.ipynb',
'type': 'notebook',
'kernel': {
'id': '157590e9-039a-4a3b-8a42-1090dbe74ceb',
'name': 'python3',
'last_activity': '2021-06-18T16:33:04.173662Z',
'execution_state': 'idle',
'connections': 3},
'notebook': {'path': 'nbtest.ipynb',
'name': 'nbtest.ipynb'}}]