|
| 1 | +========================= |
| 2 | +Jupyter Server Extensions |
| 3 | +========================= |
| 4 | + |
| 5 | +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. |
| 6 | + |
1 | 7 | Authoring a basic server extension
|
2 |
| -================================== |
| 8 | +================================== |
| 9 | + |
| 10 | +The simplest way to write a Jupyter Server extension is to write an extension module with a ``_load_jupyter_server_extension`` function. This function should take a single argument, an instance of the ``ServerApp``. |
| 11 | + |
| 12 | + |
| 13 | +.. code-block:: python |
| 14 | +
|
| 15 | + def _load_jupyter_server_extension(serverapp): |
| 16 | + """ |
| 17 | + This function is called when the extension is loaded. |
| 18 | + """ |
| 19 | + pass |
| 20 | +
|
| 21 | +
|
| 22 | +Adding extension endpoints |
| 23 | +-------------------------- |
| 24 | + |
| 25 | +The easiest way to add endpoints and handle incoming requests is to subclass the ``JupyterHandler`` (which itself is a subclass of Tornado's ``RequestHandler``). |
| 26 | + |
| 27 | +.. code-block:: python |
| 28 | +
|
| 29 | + from jupyter_server.base.handlers import JupyterHandler |
| 30 | +
|
| 31 | + class MyExtensionHandler(JupyterHandler): |
| 32 | +
|
| 33 | + def get(self): |
| 34 | + ... |
| 35 | +
|
| 36 | + def post(self): |
| 37 | + ... |
| 38 | +
|
| 39 | +
|
| 40 | +Then add this handler to Jupyter Server's Web Application through the ``_load_jupyter_server_extension`` function. |
| 41 | + |
| 42 | +.. code-block:: python |
| 43 | +
|
| 44 | + def _load_jupyter_server_extension(serverapp): |
| 45 | + """ |
| 46 | + This function is called when the extension is loaded. |
| 47 | + """ |
| 48 | + handlers = [ |
| 49 | + ('/myextension/hello', MyExtensionHandler) |
| 50 | + ] |
| 51 | + serverapp.web_app.add_handlers('.*$', handlers) |
| 52 | +
|
| 53 | +
|
| 54 | +
|
| 55 | +Making an extension discoverable |
| 56 | +-------------------------------- |
| 57 | + |
| 58 | +To make this extension discoverable to Jupyter Server, there are two steps. First, the extension module must define a ``_jupyter_server_extension_paths()`` function that returns some metadata about the extension entry-points in the module. This informs Jupyter Server what type of extension is being loaded and where to find the ``_load_jupyter_server_extension``. |
| 59 | + |
| 60 | +.. code-block:: python |
| 61 | +
|
| 62 | + def _jupyter_server_extension_paths(): |
| 63 | + """ |
| 64 | + Returns a list of dictionaries with metadata describing |
| 65 | + where to find the `_load_jupyter_server_extension` function. |
| 66 | + """ |
| 67 | + return [ |
| 68 | + { |
| 69 | + "module": "my_extension" |
| 70 | + } |
| 71 | + ] |
| 72 | +
|
| 73 | +Second, the extension must be listed in the user’s ``jpserver_extensions`` config trait. This can be manually added by users in their ``jupyter_server_config.py`` file: |
| 74 | + |
| 75 | +.. code-block:: python |
| 76 | +
|
| 77 | + c.ServerApp.jpserver_extensions = { |
| 78 | + "my_extension": True |
| 79 | + } |
| 80 | +
|
| 81 | +Alternatively, an extension can automatically enable itself by creating the following JSON in the jupyter_server_config.d directory on installation. See XX for more details. |
| 82 | + |
| 83 | +.. code-block:: python |
| 84 | +
|
| 85 | + { |
| 86 | + "ServerApp": { |
| 87 | + "jpserver_extensions": { |
| 88 | + "my_extension": true |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | +
|
| 93 | +
|
| 94 | +Authoring a configurable extension application |
| 95 | +============================================== |
| 96 | + |
| 97 | +Some extensions are full-fledged client applications that sit on top of the Jupyter Server. For example, `JupyterLab <https://jupyterlab.readthedocs.io/en/stable/>`_ is a server extension. It can be launched from the command line, configured by CLI or config files, and serves+loads static assets behind the server (i.e. html templates, Javascript, etc.) |
| 98 | + |
| 99 | +Jupyter Server offers a convenient base class, ``ExtensionsApp``, that handles most of the boilerplate code for building such extensions. |
| 100 | + |
| 101 | +Anatomy of an ``ExtensionApp`` |
| 102 | +------------------------------ |
| 103 | + |
| 104 | +An ExtensionApp: |
| 105 | + |
| 106 | + - has traits. |
| 107 | + - is configurable (from file or CLI) |
| 108 | + - has a name (see the ``extension_name`` trait). |
| 109 | + - has an entrypoint, ``jupyter <extension_name>``. |
| 110 | + - can server static content from the ``/static/<extension_name>/`` endpoint. |
| 111 | + - can add new endpoints to the Jupyter Server. |
| 112 | + |
| 113 | +The basic structure of an ExtensionApp is shown below: |
| 114 | + |
| 115 | +.. code-block:: python |
| 116 | +
|
| 117 | + from jupyter_server.extension.application import ExtensionApp |
| 118 | +
|
| 119 | +
|
| 120 | + class MyExtensionApp(ExtensionApp): |
| 121 | +
|
| 122 | + # -------------- Required traits -------------- |
| 123 | + extension_name = "myextension" |
| 124 | + extension_url = "/myextension" |
| 125 | + load_other_extensions = True |
| 126 | +
|
| 127 | + # --- ExtensionApp traits you can configure --- |
| 128 | + static_paths = [...] |
| 129 | + template_paths = [...] |
| 130 | + settings = {...} |
| 131 | + handlers = [...] |
| 132 | +
|
| 133 | + # ----------- add custom traits below --------- |
| 134 | + ... |
| 135 | +
|
| 136 | + def initialize_settings(self): |
| 137 | + ... |
| 138 | + # Update the self.settings trait to pass extra |
| 139 | + # settings to the underlying Tornado Web Application. |
| 140 | + self.settings.update({'<trait>':...}) |
| 141 | +
|
| 142 | + def initialize_handlers(self): |
| 143 | + ... |
| 144 | + # Extend the self.handlers trait |
| 145 | + self.handlers.extend(...) |
| 146 | +
|
| 147 | + def initialize_templates(self): |
| 148 | + ... |
| 149 | + # Change the jinja templating environment |
| 150 | +
|
| 151 | +
|
| 152 | +The ``ExtensionApp`` uses the following methods and properties to connect your extension to the Jupyter server. You do no need to define a ``_load_jupyter_server_extension`` function for these apps. Instead, overwrite the pieces below to add your custom settings, handlers and templates: |
| 153 | + |
| 154 | +Methods |
| 155 | + |
| 156 | +* ``initialize_setting()``: adds custom settings to the Tornado Web Application. |
| 157 | +* ``initialize_handlers()``: appends handlers to the Tornado Web Application. |
| 158 | +* ``initialize_templates()``: initialize the templating engine (e.g. jinja2) for your frontend. |
| 159 | + |
| 160 | +Properties |
| 161 | + |
| 162 | +* ``extension_name``: the name of the extension |
| 163 | +* ``extension_url``: the default url for this extension—i.e. the landing page for this extension when launched from the CLI. |
| 164 | +* ``load_other_extensions``: a boolean enabling/disabling other extensions when launching this extension directly. |
| 165 | + |
| 166 | +``ExtensionApp`` request handlers |
| 167 | +--------------------------------- |
| 168 | + |
| 169 | +``ExtensionApp`` Request Handlers have a few extra properties. |
| 170 | + |
| 171 | +* ``config``: the ExtensionApp's config object. |
| 172 | +* ``server_config``: the ServerApp's config object. |
| 173 | +* ``extension_name``: the name of the extension to which this handler is linked. |
| 174 | +* ``static_url()``: a method that returns the url to static files (prefixed with ``/static/<extension_name>``). |
| 175 | + |
| 176 | +Jupyter Server provides a convenient mixin class for adding these properties to any ``JupyterHandler``. For example, the basic server extension handler in the section above becomes: |
| 177 | + |
| 178 | +.. code-block:: python |
| 179 | +
|
| 180 | + from jupyter_server.base.handlers import JupyterHandler |
| 181 | + from jupyter_server.extension.handler import ExtensionHandlerMixin |
| 182 | +
|
| 183 | +
|
| 184 | + class MyExtensionHandler(ExtensionHandlerMixin, JupyterHandler): |
| 185 | +
|
| 186 | + def get(self): |
| 187 | + ... |
| 188 | +
|
| 189 | + def post(self): |
| 190 | + ... |
| 191 | +
|
| 192 | +
|
| 193 | +Jinja templating from frontend extensions |
| 194 | +----------------------------------------- |
| 195 | + |
| 196 | +Many Jupyter frontend applications use Jinja for basic HTML templating. Since this is common enough, Jupyter Server provides some extra mixin that integrate Jinja with Jupyter server extensions. |
| 197 | + |
| 198 | +Use ``ExtensionAppJinjaMixin`` to automatically add a Jinja templating environment to an ``ExtensionApp``. This adds a ``<extension_name>_jinja2_env`` setting to Tornado Web Server's settings that be be used by request handlers. |
| 199 | + |
| 200 | +.. code-block:: python |
| 201 | +
|
| 202 | +
|
| 203 | + from jupyter_server.extension.application import ExtensionApp, ExtensionAppJinjaMixin |
| 204 | +
|
| 205 | +
|
| 206 | + class MyExtensionApp(ExtensionAppJinjaMixin, ExtensionApp): |
| 207 | + ... |
| 208 | +
|
| 209 | +
|
| 210 | +Pair the example above with ``ExtensionHandlers`` that also inherit the ``ExtensionHandlerJinjaMixin`` mixin. This will automatically load HTML templates from the Jinja templating environment created by the ``ExtensionApp``. |
| 211 | + |
| 212 | + |
| 213 | +.. code-block:: python |
| 214 | +
|
| 215 | +
|
| 216 | + from jupyter_server.base.handlers import JupyterHandler |
| 217 | + from jupyter_server.extension.handler import ( |
| 218 | + ExtensionHandlerMixin, |
| 219 | + ExtensionHandlerJinjaMixin |
| 220 | + ) |
| 221 | +
|
| 222 | + class MyExtensionHandler( |
| 223 | + ExtensionHandlerMixin, |
| 224 | + ExtensionHandlerJinjaMixin, |
| 225 | + JupyterHandler |
| 226 | + ): |
| 227 | +
|
| 228 | + def get(self): |
| 229 | + ... |
| 230 | +
|
| 231 | + def post(self): |
| 232 | + ... |
| 233 | +
|
| 234 | +
|
| 235 | +.. note:: The mixin classes in this example must come before the base classes, ``ExtensionApp`` and ``ExtensionHandler``. |
| 236 | + |
| 237 | + |
| 238 | +Making an ``ExtensionApp`` discoverable |
| 239 | +--------------------------------------- |
| 240 | + |
| 241 | +To make an ``ExtensionApp`` discoverable by Jupyter Server, add the ``app`` key+value pair to the ``_jupyter_server_extension_paths()`` function example above: |
| 242 | + |
| 243 | +.. code-block:: python |
| 244 | +
|
| 245 | + from myextension import MyExtensionApp |
| 246 | +
|
| 247 | +
|
| 248 | + def _jupyter_server_extension_paths(): |
| 249 | + """ |
| 250 | + Returns a list of dictionaries with metadata describing |
| 251 | + where to find the `_load_jupyter_server_extension` function. |
| 252 | + """ |
| 253 | + return [ |
| 254 | + { |
| 255 | + "module": "myextension", |
| 256 | + "app": MyExtensionApp |
| 257 | + } |
| 258 | + ] |
| 259 | +
|
| 260 | +
|
| 261 | +Launching an ``ExtensionApp`` |
| 262 | +----------------------------- |
| 263 | + |
| 264 | +To launch the application, simply call the ``ExtensionApp``'s ``launch_instance`` method. |
| 265 | + |
| 266 | +.. code-block:: python |
| 267 | +
|
| 268 | + launch_instance = MyFrontend.launch_instance |
| 269 | + launch_instance() |
| 270 | +
|
| 271 | +
|
| 272 | +To make your extension executable from anywhere on your system, point an entry-point at the ``launch_instance`` method in the extension's ``setup.py``: |
| 273 | + |
| 274 | +.. code-block:: python |
| 275 | +
|
| 276 | + from setuptools import setup |
| 277 | +
|
| 278 | +
|
| 279 | + setup( |
| 280 | + name='myfrontend', |
| 281 | + ... |
| 282 | + entry_points={ |
| 283 | + 'console_scripts': [ |
| 284 | + 'jupyter-myextension = myextension:launch_instance' |
| 285 | + ] |
| 286 | + } |
| 287 | + ) |
| 288 | +
|
| 289 | +Distributing a server extension |
| 290 | +=============================== |
| 291 | + |
| 292 | + |
| 293 | + |
| 294 | +Example Server Extension |
| 295 | +======================== |
| 296 | + |
| 297 | +You can check some simple example on the `GitHub jupyter_server repository |
| 298 | +<https://github.com/jupyter/jupyter_server/tree/master/examples/simple>`_. |
0 commit comments