🐍

pipでもlockで依存パッケージバージョンを管理しよう

2021/02/15に公開

まとめ

  • 2021 年も pip は良い選択肢。
  • requirements.txtを用意してpip freezerequirements.lockを作成しバージョン管理しませんか。
  • lock するための簡単なスクリプト書いておきました。

lock ファイルによるバージョン管理とは

プログラムを開発する際には外部から誰かが作った既存のライブラリをインストールし、それを利用しながら開発することがほとんどです。
その依存するライブラリは時間の経過とともに開発が進み、バージョンが上がっていくことがありまして、「バージョンの上がった依存ライブラリを気づかず利用し続けることで、そのライブラリが以前とは違う動作をしてしまいそれが問題になる」というようなことがあります。
そのため、「このプログラムではこの依存ライブラリはこのバージョンでないといけない」といったように、依存ライブラリのバージョンを正確に管理することが必要になる場面は多いです。

これは Python だけに限らずさまざまな言語での開発に関しての話なのですが、依存ライブラリのバージョン管理を以下のような方式で行うことが多いです。

  • 依存するライブラリの一覧を記述したファイル(この記事では「依存ライブラリ一覧ファイル」と呼びます)を手書き等で作成する。
  • lock と呼ばれるファイル(この記事では「lock ファイル」と呼びます)を上記ファイルから生成する。

ここで lock ファイルには「依存するライブラリがさらに依存するライブラリ」も含め全てのライブラリ一覧が記載されており、かつ全てのライブラリに関して固定されたバージョン情報が書かれてあります。

この lock ファイルを git リポジトリ内に含めて共有することで、全てのユーザーが同一のバージョンのライブラリを用いてそのプログラムを動作させることができるようになるわけです。

Python におけるパッケージバージョン管理の今

Python においてはパッケージのインストールなどを pip という Python に標準で付属しているツールを用いて行うことが多いです。
しかし、pip を用いて依存ライブラリのバージョンをどのように管理するかということについては、標準化された方法が公式で提唱されているわけでなく、プロジェクトによってまちまちな印象です。

そんな中で、2018 年頃から pipenv という pip に変わる「パッケージインストール・管理ツール」が流行し始めます。
この pipenv を用いれば、lock ファイルを生成しバージョンを正確に管理することもできるので、非常に便利です。

しかし、Python に標準に付属する pip を上回って利用されるまでにはいたらず、さらに poetrypyflow という新参のパッケージインストール・管理ツールも登場しそちらにも人気が集まったため、さらに分断が進みます。
poetry や pyflow では lock ファイル方式での依存ライブラリバージョン管理に加えて、pipenv にはない以下の特徴があったりします。

  • pyproject.toml という従来の setup.py に変わるパッケージ設定ファイルを利用できる(これは PEP518 という公式文章で提案されている公式の方法)
  • プロジェクトをパッケージとしてリリースしたい場合に便利な機能がある

Python のパッケージ管理はやや複雑でどのツールもある程度覚えることがあり、あまり気軽に相互に移行できる感じでないため、どのツールを選ぶかは悩みの種になっているように思います。

Python のパッケージ管理ツールは現在そういった状況にあり将来デファクトスタンダードとなるツールが確定していなさそうなので、2021 年においても(多少機能が足りず、設定方法もわかりにくく、不便であったとしても)圧倒的に利用者の多い pip を利用することが良い場面が多いだろうと筆者は考えています。

pip でも lock ファイルを利用しよう

pip でも(標準的な手順の合意が取れていないだけで)lock ファイルを用いるような方式で管理することはできるのです。
ここからがやっと本題ですが、この記事では私の考える pip を利用しながら、lock ファイル方式を取る良い手順を紹介します。非常に簡単です。

まず、requirements.txt(依存ライブラリ一覧ファイル)を用意します。
ここには実際に import するライブラリを一覧で記述します。
例えば以下のような感じです。

requirements.txt
Django < 3
gunicorn
pynamodb
requests

ここで、Django は v3 以上には上げないように記述しています。
どのようにバージョンを制限できるかは PEP440pip documentation を参考にしてください。
そして以下のようなスクリプトを用意して、実行します。

lock.sh
timestamp=$(date +%s)
Python -m venv .venv_temp_${timestamp}
source .venv_temp_${timestamp}/bin/activate
pip install -U pip wheel
pip install -r requirements.txt
pip freeze >| requirements.lock
rm -rf .venv_temp_${timestamp}

これを行うことで、以下のような requirements.lock が生成されるはずです。

requirements.lock
botocore==1.20.7
certifi==2020.12.5
chardet==4.0.0
Django==2.2.18
gunicorn==20.0.4
idna==2.10
jmespath==0.10.0
pynamodb==5.0.2
Python-dateutil==2.8.1
pytz==2021.1
requests==2.25.1
six==1.15.0
sqlparse==0.4.1
urllib3==1.26.3

requirements.lock を git で管理し、bash lock.sh を定期的に実行することで依存ライブラリに更新があった場合にはそれが更新されるようになります。
bash lock.sh を実行した際には git diff などで、最新のバージョンと今のバージョンの差分を検知し、本当に更新したいかは検証すべきでしょう。
マイクロバージョンの更新はそのまま利用し、メジャー、マイナーバージョンの更新があった場合には注意しながら利用すると良いように思います。
ここで更新をしたくない場合などでは、Django < 3 で行ったようにrequirements.txtを更新することでバージョンに制限を行います。

この方式を用いることで、pip を利用していても他の lock 方式と同様なバージョン管理ができるはずです。

Discussion