仮想環境とパッケージのバージョン管理
この演習では、個数の表示を作るlbox-reportと、その内部で使うlbox-formatを扱います。教材用の二つの配布一覧から仮想環境を作り、選ばれた版と表示を比較した後、依存先も含めて版を固定します。
説明
プログラムが使う外部パッケージは、それ自体が別のパッケージを必要とすることがあります。直接指定した版が同じでも、その先の依存に許容範囲があると、環境を作った時点で選ばれる版が変わる場合があります。
この演習では、個数の表示を作るlbox-reportと、その内部で使うlbox-formatを扱います。教材用の二つの配布一覧から仮想環境を作り、選ばれた版と表示を比較した後、依存先も含めて版を固定します。
1直接指定したパッケージ
requirements.txtにはlbox-reportの版を指定しています。lbox-formatの指定は、このファイルにはありません。
cat requirements.txt直接インストールするパッケージの指定==1.0.0はlbox-reportの版を厳密に指定します。ここだけを見ても、lbox-reportが必要とする別パッケージの版までは分かりません。
2配布一覧と依存の条件
教材用パッケージのwheelを用意します。oldにはlbox-format 1.0.0だけ、newには1.1.0と2.0.0も含まれます。lbox-reportの依存条件はどちらも同じです。
python3 make_wheels.py教材の配布ファイルを用意
python3 -配布一覧とメタデータを表示 <<'PYCODE'
from pathlib import Path
from zipfile import ZipFilewheel内のファイルを参照
for folder in ("old", "new"):
print(folder, sorted(p.name for p in (Path("wheelhouse") / folder).glob("*.whl")))
with ZipFile("wheelhouse/new/lbox_report-1.0.0-py3-none-any.whl") as wheel:
text = wheel.read("lbox_report-1.0.0.dist-info/METADATA").decode()
print(next(line for line in text.splitlines() if line.startswith("Requires-Dist:依存先の条件")))
PYCODERequires-Distの>=1,<2は1以上かつ2未満を許可します。1.0.0と1.1.0は範囲内ですが、2.0.0は範囲外です。配布パッケージのlbox-reportという名前と、Pythonでimportするlbox_reportという名前は表記が異なります。
3古い配布一覧から環境を作る
env-aという仮想環境を作り、oldの一覧だけを使ってインストールします。-qは通常の進行表示を省きます。
python3 -m venv env-a独立した環境を作る
env-a/bin/python -m pip install -qenv-aへインストール --no-index --find-links wheelhouse/old配布一覧をoldに限定 -r requirements.txt依存指定ファイル
env-a/bin/python inspect_env.py環境・版・処理結果を確認env-aは元のPython環境から分離されています。report 1.0.0が必要とするformatは1.0.0で満たされ、表示はqty=3です。python -m pipとすることで、そのPythonに対応する環境へインストールします。
4新しい配布一覧で同じ指定を使う
同じrequirements.txtを使い、別のenv-bを作ります。配布一覧だけをnewに変更します。
python3 -m venv env-b比較用の環境を作る
env-b/bin/python -m pip install -qenv-bへインストール --no-index --find-links wheelhouse/new新しい一覧を使う -r requirements.txt指定ファイルは同じ
env-b/bin/python inspect_env.py選ばれた版と表示を確認reportは1.0.0のままですが、formatは条件を満たす新しい1.1.0が選ばれます。表示もquantity=3に変わりました。2.0.0は<2に反するため選ばれません。直接の依存を固定しただけでは、その先の依存まで同じになるとは限りません。
5インストール済みの版を記録
env-aに入ったパッケージを、版を固定した一覧としてrequirements.lockへ出力します。このファイル名はこの演習の命名であり、pip freezeの専用形式名ではありません。
env-a/bin/python -m pip freezeenv-aのインストール済み版 > requirements.lock固定一覧へ保存 && cat requirements.lock保存した内容を確認lbox-formatとlbox-reportの両方が==で固定されます。pip freezeはその環境に入っている版を記録する機能です。このファイルだけでPython本体の版やOSまで固定されるわけではありません。
6新しい一覧から古い版を再現
env-cにはnewの配布一覧を使いますが、今度はrequirements.lockを指定します。
python3 -m venv env-c再現確認用の環境
env-c/bin/python -m pip install -q固定版をインストール --no-index --find-links wheelhouse/new新しい一覧を使用 -r requirements.lock依存先も含めた版指定
env-c/bin/python inspect_env.py再現した版と結果を確認新しい1.1.0が入手可能でも、指定に従ってformat 1.0.0が選ばれ、qty=3に戻ります。固定した配布ファイルを引き続き入手できることも再現に必要です。
7依存条件の整合性を検査
インストール済みのメタデータ上で、必要な依存が満たされているか確認します。
env-c/bin/python -m pip checkインストール済み依存の整合性を検査No broken requirements found.は依存条件に不整合がないという検査結果です。表示や計算が要求どおりであるかは、前の操作のようにプログラムも実行して確認する必要があります。
8同時に満たせない指定
report 1.0.0とformat 2.0.0を同時に要求し、インストール前の解決だけを試します。失敗の終了コードと、依存条件に関する行を表示します。
env-c/bin/python -m pip install --dry-run環境を変更せず依存を解決 --no-index --find-links wheelhouse/new解決に使う配布一覧 lbox-report==1.0.0 lbox-format==2.0.0両立しない版指定 > resolution.txt 2>&1
status=$?終了コードを保持
printf 'exit=%s
' "$status"
grep -E失敗の要点を抽出 'depends on|ResolutionImpossible' resolution.txt終了コードは1です。reportが要求するformat<2と、今回指定したformat==2.0.0は同時に満たせません。--dry-runなので、この確認によってenv-cのパッケージは入れ替わりません。
9版番号に加えてファイルも識別
同じ版番号の配布ファイルでも、中身が同じかを確認するにはハッシュ値が役立ちます。再現に使った二つのwheelのSHA-256を表示します。
env-c/bin/python -m pip hash配布ファイルのSHA-256を計算 wheelhouse/new/lbox_format-1.0.0-py3-none-any.whl間接依存の固定版 wheelhouse/new/lbox_report-1.0.0-py3-none-any.whl直接依存の固定版版番号はパッケージの版を指定し、ハッシュは配布ファイルのバイト列を識別します。厳密な再現では、全依存の固定版に加えて、取得するファイルのハッシュやPython・OSの条件も記録します。ハッシュを表示するだけではインストール時の検証は有効になりません。
まとめ
直接指定したパッケージの版が同じでも、許容範囲のある依存先は配布一覧によって変わります。
仮想環境ごとのPythonを明示し、直接・間接の依存をともに固定すると、別環境でも同じ版を選べます。
依存条件の整合性、プログラムの動作、配布ファイルの同一性は、それぞれ確認が必要です。
操作履歴と解説を振り返れます。
シェル / Python 3
cat requirements.txtlbox-report==1.0.0
python3 make_wheels.py
python3 - <<'PYCODE'
from pathlib import Path
from zipfile import ZipFile
for folder in ("old", "new"):
print(folder, sorted(p.name for p in (Path("wheelhouse") / folder).glob("*.whl")))
with ZipFile("wheelhouse/new/lbox_report-1.0.0-py3-none-any.whl") as wheel:
text = wheel.read("lbox_report-1.0.0.dist-info/METADATA").decode()
print(next(line for line in text.splitlines() if line.startswith("Requires-Dist:")))
PYCODEold ['lbox_format-1.0.0-py3-none-any.whl', 'lbox_report-1.0.0-py3-none-any.whl'] new ['lbox_format-1.0.0-py3-none-any.whl', 'lbox_format-1.1.0-py3-none-any.whl', 'lbox_format-2.0.0-py3-none-any.whl', 'lbox_report-1.0.0-py3-none-any.whl'] Requires-Dist: lbox-format>=1,<2
python3 -m venv env-a
env-a/bin/python -m pip install -q --no-index --find-links wheelhouse/old -r requirements.txt
env-a/bin/python inspect_env.pyenvironment: env-a isolated: True lbox-report: 1.0.0 requires: ['lbox-format>=1,<2'] lbox-format: 1.0.0 qty=3
python3 -m venv env-b
env-b/bin/python -m pip install -q --no-index --find-links wheelhouse/new -r requirements.txt
env-b/bin/python inspect_env.pyenvironment: env-b isolated: True lbox-report: 1.0.0 requires: ['lbox-format>=1,<2'] lbox-format: 1.1.0 quantity=3
env-a/bin/python -m pip freeze > requirements.lock && cat requirements.locklbox-format==1.0.0 lbox-report==1.0.0
python3 -m venv env-c
env-c/bin/python -m pip install -q --no-index --find-links wheelhouse/new -r requirements.lock
env-c/bin/python inspect_env.pyenvironment: env-c isolated: True lbox-report: 1.0.0 requires: ['lbox-format>=1,<2'] lbox-format: 1.0.0 qty=3
env-c/bin/python -m pip checkNo broken requirements found.
env-c/bin/python -m pip install --dry-run --no-index --find-links wheelhouse/new lbox-report==1.0.0 lbox-format==2.0.0 > resolution.txt 2>&1
status=$?
printf 'exit=%s
' "$status"
grep -E 'depends on|ResolutionImpossible' resolution.txtexit=1
lbox-report 1.0.0 depends on lbox-format<2 and >=1
ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflictsenv-c/bin/python -m pip hash wheelhouse/new/lbox_format-1.0.0-py3-none-any.whl wheelhouse/new/lbox_report-1.0.0-py3-none-any.whlwheelhouse/new/lbox_format-1.0.0-py3-none-any.whl: --hash=sha256:b47df86645ff67b64a11ed5d7c860e9ec6c16f925184fd98446418df5e358091 wheelhouse/new/lbox_report-1.0.0-py3-none-any.whl: --hash=sha256:50e116565b150e6f63607b2dab8af11436836f5fff1a985ab7d44050abd8e836
演習完了