Server Extensions#
A Jupyter Server extension is typically a module or package that extends to Server’s REST API/endpoints—i.e. adds extra request handlers to Server’s Tornado Web Application.
You can check some simple examples on the examples folder in the GitHub jupyter_server repository.
Distributing a server extension#
Putting it all together, authors can distribute their extension following this steps:
- Add a
_jupyter_server_extension_points()
function at the extension’s root. This function should likely live in the
__init__.py
found at the root of the extension package. It will look something like this:# Found in the __init__.py of package def _jupyter_server_extension_points(): return [ { "module": "myextension.app", "app": MyExtensionApp } ]
- Add a
- Create an extension by writing a
_load_jupyter_server_extension()
function or subclassingExtensionApp
. This is where the extension logic will live (i.e. custom extension handlers, config, etc). See the sections above for more information on how to create an extension.
- Create an extension by writing a
- Add the following JSON config file to the extension package.
The file should be named after the extension (e.g.
myextension.json
) and saved in a subdirectory of the package with the prefix:jupyter-config/jupyter_server_config.d/
. The extension package will have a similar structure to this example:myextension ├── myextension/ │ ├── __init__.py │ └── app.py ├── jupyter-config/ │ └── jupyter_server_config.d/ │ └── myextension.json └── setup.py
The contents of the JSON file will tell Jupyter Server to load the extension when a user installs the package:
{ "ServerApp": { "jpserver_extensions": { "myextension": true } } }
When the extension is installed, this JSON file will be copied to the
jupyter_server_config.d
directory found in one of Jupyter’s paths.Users can toggle the enabling/disableing of extension using the command:
jupyter server extension disable myextension
which will change the boolean value in the JSON file above.
- Create a
setup.py
that automatically enables the extension. Add a few extra lines the extension package’s
setup
functionfrom setuptools import setup setup( name="myextension", ... include_package_data=True, data_files=[ ( "etc/jupyter/jupyter_server_config.d", ["jupyter-config/jupyter_server_config.d/myextension.json"] ), ] )
- Create a
Migrating an extension to use Jupyter Server#
If you’re a developer of a classic Notebook Server extension, your extension
should be able to work with both the classic notebook server and
jupyter_server
.
There are a few key steps to make this happen:
- Point Jupyter Server to the
load_jupyter_server_extension
function with a new reference name. The
load_jupyter_server_extension
function was the key to loading a server extension in the classic Notebook Server. Jupyter Server expects the name of this function to be prefixed with an underscore—i.e._load_jupyter_server_extension
. You can easily achieve this by adding a reference to the old function name with the new name in the same module.def load_jupyter_server_extension(nb_server_app): ... # Reference the old function name with the new function name. _load_jupyter_server_extension = load_jupyter_server_extension
- Point Jupyter Server to the
- Add new data files to your extension package that enable it with Jupyter Server.
This new file can go next to your classic notebook server data files. Create a new sub-directory,
jupyter_server_config.d
, and add a new.json
file there:myextension ├── myextension/ │ ├── __init__.py │ └── app.py ├── jupyter-config/ │ └── jupyter_notebook_config.d/ │ └── myextension.json │ └── jupyter_server_config.d/ │ └── myextension.json └── setup.py
The new
.json
file should look something like this (you’ll notice the changes in the configured class and trait names):{ "ServerApp": { "jpserver_extensions": { "myextension": true } } }
Update your extension package’s
setup.py
so that the data-files are moved into the jupyter configuration directories when users download the package.from setuptools import setup setup( name="myextension", ... include_package_data=True, data_files=[ ( "etc/jupyter/jupyter_server_config.d", ["jupyter-config/jupyter_server_config.d/myextension.json"] ), ( "etc/jupyter/jupyter_notebook_config.d", ["jupyter-config/jupyter_notebook_config.d/myextension.json"] ), ] )
- (Optional) Point extension at the new favicon location.
The favicons in the Jupyter Notebook have been moved to a new location in Jupyter Server. If your extension is using one of these icons, you’ll want to add a set of redirect handlers this. (In
ExtensionApp
, this is handled automatically).This usually means adding a chunk to your
load_jupyter_server_extension
function similar to this:def load_jupyter_server_extension(nb_server_app): web_app = nb_server_app.web_app host_pattern = '.*$' base_url = web_app.settings['base_url'] # Add custom extensions handler. custom_handlers = [ ... ] # Favicon redirects. favicon_redirects = [ ( url_path_join(base_url, "/static/favicons/favicon.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon.ico") ), ( url_path_join(base_url, "/static/favicons/favicon-busy-1.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-1.ico")} ), ( url_path_join(base_url, "/static/favicons/favicon-busy-2.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-2.ico")} ), ( url_path_join(base_url, "/static/favicons/favicon-busy-3.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-3.ico")} ), ( url_path_join(base_url, "/static/favicons/favicon-file.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-file.ico")} ), ( url_path_join(base_url, "/static/favicons/favicon-notebook.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-notebook.ico")} ), ( url_path_join(base_url, "/static/favicons/favicon-terminal.ico"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-terminal.ico")} ), ( url_path_join(base_url, "/static/logo/logo.png"), RedirectHandler, {"url": url_path_join(serverapp.base_url, "static/base/images/logo.png")} ), ] web_app.add_handlers( host_pattern, custom_handlers + favicon_redirects )