• 作成:
  • 更新:

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.txtpyproject.tomlという2つの依存関係定義ファイルが混ざってしまう。よってpoetryが実行ステージに入ることを許容することにしました。依存関係はpoetryサイドのpyproject.tomlpoetry.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すれば良いと気がつきました。