1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
Custom bundler extensions
=========================
The notebook server supports the writing of *bundler extensions* that
transform, package, and download/deploy notebook files. As a developer, you
need only write a single Python function to implement a bundler. The notebook
server automatically generates a *File -> Download as* or *File -> Deploy as*
menu item in the notebook front-end to trigger your bundler.
Here are some examples of what you can implement using bundler extensions:
* Convert a notebook file to a HTML document and publish it as a post on a
blog site
* Create a snapshot of the current notebook environment and bundle that
definition plus notebook into a zip download
* Deploy a notebook as a standalone, interactive `dashboard <https://github.com/jupyter-incubator/dashboards_bundlers>`_
To implement a bundler extension, you must do all of the following:
* Declare bundler extension metadata in your Python package
* Write a `bundle` function that responds to bundle requests
* Instruct your users on how to enable/disable your bundler extension
The following sections describe these steps in detail.
Declaring bundler metadata
--------------------------
You must provide information about the bundler extension(s) your package
provides by implementing a `_jupyter_bundlerextensions_paths` function. This
function can reside anywhere in your package so long as it can be imported
when enabling the bundler extension. (See :ref:`enabling-bundlers`.)
.. code:: python
# in mypackage.hello_bundler
def _jupyter_bundlerextension_paths():
"""Example "hello world" bundler extension"""
return [{
'name': 'hello_bundler', # unique bundler name
'label': 'Hello Bundler', # human-readable menu item label
'module_name': 'mypackage.hello_bundler', # module containing bundle()
'group': 'deploy' # group under 'deploy' or 'download' menu
}]
Note that the return value is a list. By returning multiple dictionaries in
the list, you allow users to enable/disable sets of bundlers all at once.
Writing the `bundle` function
-----------------------------
At runtime, a menu item with the given label appears either in the
*File -> Deploy as* or *File -> Download as* menu depending on the `group`
value in your metadata. When a user clicks the menu item, a new browser tab
opens and notebook server invokes a `bundle` function in the `module_name`
specified in the metadata.
You must implement a `bundle` function that matches the signature of the
following example:
.. code:: python
# in mypackage.hello_bundler
def bundle(handler, model):
"""Transform, convert, bundle, etc. the notebook referenced by the given
model.
Then issue a Tornado web response using the `handler` to redirect
the user's browser, download a file, show a HTML page, etc. This function
must finish the handler response before returning either explicitly or by
raising an exception.
Parameters
----------
handler : tornado.web.RequestHandler
Handler that serviced the bundle request
model : dict
Notebook model from the configured ContentManager
"""
handler.finish('I bundled {}!'.format(model['path']))
Your `bundle` function is free to do whatever it wants with the request and
respond in any manner. For example, it may read additional query parameters
from the request, issue a redirect to another site, run a local process (e.g.,
`nbconvert`), make a HTTP request to another service, etc.
The caller of the `bundle` function is `@tornado.gen.coroutine` decorated and
wraps its call with `torando.gen.maybe_future`. This behavior means you may
handle the web request synchronously, as in the example above, or
asynchronously using `@tornado.gen.coroutine` and `yield`, as in the example
below.
.. code:: python
from tornado import gen
@gen.coroutine
def bundle(handler, model):
# simulate a long running IO op (e.g., deploying to a remote host)
yield gen.sleep(10)
# now respond
handler.finish('I spent 10 seconds bundling {}!'.format(model['path']))
You should prefer the second, asynchronous approach when your bundle operation
is long-running and would otherwise block the notebook server main loop if
handled synchronously.
For more details about the data flow from menu item click to bundle function
invocation, see :ref:`bundler-details`.
.. _enabling-bundlers:
Enabling/disabling bundler extensions
-------------------------------------
The notebook server includes a command line interface (CLI) for enabling and
disabling bundler extensions.
You should document the basic commands for enabling and disabling your
bundler. One possible command for enabling the `hello_bundler` example is the
following:
.. code:: bash
jupyter bundlerextension enable --py mypackage.hello_bundler --sys-prefix
The above updates the notebook configuration file in the current
conda/virtualenv environment (`--sys-prefix`) with the metadata returned by
the `mypackage.hellow_bundler._jupyter_bundlerextension_paths` function.
The corresponding command to later disable the bundler extension is the
following:
.. code:: bash
jupyter bundlerextension disable --py mypackage.hello_bundler --sys-prefix
For more help using the `bundlerextension` subcommand, run the following.
.. code:: bash
jupyter bundlerextension --help
The output describes options for listing enabled bundlers, configuring
bundlers for single users, configuring bundlers system-wide, etc.
Example: IPython Notebook bundle (.zip)
---------------------------------------
The `hello_bundler` example in this documentation is simplistic in the name
of brevity. For more meaningful examples, see
`notebook/bundler/zip_bundler.py` and `notebook/bundler/tarball_bundler.py`.
You can enable them to try them like so:
.. code:: bash
jupyter bundlerextension enable --py notebook.bundler.zip_bundler --sys-prefix
jupyter bundlerextension enable --py notebook.bundler.tarball_bundler --sys-prefix
.. _bundler-details:
Bundler invocation details
--------------------------
Support for bundler extensions comes from Python modules in `notebook/bundler`
and JavaScript in `notebook/static/notebook/js/menubar.js`. The flow of data
between the various components proceeds roughly as follows:
1. User opens a notebook document
2. Notebook front-end JavaScript loads notebook configuration
3. Bundler front-end JS creates menu items for all bundler extensions in the
config
4. User clicks a bundler menu item
5. JS click handler opens a new browser window/tab to
`<notebook base_url>/bundle/<path/to/notebook>?bundler=<name>` (i.e., a
HTTP GET request)
6. Bundle handler validates the notebook path and bundler `name`
7. Bundle handler delegates the request to the `bundle` function in the
bundler's `module_name`
8. `bundle` function finishes the HTTP request
|