各種Jupyter Notebook Extensionの作り方

# はじめに

この記事は[jupyter notebook Advent Calender 2016](http://qiita.com/advent-calendar/2016/jupyter) 19日目の記事です。

## Jupyter Notebook Extensionとは

その名の通り、Jupyter Notebookの拡張です。Jupyter Notebookの拡張というと、[nbextensions](https://github.com/ipython-contrib/jupyter_contrib_nbextensions)という様々な拡張を含んだパッケージが有名かと思います。これをインストールすると色々な便利機能が使えるようになりますが、それでもまだもの足りなかったり、こういう機能が欲しい！というのが出てくることがあります。そこで、本記事では独自の拡張を作成する方法について述べます。

## Extensionsの種類

Jupyter Notebookには、3種類のExtensionsがあります。

* front end extension
* server extension
* bundle extension

(あ、あとキーマップを変えるやつとかがあった気がしますがそちらについては省略します。すいません。)

上二つは名前の通りです。フロントエンド、すなわちブラウザ側で動くコードの拡張、そして、サーバー側で動く拡張です。最後の１つについては後程説明します。

# Hello Extensions!

さて、さっそく作っていきたいですが、まずは環境構築から始めましょう。Pythonのバージョンは3.5.2を想定しています。

```bash
pip install --upgrade pip
pip install notebook
pip install jupyter_contrib_nbextensions
jupyter contrib nbextension install --user
jupyter notebook --generate-config
```

こうすると、ホームディレクトリに.jupyterというディレクトリが作成されます。virtualenvで作成した環境でやってもホームディレクトリに作成されて悲しい。どなたか正しい方法を知っていたら教えてください...。

## front end extension

front end extensionは、Anacondaやvirtualenvで作成したPython環境のディレクトリのshare/jupyter/nbextensionsに配置します。例えば、virtualenvで作成したPython環境は、~/.pyenv/versions/(ここにPython環境名)/share/jupyter/nbextensionsです。jupyter/nbextensionsがない場合は作成してください。

では、拡張を作成しましょう。ここではHelloFrontEndExtという名前で作ります。次のような構成のディレクトリを上に書いたディレクトリに配置します。

- HelloFrontEndExt
    - readme.md
    - main.js
    - HelloFrontEndExt.yaml
    - icon.png

readme.mdには説明を書きます。まあ要するに普通のreadmeです。これはJupyter Notebook上で表示されるので何かしら書いておくとよいでしょう。
yamlファイルは次のように記述します。

```yaml:HelloFrontEndExt.yaml
Type: IPython Notebook Extension
Name: Hello Front End Ext
Description: This is my first front-end extension
Link: readme.md
Icon: icon.png
Main: main.js
```

さて、拡張のスクリプトを書いていきましょう。Jupyter NotebookのフロントエンドのコードではCommonJSではなくAMDが採用されています。なので、この拡張もAMDモジュールとして書くことになります。main.jsを以下のように記述してください。

```js:main.js
define([
    'base/js/namespace',
    ],
    function (Jupyter) {
        "use strict";
        function hello() {
            console.log('Hello');
        }
        Jupyter.toolbar.add_buttons_group([{
            id: 'hello',
            label: 'say hello',
            callback: hello
        }]);

        return {
            load_jupyter_extension: function () { console.log('My Ext is loaded!'); }
        };
    }
);
```
base/js/namespaceをJupyterという名前でインポートすると、いろいろできるようになります。この拡張では、ツールバーにsay helloという名前の、押すとコンソールに"Hello"と表示するボタンを配置します。また、起動時にload_jupyter_extensionで指定した関数が呼ばれます。

では、実行して確認してみましょう。jupyter notebookを起動したら、Nbextensionsというタブがあるのでそれを開いてください。

![nbextensions_screen_shot.png](https://qiita-image-store.s3.amazonaws.com/0/90240/7fbcb24c-bdc3-e148-61aa-3a757d558818.png)

画像のように、右側にHelloFrontEndExtというのがあると思いますが、これはチェックが入れられない状態になっています。Configurable nbextensionsと書いてあるところの下のチェックボックスのチェックを外すと、自作のExtensionも選択できるようになります。HelloFrontEndExtのチェックを入れたら、適当に新しいノートブックを作成して動作を確認しましょう。

![test.png](https://qiita-image-store.s3.amazonaws.com/0/90240/bb3e0940-8495-c35f-c7e1-a4e2d447ef89.png)

警告マークの怪しいボタンがツールバーにできましたが、これが先ほど追加したものです。開発者ツールのコンソールを開いた状態でボタンを押すと、コンソールにHelloと表示されると思います。

これでfront end extensionは完成です。このやり方だとやはり面倒くさいですが、僕はこれしか知らないので誰か教えてください...。あと、他にもいろいろでいるのですが、何ができるのかは、[ソースコードのここ](https://github.com/jupyter/notebook/tree/master/notebook)や、[ドキュメントのここ](https://jupyter-notebook.readthedocs.io/en/latest/extending/frontend_extensions.html)を参照してください。

## server extension

server extensionは、Python環境のディレクトリのlibフォルダなどに入っている、site-packages/IPython/extensionsに配置します。server extensionは単一のPythonファイルだけでokです。では、早速作りましょう。hello_server_ext.pyという名前で作ります。

```py3:hello_server_ext.py
from notebook.utils import url_path_join
from notebook.base.handlers import IPythonHandler

class HelloHandler(IPythonHandler):
    def get(self):
        self.finish('Hello')

def load_jupyter_server_extension(nbapp):
    web_app = nbapp.web_app
    host_pattern = '.*$'
    route_pattern = url_path_join(web_app.settings['base_url'], '/hello')
    web_app.add_handlers(host_pattern, [(route_pattern, HelloHandler)])
```
(コードが横長ですいません。)
この拡張は、/helloに対してリクエストがあったらHelloとレスポンスを返すということをします。また、load_jupyter_server_extension(nbapp)は、jupyterの起動時に呼ばれます。
では、動作確認...とその前に、一つ設定が必要になります。~/.jupyter/jupyter_notebook_config.jsonを編集して、今作成した拡張を登録します。(本当はコマンドでできると思うのですが、うまくいかなかったので、今回は手動でファイルを編集しています。)

```json:jupyter_notebook_config.json
{
  "NotebookApp": {
    "nbserver_extensions": {
      "jupyter_nbextensions_configurator": true,
      "hello_server_ext":true
    }
  }
}
```

"hello_server_ext":trueというところが書き足したところです。では、jupyterを起動して動作を確認しましょう。
今回は/helloに対してリクエストを送ればよいので、ブラウザで http://localhost:8888/hello にアクセスしてみます。

![スクリーンショット 2016-12-19 23.54.47.png](https://qiita-image-store.s3.amazonaws.com/0/90240/a881fa37-dd3e-0ff3-f7dd-bf13f62afd1f.png)

できました。
ちなみに、JupyterはTornadoというパッケージを用いてWebサーバを構築しており、IPythonHandlerというクラスもTornadoのサーバーを継承したものです。この辺りを調べるといろいろできることがわかるようになると思います。こちらについても[ソースコードのここ](https://github.com/jupyter/notebook/tree/master/notebook) が参考になると思います。

## bandler extension

ちょっと記事が遅刻しちゃったので今回は割愛させていただきます。
ざっくり説明すると、File->Download asとかを拡張できるもので、現在開発中のJupyter Notebook 5.0.0devから追加される機能です。ipynbファイルを別の形式に直してからダウンロードするという拡張を作る時は、今後はこれを使うことになるでしょう。(私はfront endとserverの方を書いてごり押ししましたが...。)

# おわりに

さて、かなり無理やりだと思われた方も多いと思います。多分もっといい方法があるはずなので、また何かわかったら書いていこうと思います。また、何かわかった方はどんどん書いてください、本当にお願いします。

また、この記事は急いで書いたので結構不備があるかと思います。おかしいところなどがあればぜひコメントでご指摘ください。
そして遅刻してすいませんでした！！