Skip to content

Latest commit

 

History

History
183 lines (133 loc) · 6.92 KB

bundler_extensions.rst

File metadata and controls

183 lines (133 loc) · 6.92 KB

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

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 enabling-bundlers.)

# 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:

# 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.

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 bundler-details.

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:

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:

jupyter bundlerextension disable --py mypackage.hello_bundler --sys-prefix

For more help using the bundlerextension subcommand, run the following.

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:

jupyter bundlerextension enable --py notebook.bundler.zip_bundler --sys-prefix
jupyter bundlerextension enable --py notebook.bundler.tarball_bundler --sys-prefix

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