サンプルコード:https://github.com/tamanobi/poetry-with-docker-sample
次の点を踏まえて、 Pipenv から Poetry へ移行しました。そのときの知見をここに記します。
- Pipenv はメンテナンス頻度が落ちていること
- Pipenv のライブラリ依存解決の機能が遅く、解決できなくなることが多くなること
メンテナンス頻度の懸念は、安定しており変更もほぼ必要ない状態であれば問題ありません。しかし2つ目の問題が障害になりました。
Pipenv を使っている人は感じていると思いますが、 Pipenv の lock ファイル生成は長い時間がかかります。その遅さにしびれを切らした私はしばしば --skip-lock を入れてライブラリの動作検証を行なうこともしばしばありました。これは悪癖です。しかし、耐えられないほど長い時間であることも事実でした。
Poetry は lock ファイルの生成が非常に高速です。私は趣味プロジェクトで導入してから「これはいいぞ」と仕事のプロジェクトでも導入しました。パッケージマネージャーの移行作業は思ったよりもうまくいきました。ただし、多少工夫が必要でした。
Pipfile から pyproject.toml
まず最初にやったのは、 poetry init
による pyproject.toml
の生成です。実行すれば対話に答えていくだけで生成できます。デフォルトで問題ないはずです。
次に、 Pipfile の中身を適宜コピーして poetry add hogehoge
することでした。表記に違いはほとんどないので、ただの作業でした。
めんどくさがりな私は次のような Pipfile に書かれた文字を1行に置換して poetry add
しました。
mypy = "~=0.761"
pytest = "~=5.3.5"
を mypy~=0.761 pytest~=5.3.5
のように整形し、 poetry add mypy~=0.761 pytest~=5.3.5
としました。開発用パッケージは、 poetry add --dev hogehoge
でインストール可能です。
Poetry に対応した Dockerfile
Poetry の公式では、 pip
によるインストールは推奨されていません。これは poetry
が依存しているライブラリをインストールすることになるため、依存関係の解決が困難になる可能性があるからです。
そのため Dockerfile の中では、公式が推奨している get-poetry.py
をダウンロードする手法を使います。公式の手法そのままだと、 poetry のバージョン固定ができません。常に最新版がインストールされます。回避するために、ダウンロード先を変更します。
ダウンロード先は GitHub だったので、 master
となっているところを Git のタグで置換することができます。
- https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
+ https://raw.githubusercontent.com/python-poetry/poetry/${POETRY_VERSION}/get-poetry.py
POETRY_VERSION という変数を変更すれば Poetry のバージョンを簡単にコントロールできます。
Poetry がライブラリをインストールするためには pyproject.toml と poetry.lock が必要なので COPY します。 poetry は export
コマンドで requirements.txt を生成することができます。 -f
は出力フォーマット、 -o
は出力先ファイル名を指しています。出力フォーマットは現状では、 requirements.txt
にしか対応していません。
$ poetry export -f requirements.txt -o requirements.lock
今回作成した、 Dockerfile ではマルチステージビルドを利用していますが、必要でなければ無視してください。作成した Dockerfile の全体像を次に示します。
FROM python:3.8-slim as builder
WORKDIR /app
ENV POETRY_VERSION 1.0.5
ADD https://raw.githubusercontent.com/python-poetry/poetry/${POETRY_VERSION}/get-poetry.py ./get-poetry.py
COPY pyproject.toml poetry.lock /app/
RUN python get-poetry.py && \
# Docker なので virtualenvs.create する必要がない。 see: https://stackoverflow.com/questions/53835198/integrating-python-poetry-with-docker/54186818
/root/.poetry/bin/poetry config --local virtualenvs.create false && \
/root/.poetry/bin/poetry export -f requirements.txt -o requirements.lock && \
pip install -r requirements.lock
FROM python:3.8-slim as runner
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
COPY . /app/
CMD ["python", "main.py"]
Pipfile にあったタスクランナーの機能が Poetry にはない
Pipenv ではタスクランナーの機能がついていました。Pipfile に記述しておけば pipenv run test
や pipenv run format
というように独自のスクリプトが定義できる仕組みです。
不幸にも Poetry にはその機能がありません。似たような機能はありますが、ユースケースが異なります。その詳細については次に示すリンク先を読んでください。
- https://github.com/python-poetry/poetry/pull/591#issuecomment-504762152
- https://tech.515hikaru.net/2020-02-25-poetry-scripts/
そのため、タスクランナーを別途用意する必要があります。タスクランナーを自前で作ったり、そのためだけにライブラリを導入するのは無駄が多いです。今回は、 小さなタスクランナーを Makefile とシェルスクリプトで作成しました。
Makefile にはターゲットと実行コマンドを書きます。ターゲットは .PHONY に入れてファイルと競合しないように注意してください。また、引数が必要な実行については後述する注意が必要です。
.PHONY: isort black test flake8 say
format: isort black
isort:
isort -rc .
black:
black
flake8:
flake8 --config .flake8
test:
pytest .
Makefile で可変長変数をプロキシする
引数を受け取らないタスクランナーであれば、上述した記述で make format
を打ち込むだけで簡単に isort と black が動作してくれます。これは非常に便利です。
しかし make の本来の用途であるビルドとは異なった使い方です。故に可変長引数の扱いが簡単ではありません。そこで、小さなシェルスクリプトで回避策を考えました。次に示します。
#!/bin/bash
TARGET=$1
shift
make "$TARGET" ARGS="$*"
これは make のラッパーです。 ./run-script test
と書けば make test
に展開されます。 第2引数以降は $*
を利用することでスペースを含んだ文字列として Makefile の ARGS に展開できます。
例えば可変長引数を受け取りたい次のような make ターゲットが合ったとすると、 $(ARGS)
の部分が第2引数以降として処理されます。
.PHONY: say
say:
python -m scripts.main say $(ARGS)
この工夫によって、 Makefile とシェルスクリプトでタスクランナーが実現できます。
移行を終えて
移行の方法は以上です。パッケージマネージャーの移行作業はより骨が折れるかと思いましたが、案外簡単にすみました。実作業にして1時間です(調査時間は含んでいません)。
Pipenv を使っていてずっと悩んでいた依存解決や、遅さが Poetry を選択してからまったく気にならなくなりました。