Get current notebook path in JupyterLab

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'}}]
1 Like

I know it is a late answer, but… I would use INotebookTracker. Well, I am using INotebookTracker in the LSP extension for that exact purpose.

2 Likes