File: bundler_extensions.rst

package info (click to toggle)
jupyter-notebook 6.4.13-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 13,860 kB
  • sloc: javascript: 20,765; python: 15,658; makefile: 255; sh: 160
file content (183 lines) | stat: -rw-r--r-- 7,085 bytes parent folder | download | duplicates (3)
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