|
| 1 | +# Standalone Jupyter server enhancement proposal [active] |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +There are now multiple frontends that talk to the backend services provided by the notebook server: the legacy Notebook, the dashboards, JupyterLab, standalone widgets and more. The configuration of legacy notebook and the backend server are tightly coupled. As a consequence, the other applications are forced to load the legacy notebook to use the backend server. |
| 6 | + |
| 7 | +## Proposed Enhancement |
| 8 | + |
| 9 | +Decouple the backend server (and its configuration) from the classic notebook frontend. This will require the following steps: |
| 10 | + |
| 11 | +1. Create `jupyter_server` repository |
| 12 | + - Fork of `notebook` repository. |
| 13 | + - Pure Python package with notebook backend services. |
| 14 | + - `notebook` tornado handlers and frontend logic stay in `notebook` repo. |
| 15 | + - Deprecated notebook server APIs do not move to `jupyter_server`. |
| 16 | +2. New server extensions mechanism. |
| 17 | + - server extensions move to `jupyter_server`. |
| 18 | + - new base classes to create applications from server extensions. |
| 19 | + - nbextensions stay in `notebook`. |
| 20 | + - `jupyter_notebook_config.d` folder becomes `jupyter_server_config.d` |
| 21 | + - `notebook` becomes a server extension. |
| 22 | +3. Services become server extensions |
| 23 | + - Services are just serve extensions with dependencies. |
| 24 | + - Add dependency injection system. |
| 25 | +5. Namespacing static files and REST API urls. |
| 26 | + - Each extension serves static files at the `/static/<extension>` url. |
| 27 | + - |
| 28 | +6. Migrate configuration. |
| 29 | + - Notebook App configuration stays in `jupyter_notebook_config.py` |
| 30 | + - Server and services configurations move to `jupyter_server_config.py` |
| 31 | + - Add a `migrate` application to automate this migration. |
| 32 | +7. Add necessary documentation to notebook and jupyter_server repos. |
| 33 | + |
| 34 | +## Detailed Explanation |
| 35 | + |
| 36 | +### Create a `jupyter_server` repository |
| 37 | + |
| 38 | +The first thing to do is fork `notebook`. A new `jupyter_server` repo will keep the server specific logic and remove: |
| 39 | +1. the notebook frontend code, |
| 40 | +2. deprecated notebook server APIs, and |
| 41 | +3. tornado handlers to the classic notebook interface. These pieces will stay in the `notebook` repository. |
| 42 | + |
| 43 | +Things that stay in notebook: |
| 44 | + |
| 45 | +- `edit` module: the handlers for the classic notebook text editor. |
| 46 | +- `templates` directory: all html templates for the classic notebook |
| 47 | +- `terminal` module: handlers for the classic notebook terminal application. |
| 48 | +- `view` module: handlers for file viewer component. |
| 49 | +- `static` directory: all js and style sheets for notebook frontend. |
| 50 | +- `tree` module: a classic notebook file browser+handlers. |
| 51 | +- `auth` module? *(Should this move to jupyter_server?)* |
| 52 | + |
| 53 | +Things that move to jupyter_server: |
| 54 | +- `services` module: all jupyter server services, managers, and handlers |
| 55 | +- `bundler`: handlers for building download bundles |
| 56 | +- `files`: handlers for serving files from contents manager. |
| 57 | +- `kernelspec`: handler for getting kernelspec |
| 58 | +- `base`: base handler for jupyter apps. |
| 59 | +- `i18n`: module for internationalizing the jupyter server |
| 60 | + |
| 61 | + |
| 62 | +Preliminary work resides in [jupyter_server](https://github.com/jupyter/jupyter_server). |
| 63 | + |
| 64 | +### Server Extensions |
| 65 | + |
| 66 | +The extension mechanism for the *jupyter server* will be the main area where server extensions differ from notebook's server extensions. |
| 67 | + |
| 68 | +Enabled server extensions will still be loaded using the `load_jupyter_server_extension` approach when the jupyter server is started. |
| 69 | + |
| 70 | +**Server extensions as applications** |
| 71 | + |
| 72 | +In the proposed jupyter_server, extension developers may also create an application from their extension, using a new `JupyterServerExtensionApp` class. Extension developers can subclass the new base class to make server extensions into Jupyter applications (configurable and launchable from the commmand line). This new class loads extension config from file and parses configuration set from the command line. |
| 73 | + |
| 74 | +For example, the legacy notebook could be started: 1) as an enabled extension or 2) by running the traitlets application from the command line. |
| 75 | + |
| 76 | +Example extension: |
| 77 | +```python |
| 78 | +from .extension import load_jupyter_server_extension |
| 79 | + |
| 80 | +class NotebookApp(JupyterServerExtensionApp): |
| 81 | + |
| 82 | + name = 'notebook' |
| 83 | + description = 'NotebookApp as a server extension.' |
| 84 | + load_jupyter_server_extension = staticmethod(load_jupyter_server_extension) |
| 85 | +``` |
| 86 | + |
| 87 | +`JupyterServerExtensionApp` subclasses are configurable using Jupyter's configuration system. Users can generate config files for each extension in the Jupyter config path (see `jupyter --paths`). Each extension's configuration is loaded when the server is initialized or the extension application is launched. |
| 88 | + |
| 89 | +As will all jupyter applications, users can autogenerate a configuration file for their extension using `jupyter my_extension --generate-config`. |
| 90 | + |
| 91 | +Initial experimental work resides in [`jupyter_server_extension`](https://github.com/Zsailer/jupyter_server_extension). |
| 92 | + |
| 93 | +**Classic notebook server extension** |
| 94 | + |
| 95 | +The classic `NotebookApp` will become a server extension. It will inherit `JupyterServerExtensionApp`. Users can enable the notebook using the extension install/enable mechanism or enable the `notebook` in their `jupyter_server_config.py` file: |
| 96 | +```python |
| 97 | +c.ServerApp.jpserver_extensions = { |
| 98 | + 'notebook': True |
| 99 | +} |
| 100 | +``` |
| 101 | +Users can also launch the notebook application using the (usual)`jupyter notebook` command line interface. |
| 102 | + |
| 103 | +**Extension installing/enabling mechanism** |
| 104 | + |
| 105 | +The new extension mechanism in the *jupyter server* will differ from notebook's server extensions. |
| 106 | + |
| 107 | +* The `--sys-prefix` installation would become the default. (Users are confused when enabling an extension requires more permissions than the installation of the package). Installation in system-wide directories would be done with the `--system` option. |
| 108 | +* Installing an extension will include the addition of a 'manifest' file into a conf.d directory (under one of the Jupyter configuration directories, `user / sys-prefix / system`). The placement of such an extension manifest provided by a Python package can be done with `jupyter server extension install --py packagename [--user / --system / --sys-prefix]`. Packages (conda or wheels) carrying server extensions could place such manifests in the sys-prefix path by default effectively installing them. Development installations would also require the call to the installation command. |
| 109 | + |
| 110 | +* Enabling an extension is separate from the installation. Multiple scenarios are possible: |
| 111 | + - enabling an extension at the same level (user / sys-prefix / system) as where it was installed, or at a higher level (user for sys-prefix and system, or sys-prefix for system). |
| 112 | + - forcibly disabling an extension that was enabled at a lower level of precedence. |
| 113 | + - forcibly enabling an extension that was disabled at a lower level of precedence. |
| 114 | + This would be done via two `conf.d` configuration directories managing a list of disabled extensions and list of enabled extensions in the form of empty files having the name of the corresponding extension. If an extension is both disabled and enabled at the same level of precedence, disabling has precedence. Packages (conda or wheels) could place such a enabler file in the sys-prefix path by default. The `jupyter server extension enable` command would be required for development installations. |
| 115 | + |
| 116 | +(Possibly) when an extension is enabled at a given precedence level, it may only look for the version of the extension installed at the same or lower precedence level. For example, if an extension `foobar` is installed and enabled system wide, but a user installs a version with `--user`, this version will only be picked up if the user also enables it with `--user`. |
| 117 | + |
| 118 | +### Services become server extensions (with dependency injection) |
| 119 | + |
| 120 | +Right now, there are two ways to extend and customize the jupyter server: services and server extensions. This is a bit confusing for new contributors. The main difference is that services often (but not always) **depend on other services**. |
| 121 | + |
| 122 | +On example is the `sessions` service, which depends on the `kernels` and `kernelspec` services. Without these two services, the `sessions` service doesn't work (or even make sense). |
| 123 | + |
| 124 | +We could reduce complexity around `jupyter_server` by making *everything* a server extension. We would need to add a **dependency injection** system to allow extensions to depend on other extensions. Some good options are [pyinject](https://github.com/google/pinject) or [python-dependency-injector](https://github.com/ets-labs/python-dependency-injector). |
| 125 | + |
| 126 | +To port a service, it will need to be refactored using extension mechanism mentioned above. |
| 127 | + |
| 128 | +### Add namespacing to `static` endpoints and REST API urls. |
| 129 | + |
| 130 | +Currently, the notebook tornado application serves all static files underneath the `/static/` prefix. Jupyter server will add namespacing under the static url and extension REST API urls. Each extension will serve their static files under the `/static/<extension-name>` prefix and their API handlers behind a `/extension/api/<extension-name>` prefix. |
| 131 | + |
| 132 | +For example, the classic notebook server extension will add static handlers that reroute requests to the `/static/notebook/` endpoints. |
| 133 | + |
| 134 | +The jupyter_server will provide a new `JupyterExtensionHandler` base class that reroute requests to the extension's namespaced static and REST API endpoints. |
| 135 | + |
| 136 | +Preliminary experimental work resides in the [`jupyter_server_extension`](https://github.com/Zsailer/jupyter_server_extension) repository. |
| 137 | + |
| 138 | +### Configuration System |
| 139 | + |
| 140 | +The configuration of the server and the legacy notebook are currently tightly coupled in a single NotebookApp configurable. The proposed jupyter server has server-specific configurations in a separate config system from the classic notebook (and jupyterlab) application configurations. |
| 141 | + |
| 142 | +The table below show all the classic notebook traits and where they will live after migration. |
| 143 | + |
| 144 | +**Overview of configuration structure** |
| 145 | + |
| 146 | +The notebook and server configuration both live in the `jupyter_notebook_config.py` file. Extensions are configured in separate files (named after the extension) in the `jupyter_notebook_config.d` directory: |
| 147 | +``` |
| 148 | +~/.jupyter/ |
| 149 | +├── jupyter_notebook_config.py |
| 150 | +└── jupyter_notebook_config.d |
| 151 | + └── my_extension.json |
| 152 | +``` |
| 153 | + |
| 154 | +**New proposed configuration structure** |
| 155 | + |
| 156 | +The jupyter_server configuration lives in the `jupyter_server_config.py` file. Extensios are configured in separate files in the `jupyter_server_config.d` folder. The notebook configuration is stored in a `notebook.py` file, just like other extensions. |
| 157 | +``` |
| 158 | +~/.jupyter/ |
| 159 | +├── jupyter_server_config.py |
| 160 | +└── jupyter_server_config.d |
| 161 | + ├── notebook.py|json |
| 162 | + ├── lab.py|json |
| 163 | + └── my_extension.py|json |
| 164 | +``` |
| 165 | + |
| 166 | +**Migration application** |
| 167 | + |
| 168 | +To make migration easier on users, the jupyter server will include a `migrate` application to split notebook and server configurations into their appropriate locations (listed above). This application will find any `jupyter_notebook_config.py|json` files in `jupyter --paths`, read configured traits, sort server vs. notebook traits, and write them to the appropriate `jupyter_server_config.py|json` and `jupyter_notebook_config.py|json` files. |
| 169 | + |
| 170 | + |
| 171 | +### How this effects other projects |
| 172 | + |
| 173 | +[**Classic notebook**]() |
| 174 | +In short, the classic notebook will become a server extension application. The rest of this proposal describes the details behind what will change in the notebook repo. |
| 175 | +[`JupyterServerExtensionApp`](). |
| 176 | + |
| 177 | +[**Jupyter Lab**]() |
| 178 | +Jupyter lab will also become a server extension application. The new classes described above should simplify the way JupyterLab interfaces with the server. |
| 179 | + |
| 180 | +[**Kernel Gateway**]() |
| 181 | +Kernel Gateway writes custom `kernel` and `kernelmanager` services and load them as server extensions. |
| 182 | + |
| 183 | +[**Kernel Nanny**]() |
| 184 | + |
| 185 | +## Pros and Cons |
| 186 | + |
| 187 | +**Pros** associated with this implementation include |
| 188 | + |
| 189 | +* Allow various frontends to use the backend services of the jupyter server. |
| 190 | +* Provides base classes for extension developers writing server extension applications and handlers. |
| 191 | +* Reduce complexity when extending the server by making everything a server extension. |
| 192 | +* Organizes the configuration for server and extension in a sane and logical manner (`conf.d` approach). |
| 193 | + |
| 194 | +**Cons** associated with this implementation include: |
| 195 | + |
| 196 | +* Break the classic notebook in a backwards incompatible way. |
| 197 | +* Affects many projects. The transition may be painful? |
| 198 | +* Adding a dependency injection system adds new complexity. |
| 199 | + |
| 200 | +## Relevent Issues, PRs, and discussion |
| 201 | + |
| 202 | +Moving to a `conf.d` approach. |
| 203 | +* PR [3116](https://github.com/jupyter/notebook/pull/3116), `jupyter/notebook`: extension config in `config.d` directory. |
| 204 | +* PR [3782](https://github.com/jupyter/notebook/issues/3782), `jupyter/notebook`: server extension use `conf.d` approach |
| 205 | +* PR [2063](https://github.com/jupyter/notebook/issues/2063), `jupyter/notebook`: config merging problems. |
| 206 | + |
| 207 | +Conversation on server/notebook extensions: |
| 208 | +* PR [1706](https://github.com/jupyter/notebook/issues/1706), `jupyter/notebook`: proposal to improve server/notebook extensions |
| 209 | +* PR [2824](https://github.com/jupyter/notebook/issues/2824), `jupyter/notebook`: enable nbextensions by default |
| 210 | + |
| 211 | +Static Namespace: |
| 212 | +* PR [21](https://github.com/jupyter/enhancement-proposals/pull/21#issuecomment-248647152)`jupyter/enhancement-proposals`: mention namespacing. |
| 213 | + |
| 214 | +## Interested |
| 215 | + |
| 216 | +@Zsailer, @SylvainCorlay, @ellisonbg, @blink1073, @kevin-bates |
0 commit comments