自動でvenvを(de)activateする


<!-- 1 Intro -------------------------------------------->

# Introdaction
Pythonで仮想環境作ったはいいけど毎回activate/deactivateするのが面倒臭い。
自動化させましょう。

## 環境
Python3.3 以降
Ubuntu 20.04

## 特徴

- プロジェクトディレクトリに移動した際に、直下に仮想環境があれば、自動でactivateする
- プロジェクトディレクトリの外へ`cd`する際には自動的にdeactivateする
    - 同一プロジェクトディレクトリ内で`cd`してもdeactivateしない
- 仮想環境名は任意に設定できる

※参考にさせていただいたのはこちら[[1−3](#references)]。
こちらのページで示されている方法を実装すればそれで十分だったが、プロジェクトディレクトリ下にあるサブディレクトリに移動してもdeactivateされてしまったり、仮想環境名を固定しなければいけなかったりと、ちょっと自分の使い方では痒所に手が届かなかったので自分好みにカスタマイズした。

<!-- Switcher ---------------------------------------------------->

# 方法
cdを関数化して、cd先に仮想環境があったときにactivateを実行させれば良い。処理の流れとしては、
1. 任意のディレクトリに移動
1. 仮想環境があるか判別
1. 仮想環境がある場合は、仮想環境ディレクトリ下にある`bin/activate`を実行

となる。具体的には、下記のスクリプト`.venvSwitcher.sh`を作成し、`~/.bashrc`で呼び出して実行させる。直接`~/.bashrc`に追記する形でも良いのだが、個人的に`~/.bashrc`が冗長になるのを好まないので、別途作成する。

``` bash:~/.venvSwitcher.sh
#!/bin/bash

cd() {

  builtin cd "$@"

  function activate_venv() {
          if [[ $(ls -a -U1 .venv/ |wc -l) < 1 ]]; then #.venvディレクトリ下に仮想環境なし
                echo "There is no virtual env"
          elif [[ $(ls -a -U1 .venv/ |wc -l) > 1 ]]; then #.venv直下に仮想環境が複数ある
                echo "There are over 2 venv dirs"
                echo "Please activate venv manually"
          else #.venvディレクトリが１つある時
                 source .venv/$(ls .venv/)/bin/activate
          fi
  }


  if [ -z "$VIRTUAL_ENV" ]; then
          if [ -d .venv ]; then
                activate_venv
          fi

  elif [[ $(pwd) != $(dirname $(dirname "$VIRTUAL_ENV"))/* ]] ; then
          deactivate

          if [ -d .venv ]; then
                activate_venv
          fi
  fi

}

```

## 解説

### 仮想環境名について

細かい処理の解説の前に、ちょっとこだわりたいポイントとして仮想環境名`hoge`について検討する。
”cd先に仮想環境があれば、そのディレクトリ下の`/bin/activate`を実行”という挙動を実装する際に以下の懸念点が生じる

1. **仮想環境名が分からない**
上記Step2での仮想環境の有無チェックにおいて、その環境名が分からないことには判別しようがないのである。`cd directory`に追加で仮想環境名を変数として引き渡すとなると余計な手間が増えて面倒だし、そもそも仮想環境名を覚えておく（事前に確認しておく）必要があるので本当にだるい。cd先の、つまりプロジェクトのディレクトリ名と同じ仮想環境名にすることで対応可能なのだが、任意の名前が付けられないと困ることも多い（バージョン切り替えのために複数の環境が用意されていたりなど）

1. **仮想環境名を定数にするために共通の名前にする”ことは避けたい**
仮想環境をactivateすると、`(.venv)username@PCname:~$`のようにプロンプトの頭に環境名が表示されるわけだが、環境名が共通では"どの仮想環境かは分からないけれど、activateされている状態”というインジケータになってしまう。全自動でのactivateが上手く機能しているのであれば別に気にすることでもないのだが、例えば同じプロジェクト内で仮想環境を切り替えたいという場合には違いがわかった方が良い。

そのために以下の方針を採用する
* 目標：何も考えずに気がついたらactivateしてくれている、そんな素敵な環境を作る。
* 方法：`.venv`ディレクトリ下に、仮想環境を作る

自作の`.venv`ディレクトリ（プロジェクトに関わらず共通名）を一回噛ませ、任意の仮想環境名はlsで取得するという作戦。”仮想環境は.venv直下にあるという”前提条件を作ることで、仮想環境名に関わらず一意に仮想環境を指定できるようにする。

### スクリプトについて

* **builin**
`builtin`を使うことで、自作cd関数のなかで優先的にビルトインコマンドのcdを実行させる。その後にactivation周りの処理を組んでいくことになる

* **activate_venv function**
本スクリプトでは、`.venv`ディレクトリ直下にあるディレクトリに応じた処理をさせる。
1. `.venv/`にディレクトリがないとき：環境がないと警告
1. `.venv/`に仮想環境ディレクトリが複数あるとき：手動でactivateするように警告
1. `.venv/`に仮想環境が１つあるとき：activation

`.venv`下に仮想環境が複数ある場合は警告を出して手動でのactivateを促しているが、キーボード入力を利用してどれをactivateさせるかを選ばせても良いかもしれない

* **if [ -z "$VIRTUAL_ENV" ]; then**
仮想環境がactivateされると、環境変数`VIRTUAL_ENV`が定義される。これを利用し、まだ環境変数`VIRTUAL_ENV`が未定義（文字列長が0）であれば`activate_venv`を実行するという場合わけをする。

* __elif [[ $(pwd) != $(dirname $(dirname "$VIRTUAL_ENV"))/* ]] ; then__
cd先のディレクトリ(pwd)が、現在の仮想環境がある親ディレクトリとは異なる場合、一度deactiavteしてから`activate_venv`を実行する。これにより、仮想環境がある`.venv`の親ディレクトリから出た際にはdeactivateがなされ、かつ移動先のディレクトリにまた`.venv`ディレクトリがあればactivationがなされる。

<!-- 5 Ref ---------------------------------------------------->
# References
1. [cd したら ls する ー Qiita](https://n2kia4.hatenablog.com/entry/20170217/1487333599)
2. [Python の venv を自動で有効(無効)化させたい ー Qiita](https://qiita.com/koshigoe/items/15351b1b4d137d47ca00)
2. [(Python3)venvのactivateとdeactivateを自動化する](https://qiita.com/b4b4r07/items/8cf5d1c8b3fbfcf01a5d)