PDMによるPythonのプロジェクト管理

2022/07/31に公開

PDMとは

PDMはPythonのプロジェクト、依存関係のマネージメントツールです。私は以前までPoetryを利用していましたが以下の点からPDMに乗り換えました。

  1. pyproject.tomlを利用する点はPoetryと同じだが、よりPEP準拠になっている。
  2. Poetryよりも依存解決が速い
  3. Poetryはいつまで経ってもv1.2がリリースされない。
  4. 単体でタスクランナー機能を有している。(Poetryはプラグインが必要)
  5. 指定したファイルの__version__を読み込んでくれる。
  6. プロジェクトごとにvenvを作るが、インストールされるライブラリは中央管理されていてシンボリックリンクを張ることでディスク容量を節約でき、インストールも速い。

特に私が一番気に入っているのは最後のインストールキャッシュ機能で、例えばDebian 11にてPoetryでNumPyだけをインストールした場合、.venvのサイズは88MBですが、PDMの場合はなんとわずか176KBです。これはPDMはライブラリの実体を$HOME/.cache/pdm/packages以下にインストールし、.venv/lib/python<version>/site-packages内にリンクを張っているからです。したがってPoetryの場合はNumPyを利用する各プロジェクトごとに88MB消費しますが、PDMの場合はいくらプロジェクトを増やしても同じバージョンであれば合計88MBで済みます。

インストール

Debian 11ではpipxを利用するのが一番簡単です。

sudo apt install pipx
pipx install pdm

そして忘れずに

pdm config --global install.cache True

を実行して前述のインストールキャッシュ機能を有効にします。(なぜかこれはデフォルトではFalse)

使い方

https://pdm.fming.dev/latest/usage/cli_reference/

# add dependency
pdm add numpy

# add optional dependency
pdm add -G <group_name> scipy

# add dev dependency
pdm add -d flake8 mypy black isort pytest-cov

# install from pyproject.toml
pdm install

# without dev dependency
pdm install --prod

# with optional dependency
pdm install -G <group_name>

# run with installed dependency
pdm run <cmd>

# build wheel
pdm build

なお、プロジェクトのメインのソースコードはsrc/以下に入れておくのがデフォルトのしきたりになっています。

https://pdm.fming.dev/latest/pyproject/build/#select-another-package-directory-to-look-for-packages

また、pdm install/syncはプロジェクト自体はsetup.py develop相当になっていて、setup.py install相当にするにはpdm install/sync --no-editableにします。

https://pdm.fming.dev/latest/usage/dependency/#editable-dependencies

pyproject.tomlの設定

pdm add コマンド以外で手動で編集するべき項目。詳細は

https://pdm.fming.dev/latest/pyproject/pep621/

を参照。

dynamic versioning

[project]
dynamic = ["version"]

[tool.pdm]
version = { source = "file", path = "src/app/version.py" }

これでsrc/app/version.py内の__version__を読んでくれます。

プロジェクトスクリプト

[project.scripts]
app = "app.cli:main"

これでpdm installしたときに.venv/bin/appが作成され、実際にはsrc/app/cli.pyの中のmainという名前の関数が実行されます。

タスクランナー

https://pdm.fming.dev/latest/usage/scripts/

black = "black ."
isort = "isort ."
flake8 = "flake8 --exit-zero ."
mypy = "mypy --show-column-numbers ."
test = "pytest tests --cov=app --cov-report=term --cov-report=xml"
format = { composite = ["black", "isort"] }
lint = { composite = ["flake8", "mypy"] }
check = { composite = ["format", "lint", "test"] }

上記のように書くとpdm run checkでblackとisortとflake8とmypyとpytestをすべてやってくれます。

Dockerfileの書き方

https://pdm.fming.dev/latest/usage/advanced/#use-pdm-in-a-multi-stage-dockerfile

をベースに少しいじっています。

FROM python:3.10-slim as pdm

RUN pip install -U pip setuptools wheel
RUN pip install pdm
# PDM 2.0からはデフォルトでvenvを使うのでoffにしてpep 582モードにする
RUN pdm config python.use_venv False


FROM pdm as builder
WORKDIR /project

# 一回目は依存ライブラリのみをインストール
COPY pyproject.toml pdm.lock /project/
RUN pdm sync -G server --prod --no-self

# 二回目はプロジェクト本体をインストール
# 分けることによって依存ライブラリのインストールをDockerのビルドキャッシュに載せることができる
COPY src/ /project/src
RUN pdm sync -G server --prod --no-editable


FROM python:3.10-slim

ENV PYTHONPATH=/project/lib

COPY --from=builder /project/__pypackages__/3.10/lib /project/lib
COPY --from=builder /project/__pypackages__/3.10/bin /project/bin

WORKDIR /project
COPY src/ /project/src


ENV N_WORKERS 4

CMD bin/gunicorn \
    --access-logfile - \
    --bind=0.0.0.0:8000 \
    --workers=${N_WORKERS} \
    -k uvicorn.workers.UvicornWorker \
    src.app.server:app                         

まとめ

https://github.com/lucidfrontier45/python-pdm-template

にテンプレートとして使えるプロジェクトを作成しました。上記の内容以外にlinterやformatterの設定をsetup.cfgにまとめてあり、またVSCodeの設定もvscode_templatesに入れてあるのでこれを.vscodeにコピーし、extensinos.jsonに挙げられている拡張をインストールするとすぐに快適な開発環境になります。

Discussion