JupyterLab App について真面目に考える

# JupyterLab App について真面目に考える
[以前](https://qiita.com/tetutaro/items/53b746412285920180b6)、JupyterLab は複数の（分析）プロジェクトをまたがって使う共通環境なのだから、pipx に入れても良いんじゃないか、という話をした。
その後に [JupyterLab App リリースされたという記事](https://blog.jupyter.org/jupyterlab-desktop-app-now-available-b8b661b17e9a)が出て、ようやく世間が俺に追いついたな、と。（何様）
ある程度 [JupyterLab App](https://github.com/jupyterlab/jupyterlab_app) を触って分かってきたので、JupyterLab App 関連についてまとめたいと思う。

# JupyterLab App とは
[JupyterLab App の GitHub repository](https://github.com/jupyterlab/jupyterlab_app) の Description には "A desktop application for JupyterLab, based on Electron" ってあるけど、これは JupyterLab App の半分しか言っていない。
[上記の記事](https://blog.jupyter.org/jupyterlab-desktop-app-now-available-b8b661b17e9a)では "It is a self-contained desktop application which bundles a Python environment with several popular Python libraries" って言ってて、これは正しい。

すなわち JupyterLab App には何が入っているのかというと、

* Electron で作られた（シンプルな）Web Browser
* JupyterLab が入っている conda ベースの Python 仮想環境（venv）

の２つが入っている。

で、これをインストールした人は、全く Python の環境が無くても、いきなり JupyterLab が専用アプリとして使えてしまう。
これは便利っちゃあ便利。

## 便利な点
* インストールがめちゃ簡単
    * [GitHub repository のトップページ](https://github.com/jupyterlab/jupyterlab_app) にあるリンクをクリックしてインストーラーをダウンロードし、それをダブルクリックしてインストールすれば、いきなりすぐに Jupyter が使える。
* 専用アプリで快適
    * [JupyterLab](https://github.com/jupyterlab/jupyterlab) は、起動するとブラウザが立ち上がり、そのプラウザ上で入力したり結果を見たりするものなので、Jupyter しながら Web で検索するみたいな時には、ちょっと面倒くさかったりする
    * その点、 [JupyterLab App](https://github.com/jupyterlab/jupyterlab_app) は専用アプリ（専用ブラウザ）なので、余計なものが入っていなく、シンプルで快適
    * ブラウザを閉じたら、うっかり JupyterLab も一緒に閉じちゃった、なんてことも無い

## 不便な点
リリースされたばかりなので、これから改善するのかもしれないが、現時点（2021/10/06）では以下の不便な点がある。

* 既存のパッケージや拡張機能で（すぐには）使えないものがある
    * 突然「専用の特別な conda ベースの venv」というものが導入されてしまったので、特に別コマンド・別サーバを叩きに行く拡張機能は、そのままでは使えない
* パッケージを追加することが難しい
    * "Python environment with several popular Python libraries" って言ってるけど、[NumPy](https://numpy.org/) や [Matplotlib](https://matplotlib.org/) すら入っておらず、JupyterLab を動かすための最小限のパッケージしか入っていない
    * だから JupyterLab で普通に分析をしようと思ったら、上記 NumPy, Matplotlib を始め、ほぼすべてを scratch からインストールしなければならない
    * しかし、パッケージを追加する方法は公式には提供されておらず、実際にちょっとメンドイ

特に後者は結構致命的で、[Issues](https://github.com/jupyterlab/jupyterlab_app/issues) を見ても、これ関係が多いように思う。

## JupyterLab App でのパッケージ管理

解決策を示しておく。

JupyterLab App でインストールされる venv は以下の場所にある。

* MacOS X
    * `/Applications/JupyterLab.app/Contents/Resources/jlab_server`
* Linux (Ubuntu)
    * `/opt/JupyterLab/resources/jlab_server`

なので、この `.../jlab_server` の下の `bin/pip` か `bin/conda` を叩いてパッケージのインストールやアンインストール、インストール済パッケージリストの表示をすれば良い。

例）MaxOSX に入れた JupterLab App で、pip を使ってインストール済みパッケージのリストを表示する
`> /Applications/JupyterLab.app/Contents/Resources/jlab_server/bin/pip list`

### 注意点

この時、絶対に注意しなければならないのは、上記 JupyterLab App 内の pip / conda を使ってパッケージをインストール/アンインストールする時は、必ず sudo を付けて、管理者権限でしなければならない、ということである。

そもそも JupyterLab App は、上記の venv の場所を見ても分かる通り、システム領域にインストールされるので、そこにパッケージをインストールする時は管理者権限で行わなければならない。

もしも管理者権限ではなくユーザー権限で pip を叩いた場合は、そのパッケージは `${HOME}/.local` の下にインストールされる。当然、JupyterLab App には認識されず、使えない。
でも管理者権限で pip を叩くと、pip から「管理者権限で pip するのはオススメできないでゴザル」とか言われてしまうので、常になんだかなぁという気持ちになる。
conda も同様。

正解は何なのかは未だに分からない。

# JupterLab on pipx
JupyterLab はインストールが簡単で専用アプリで快適、だけどパッケージ管理がしんどいということが分かった。

では [pipx](https://pypa.github.io/pipx/) に JupyterLab を入れるのはどうか。

同じくらい簡単。

* [pipx](https://pypa.github.io/pipx/) のインストール
    * MacOS の場合
        * `> brew install pipx`
        * `> pipx ensurepath`
    * Linux (Ubuntu) の場合
        * `> /bin/python3 -m pip install --user pipx`
        * `> /bin/python3 -m pipx eusurepath`
* pipx の設定
    * `PATH` 環境変数に `${HOME}/.local/bin` を追加する
* pipx に JupyterLab をインストールする
    * `> pipx install jupyterlab`

これだけ。こっちも簡単じゃない？

こいつを `JupyterLab on pipx` と呼ぶことにする。私にネーミングセンスが無いのは勘弁。

で、JupyterLab App と、JupyterLab on pipx は何がどう違うかというと、まぁ状況はほとんど同じ。

JupyterLab App も、JupyterLab on pipx も、通常の venv とは管理方法が違うので、そのままじゃ動かない拡張機能がある。

ただし、pipx はユーザー領域（`${HOME}/.local/pipx`）に入るので、パッケージを入れたり消したりする罪悪感は無い。JupyterLab 自体を作ったり消したりするのも JupyterLab App よりずっと簡単。

ということで、私は JupyterLab App より JupyterLab on pipx の方を推したい。推しメン。

でも、以下の問題を解決しなきゃならない。

* JupyterLab 専用アプリが無いので、快適じゃない
    * JupyterLab on pipx が JupyterLab App より劣っている点
* JupyerLab の拡張機能が一部動かない
    * JupyterLab on pipx と JupyterLab App で共通する問題点

## JupyterLab on pipx でのパッケージ管理
JupyterLab on pipx の問題点はちょっと脇に置き、まずは JupyterLab on pipx でのパッケージ管理。

以下の２種類の方法がある。

* `pipx inject` を使う
* `pipx runpip` を使う

まずは pipx inject で、pipx が作った JupyterLab 用の venv に pip を入れる。

`> pipx inject jupyterlab pip`

この inject は普通に使えて、ただ入れるだけならドンドコ inject すれば良い。

inject で pip を入れたら、今度は runpip が使える。これはもうまるっきり pip と同じ。

だから SHELL の設定に `alias pipj="pipx runpip jupyterlab"` を追加すると便利じゃないかな。個人的意見。

このようにパッケージ管理が JupyterLab App より簡単でスッキリしてるので、JupyterLab on pipx の方が良いんじゃないかな。個人的意見。

# JupyterLab 専用アプリ
JupyterLab on pipx は、専用アプリが無くて快適じゃないというただ１点で JupyterLab App より劣っているので、何とかこれを解決したい。

一番簡単な解決方法は、Chrome のショートカットを作ること。

普通に `jupyter-lab` （JupyterLab server の起動コマンド）を叩いたらブラウザが立ち上がるので、その Chrome のメニューから「その他ツール」>「ショートカットを作成」と選び、`JupyterLab` という名前で「ウィンドウとして開く」にチェックを入れてショートカットを作れば、あたかも JupyterLab 専用アプリケーションのように使うことが出来る。
（Chrome 以外のブラウザの場合は、知らない（Google 信者））

![chrome_jupyterlab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/badf0961-840a-dc25-7f70-4ddfa7a3190f.png)

これで万事解決な気がする。でも騙されてはいけない。こいつは偽装しているだけで立派な Chrome だ。
左上に「戻る」や「リロード」ボタンが有るし、右上には「拡張機能」ボタンが有る。なので、このアプリ自体が Chrome 拡張機能の影響を受ける（[Keep Last Two Tabs](https://chrome.google.com/webstore/detail/keep-last-two-tabs/fcnmaiiahjldikaollhjobhchdbhfhgf?hl=ja) とか）。まぁそのお蔭で [Stylus](https://add0n.com/stylus.html) を使って JupyterLab の見た目を変えることも出来るんだけど。

もうひとつの方法は、JupyterLab App と同じように、Electron で専用アプリを作ってしまうこと。

ブラウザから JupyterLab を使う時は、ブラウザから `http://localhost:8888/lab` にアクセスすることで JupyterLab を使えるようになる。よって、単純に `http://localhost:8888/lab` にアクセスするだけのブラウザがあれば良い。

同じことを考える人はいるもので、[adammenges/JupyterLabApp](https://github.com/adammenges/JupyterLabApp) は私の欲しい物ずばりそのものだった。

[Electron](https://www.electronjs.org/) は、Electron の簡単なサンプルとして [electron/electron-quick-start](https://github.com/electron/electron-quick-start) というものを提供していて、これは単純に中に含まれている `index.html` を表示するだけのブラウザである。
この中に含まれている HTML ファイルを表示する Electron の関数、`electron.BrowserWindow().loadFIle('index.html')` を、URL を表示する関数である `electron.BrowserWindow().loadURL('http://localhost:8888/lab')`に置き換えれば、私の欲しい物になる。
これをやったのがまさに上記のレポジトリ。

まぁ、Electron は Node.js + Chromium なので、Chrome ショートカットと実質ほぼ同じもの。でも通常のブラウジングに使うアプリと JupyterLab 専用に使うアプリが別になるのは喜ばしいことだ。

・・・これ、俺でも作れるんじゃねぇかな。

## オリジナルの JupyterLab クライアント
ということで、作った。

https://github.com/tetutaro/dotfiles/tree/main/jupyterlab

最初は jupyter って名前を付けるのは怒られるかもしれないなぁと思って、"YapyterLab" とか付けてたけど、もうどうでも良くなったんで開き直って "JupyterLab" ってそのまんまの名前にした。

ちなみにアイコンは、JuputerLab のアイコンはダサくて嫌なので、Jupyter のロゴを使っている。

さらにちなみに、Node.js や yarn の設定をして install.sh を叩くと、build, install から以下に述べている設定まで全部一気にやるようになっている。

しっかし、Electron 超すごい。Node.js を40行も書いてないのに Web アプリが出来ちまった。ほぼ electron-quick-start のパクリだけど。
アイコンを作ったり electron-builder の設定をする方が時間かかったわ。
でも、全部あわせても半日で出来た。

ビルドの仕方、インストールの仕方、使い方は全部 repository の README に書いたから、見てね。

## JupyterLab 設定
まずは Jupyter はどこの設定ファイルを読み込むのか知らなければならない。

Jupyter on pipx の venv は `${HOME}/.local/pipx/venvs/jupyterlab` にあるので、次のようにすると各種パスが分かる。

```
> ~/.local/pipx/venvs/jupyterlab/bin/jupyter --paths
config:
    /Users/[username]/.jupyter
    /Users/[username]/.local/pipx/venvs/jupyterlab/etc/jupyter
    /usr/local/etc/jupyter
    /etc/jupyter
data:
    /Users/[username]/Library/Jupyter
    /Users/[username]/.local/pipx/venvs/jupyterlab/share/jupyter
    /usr/local/share/jupyter
    /usr/share/jupyter
runtime:
    /Users/[username]/Library/Jupyter/runtime
```

他の方法で Jupyter を動かしたくなったら困るので、Jupyter on pipx だけに影響する設定をしたい。ということで、`${HOME}/.local/pipx/venvs/jupyterlab/etc/jupyter/` の下に設定ファイルを置くの一択である。

そして設定をするのだが、JupyterLab のドキュメントはほとんど存在せず、設定の項目を探るのにすら苦労した。
結局どうしたかというと、以下をやってパラメータを色々試し、実現できるまで頑張るしかない。

* `> ~/.local/pipx/venvs/jupyterlab/bin/jupyter lab --generate-config` で生成される `${HOME}/.jupyter_lab_config.py` を読んでウンウン考える
* `> ~/.local/pipx/venvs/jupyterlab/bin/jupyter lab --help` の出力を参考にする
* ググってJupyter Notebook 時代の設定を探し、それに似た名前の設定項目をいじってみる

最終的に、以下の内容のファイルを `${HOME}/.local/pipx/venvs/jupyterlab/etc/jupyter/jupyter_lab_config.py` として置くことで、JupyterLab App と同じように、JupyterLab on pipx + オリジナルJupyterLab を動かすことが出来るようになった。

```python
c.ServerApp.use_redirect_file = False
# For Mac OSX
c.ServerApp.browser = '/Applications/YapyterLab.app/Contents/MacOS/JupyterLab %s'
# For Linux
c.ServerApp.browser = '/snap/bin/jupyterlab %s'
```

`c.ServerApp.browser` は分かるだろう。
ブラウザが JupyterLab にアクセスするためには２種類の方法があって、ひとつが `http://localhost:8888/lab` にトークンを付けてアクセスすること、もうひとつが JupyterLab が生成する runtime 以下の html ファイルにアクセスすること。
デフォルトの挙動は後者（`c.ServerApp.user_redirect_file = True`）。
Mac OSX の場合はどちらでも大丈夫なんだけど、Linux の場合には runtime 以下のファイルへのアクセスが Permission denied されてしまう。良く分からん。
なのでこいつを `False` にして、URL + token でアクセスするようにすれば良い。

# JupyterLab Extension
最近の JupyterLab は、最初からそこそこ機能が付いている（デフォルトで公認の Extension が入っている）ので、そのままでも十分に使える。
でも、どうしてもひとつだけ入れたい Extension がある。

[krassowski/jupyterlab-lsp](https://github.com/krassowski/jupyterlab-lsp) である。

これが出来ることは以下。

* node navigation: 関数を使っている所から関数の定義にジャンブ出来る
* hover suggestion: 関数などをマウスで hover すると、定義がポップアップする
* linter: 文法がおかしい所（PEP8 違反など）の自動チェック
* autocompletion: 補完
* rename: 変数名の一括変換

特に linter と autocompletion は必須。もうこれが無いとコーディングなんて出来ない。

jupyterlab-lsp の他の選択肢としては、[kiteco/jupyterlab-kite](https://github.com/kiteco/jupyterlab-kite) というものが有るらしいが、外部の [Kite Engine](https://www.kite.com/) が必要。
でも俺はあまり AI って信用してないんだ（笑）。
何よりも現時点（2021/10/07）では、Kite Engine をダウンロードしようとすると "Kite is temporarily unavailable" って出て、ダウンロードできない。

ということで、話を進める。

まず JupyterLab Extension を使うには、色んな所で Node.js が必要になるので、まずはこれを入れる。アプリのビルドにも必要だし。
最近は、言語のバージョン管理は env 系を使うことがデファクトスタンダードになりつつあるので、[nodenv](https://github.com/nodenv/nodenv) を使う。（ちょっと前までは [nodebrew](https://github.com/hokaccha/nodebrew) とか色々あった）

* [nodenv](https://github.com/nodenv/nodenv) のインストール
    * `> git clone git://github.com/nodenv/nodenv.git ~/.nodenv`
* [node-build](https://github.com/nodenv/node-build) のインストール
    * ` > git clone https://github.com/nodenv/node-build.git ~/.nodenv/plugins/node-build`
* [node-build-update-defs](https://github.com/nodenv/node-build-update-defs) のインストール
    * `> git clone https://github.com/nodenv/node-build-update-defs.git ~/.nodenv/plugins/node-build-update-defs`
* nodenv の設定（`~.zshrc` とかで）
    * `PATH` に `${HOME}/.nodenv/bin` を加える
    * `eval "$(nodenv init -)"` をする
* Node.js のインストールと設定
    * `> nodenv install --list` 
        * インストールできるバージョンの一覧表示
    * `> nodenv install x.x.x`
        * 特定のバージョンをインストール
    * `> nodenv global x.x.x`
        * どこでもそのバージョンの Node.js を使えるようにする

JupyterLab App の人も、conda で Node.js を入れた方が良いと思う。

`> sudo (path-to-jlab_server)/bin/conda -c conda-forge nodejs`

## jupyterlab-lsp
そしたらいよいよ jupyterlab-lsp のインストール。
jupyterlab-lsp は JupyterLab に上記の機能を提供するガワであり、Python だけじゃなくて色々な言語（の Language Server）に対応している。
だから Python の Language Server である python-lsp-server も入れなければならない。

* `> pipx runpip jupyterlab install jupyterlab-lsp`
* `> pipx runpip jupyterlab install "python-lsp-server[all]"`

python-lsp-server に `[all]` を付けることによって、関連するパッケージ（flake8 とか）を全部いっぺんに入れちゃえる。

JupyterLab App の人は、`pipx runpip jupyterlab install` の代わりに `sudo (path-to-jlab_server)/bin/pip install` ってやるんじゃないかと思う。多分。

余談だが、Python の Language Server としては python-language-server というものもある。しかしこれは開発がストップしていて、これを fork してコミュニティで開発しているのが python-lsp-server らしい。昔は python-language-server しか無かった。老人のつぶやき。

python-language-server から python-lsp-server への乗り換えは、python-language-server の設定の `pyls` となっている所をすべて `pylsp` に置き換えることで可能。

余談が過ぎたが、python-lsp-server, jupyterlab-lsp が無事にインストール出来たからといって、これが動くかというと、ほぼ動くが、ほんのちょっとだけ動かない所がある。

## jupyterlab-lsp の設定
python-lsp-server の設定項目は、[python-lsp-server/CONFIGURATION.md](https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md) に一覧がある。

この中で明らかに設定が必要なのは、`pylsp.plugins.flake8.executable`。

jupyterlab-lsp から python-lsp-server を叩くのは、jupyterlab-lsp が入っている仮想環境の Python モジュールとして叩くので、両者が同じ仮想環境に入っていれば良い。
しかし、flake8 は pylsp のプラグインだけじゃなく普通のコマンドとしても使うので、python-lsp-server はPATH を通じて flake8 を探してしまう。しかし JupyterLab on pipx（もしくは JupyterLab App）の仮想環境という特殊な所に入っているので、flake8 が見つからない、もしくは変な所の flake8 を見てしまう。

よって、flake8 の絶対パスを jupyterlab-lsp の設定として書き、それを python-lsp-server に渡せば良い。
JupyterLab を起動し、メニューから Settings > Advanced Setting Editor を選び、左ペインから Language Server を選んで設定を記入する。

私の設定をサンプルとして示す。

```json
{
  "language_servers": {
    "pylsp": {
      "serverSettings": {
        // disabed services
        "pylsp.plugins.pycodestyle.enabled": false,
        "pylsp.plugins.pyflakes.enabled": false,
        "pylsp.plugins.rope_completion.enabled": false,
        "pylsp.plugins.yapf.enabled": false,
        // flake8
        "pylsp.plugins.flake8.enabled": true,
        "pylsp.plugins.flake8.executable":
          "${HOME}/.local/pipx/venvs/jupyterlab/bin/flake8",
        // ignoring errors
        // E402: allows imports which are not on the very top
        // E703: allows terminating ";" (for matplotlib plots)
        "pylsp.plugins.flake8.ignore": ["E402", "E703"],
        // maxLineLength = 79 (default)
        "pylsp.plugins.flake8.maxLineLength": null
      }
    }
  }
}
```

すると、こんな感じになる。

![yapyterlab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/0607644b-8951-8057-2d6e-4555c8f57356.png)

スクリーンショットじゃ、あんまり違いは分からないね・・・

# まとめ
JupyterLab App を触ってみたけど、あんまり気に入らなかったんで、自分でアプリ作ったり環境作ったりした。

そこそこ良いものになったんじゃないかと思う。

みなさんも宜しければ是非。