Sharing interactive Jupyter (IPython) Notebooks with Binder

Nikola makes sharing Jupyter (IPython) Notebooks easy. You can render a static page like this and provide a link to download the notebook for interactive usage. However, a major downside with this is that the reader must set up his/her own Python environment which has all the dependencies installed and has to know how to run notebooks. It'd be much simpler if the reader could just click a link and an interactive notebook opened in the browser without him/her needing to do anything else. Well, I just discovered a service which easily enables this: Binder.

In Binder, you provide your GitHub repository and then anyone can use URL http://mybinder.org/repo/GITHUB_USERNAME/GITHUB_REPOSITORY to access the notebooks in that repository interactively. Anyone going to that URL will get a separate isolated environment, so modifying the notebooks won't mess up anything. You can access specific notebooks by appending the path to a notebook to the URL. Simple.

This post is a Jupyter Notebook so you can click "Run Notebook" link in the top menu to run it interactively in Binder. To have some Python code for you to play with, here's a few lines of code:

In [1]:
import numpy as np
np.sqrt(1764)
Out[1]:
42.0

This post gives you instructions on how to add links to Binder into your Nikola powered blog. I assume that you have set up the blogging environment similarly as described in my previous post. But first, go to Binder and build your GitHub repository. I chose requirements.txt do define the dependencies. After building, follow these instructions to add the links to your posts.

Assuming you have created a custom theme for your site, you need to overwrite a few parts in your templates. First, create a template directory for your theme if it doesn't exist:

mkdir themes/YOUR_THEME_NAME/templates

We need to make minor changes to a few template, so we'll copy them and then modify. Copy post.tmpl of bootstrap3-jinja theme and post_header.tmpl of base-jinja theme to your templates directory. These are the files used by bootstrap3-jinja theme (which is the parent theme of my custom theme). You can see and download the full versions of the modified templates from GitHub. For clarity, I show here only the lines that have been modified. In post_header.tmpl:

... (to macro html_sourcelink)
{% if post.compiler.name == "ipynb" %} 
<p class="sourceline"><a href="{{ post.config.BINDER_URL}}/{{ post.config.GITHUB_REPOSITORY }}/{{ post.source_path }}">
Run Notebook
</a></p>
{% endif %}
...

In post.tmpl:

... (to block content)
{% if post.compiler.name == "ipynb" %} 
<br />
<p><em>
This post is a Jupyter Notebook.
You can <a href="{{ post.source_link() }}">download it</a> or
<a href="{{ post.config.BINDER_URL }}/{{ post.config.GITHUB_REPOSITORY }}/{{ post.source_path }}">run it interactively in Binder</a>.
</em></p>
{% endif %}

... (to block sourcelink)
{% if post.compiler.name == "ipynb" %} 
<li>
<a href="{{ post.config.BINDER_URL }}/{{ post.config.GITHUB_REPOSITORY }}/{{ post.source_path }}">
  Run Notebook
</a>
</li>
{% endif %}
...

Also, add the following definitions to conf.py:

GITHUB_REPOSITORY = 'YOUR_GITHUB_USERNAME/REPOSITORY_NAME'
BINDER_URL = 'http://mybinder.org/repo'

Obviously, replace YOUR_GITHUB_USERNAME and REPOSITORY_NAME with your username and repository. Now your Jupyter Notebook blog posts have three links to Binder for the reader to run the notebook interactively.

Building Binder automatically when deploying

One downside of Binder is that it doesn't automatically update on GitHub commits. You have to go to mybinder.org and manually build the repo again. This is understandable, as they told themselves: "rebuilding on every commit for every binder would put too much strain on our poor build server". Big thanks for the poor servers anyway, they're doing an amazing job.

I don't want to put too much strain on Binder servers but I would like to automate the rebuilding process somehow. So I wrote a simple script which posts a build request to Binder (thanks to Paavo Leinonen for helpful comments):

#!/bin/env python

# Automatic form submisson to http://mybinder.org/

import requests

# Add the directory of conf.py into the Python path
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))

from conf import GITHUB_REPOSITORY
from conf import BINDER_API_URL

submission = {
    "dependencies": ["requirements.txt"],
    "services": [],
}

baseurl = 'api.mybinder.org'
url = BINDER_API_URL + '/apps/' + GITHUB_REPOSITORY

r = requests.post(url, json=submission)
print(r)

I named the script bin/binder_deploy (create bin directory first) and made it executable:

chmod +x bin/binder_deploy

In order to have bin directory in your path when in your virtual environment, you can add the following lines to $VIRTUAL_ENV/bin/postactivate:

PROJECT_DIR=`cat $VIRTUAL_ENV/.project`
export PATH=$PROJECT_DIR/bin:$PATH

Now you should have binder_deploy command available after activating your virtual environment with workon blog. In order for the script to work, you need to define the following variable in conf.py:

BINDER_API_URL = 'http://api.mybinder.org'

With this setup, you should run binder_deploy after you have run nikola github_deploy and git push which pushes changes in your Jupyter Notebooks to GitHub. But don't rebuild Binder if not necessary (e.g., no changes in notebooks) in order to avoid putting too much workload on Binder.

Conclusion

If you have ideas on how to improve this setting, please comment below. Also, note that Binder is open-source software, so you can get the source code and run your own Binder server!


This post is a Jupyter Notebook. You can download it or run it interactively in Binder.

Comments

Comments powered by Disqus