poetryで管理しているPythonパッケージのバージョンを一つの場所だけで定義する
問題
pyproject.toml
の[tool.poetry]
でのversion
、
__init__.py
での__version__
、
FastAPIのversion
ひいてはOpenAPIでのバージョン定義をそれぞれ別に行いたくない。
別々に書き換えるのは面倒だし、絶対に書き換え忘れのミスが生まれる。
一つの場所だけで定義して、それをみんなが参照するようにしたい。
解決法
How to get version from pyproject.toml from python app? · Issue #273 · python-poetry/poetry から引用。
pyproject.toml
はinit状態で良いとして、それぞれの__init__.py
は以下のようにする。
import importlib.metadata
__version__ = importlib.metadata.version(__package__)
アプリ側でバージョンを知りたくなったら自身をimport
する。
from . import __version__
ここで引っかかったのは、バージョンをpyproject.toml
を書き換える形で更新した場合前のバージョンのままになってしまうこと。
poetry install
してデータを更新するか、最初からpoetry version
でバージョンデータを書き換えるようにしましょう。
また、この形式を使う場合poetry run foo/app.py
のような実行方法は使えません。
poetry run python3 -m foo.app
のように実行しましょう。
Dockerの実行ステージにpoetryをインストールすることを妥協する必要がある
これまでPythonを使うDockerfileでは、マルチステージビルドを使って、
poetry export -f requirements.txt > requirements.txt
して、実行ステージで、
pip3 install -r requirements.txt
することで実行ステージにpoetryをインストールせずに済ませていました。
しかし、このようにバージョンを取得するように書き換えると、 Dockerコンテナは以下のようなエラーで立ち上がらなくなります。
/usr/local/bin/python3: Error while finding module specification for 'foo.app' (PackageNotFoundError: No package metadata was found for foo)
pip3
で.
を対象にインストールさせることも考えましたが、それだとpyproject.toml
のコピーなども必要になりますし。そのままだとコピーしてもダメで色々ややこしいです。
requirements.txt
とpyproject.toml
という2つの依存関係定義ファイルが混ざってしまう。よってpoetryが実行ステージに入ることを許容することにしました。依存関係はpoetryサイドのpyproject.toml
とpoetry.lock
だけで管理。ステージのサイズなどは、どうせPythonとかいうクソデカランタイムが入っている時点で誤差。
私は誤差だと割り切りましたが、どうしても実行イメージにpoetryなどのパッケージ管理システムが入ることが許容できなかったり、 distrolessのようなアプローチを取りたい場合に今回の手法は適さないかもしれません。その場合どのようにするのが適切にバージョン管理の一元化になるのかは議論の余地があります。
今回力技だなと思って避けましたが、実行ステージがtomlライブラリに依存することを受け入れてそこから取得した方が良いのかもしれません。 FastAPIのOpenAPIバージョンをpyproject.tomlから取得する - Qiita 複数パッケージ等になると定型化は出来ませんが。
Pythonに限らず、コンパイルステージでだいたいの要素を含んだバイナリに出来ない言語はこの辺ややこしいことが多いですね。
JavaScriptですらpackage.json
を力技で読み込んでいることが多そうですし。これは元からNodeに含まれる力なのでそこまで面倒ではなさそうですが、位置に依存したりしそうですね。
CMDの前にもう一度poetry installする必要がある
poetry install --no-dev
する前に本体のソースコードをコンテナに含めないと、
/root/.cache/pypoetry/virtualenvs/bar-LjB2m75f-py3.10/bin/python3: Error while finding module specification for 'foo.app' (PackageNotFoundError: No package metadata was found for foo)
のようなエラーになってしまう。
しかし最初に含めるとソースコードを少し弄るたびにビルドキャッシュが崩壊してしまうためどうしたものかと思案した所、
COPY pyproject.toml poetry.lock ./
したあとにpoetry install --no-dev
して、
COPY
でメインのソースコードを取ってきた後にもう一度poetry install --no-dev
すれば良いと気がつきました。