PDMについて
PDMはPythonのサードパーティのプロジェクト・パッケージ管理ツールです。
Pythonでは、npm(Node.js)やCargo(Rust)のようなプロジェクトに紐づいたパッケージ管理機能がPython標準で存在しません。そのためいくつかのサードパーティーツールの開発が進んでおり、その中の一つがPDMです。
よりメジャーなものにPoetry
があります。私自身もPoetry
をメインに使ってきましたが、惜しい部分としてnpm-scripts
のようなユーザー定義可能なスクリプト機能がないことが挙げられます(アドオンはある)。PythonではFlake8やpytestなど、所定の手順とコマンドで実行してほしい手続きが結構多いです。
今回使用するPDM
はPoetry
と同等(またはそれより速い)依存解決パフォーマンスを、さらにPoetry
より後発であるが故の独自仕様の少なさ(PEPへの忠実さ)が売りの管理ツールですが、スクリプト機能が存在する点も、Poetryと差別化できる大きなポイントです。
poetryと同等の活用方法や依存関係解決に関する話題は、先達の皆様から既にいくつか出ていますので、このスクリプト機能に焦点を紹介したいと思います。
テストプロジェクトをPDM管理下で作成し、いろいろ試してみましょう。
テスト環境
- OS: Amazon lunux 2 (EC2 t2.micro)
- Python: 3.9.14
- pyenv: 2.3.21-2-ga2dff480
- PDM: 2.7.4
PDMのインストール
pyenv及びPythonに必要な依存パッケージの導入は済んでいる前提で記述します。
今回はAmazon linux 2(cloud9)で実行しているので、プレインストールされているopensslをバージョン1.1.1以上に切り替えた上で、Pythonをインストールする必要があります。
# PDM内部で使用されているurllib3 v2.*ではopenssl1.1.1が必要です
# Amazon Linuxではopenssl11というパッケージでインストールできます
# あらかじめ旧バージョン(1.0.2)のopensslを削除しておく必要があります
# 詳細: https://github.com/urllib3/urllib3/issues/3016
sudo yum remove -y openssl openssl-devel
sudo yum install -y openssl11 openssl11-devel
# Pyenvで任意のバージョンをインストール
# PDMで管理する環境はPython3.7以上である必要があります(パッケージのビルドはそれより古いバージョンをターゲットにすることが可能)
pyenv install 3.9.14
pyenv global 3.9.14
# PDMをインストール
curl -sSL https://pdm.fming.dev/dev/install-pdm.py | python3 -
プロジェクト初期化
-
test-project配下に同名プロジェクトを作成していきます。
- PDMでは空ディレクトリを先に作成する必要があるみたいです(?)
mkdir test-project && cd $_ pdm init
-
使用するインタプリタを選択します
Please enter the Python interpreter to use 0. /home/ec2-user/.pyenv/shims/python3 (3.9) 1. /home/ec2-user/.pyenv/shims/python (3.9) 2. /home/ec2-user/.pyenv/versions/3.9.14/bin/python3.9 (3.9) 3. /home/ec2-user/.pyenv/shims/python3.9 (3.9) 4. /usr/bin/python3.7m (3.7) 5. /usr/bin/python3.7 (3.7) 6. /usr/bin/python2.7 (2.7) 7. /home/ec2-user/.local/share/pdm/venv/bin/python (3.9) Please select (0): 2
-
選択したインタプリタのvenv環境を作成するか選択します。
Would you like to create a virtualenv with /home/ec2-user/.pyenv/versions/3.9.14/bin/python3.9? [y/n] (y):y -> Virtualenv is created successfully at /home/ec2-user/environment/test-project/.venv
注意: PDMが率先して対応していた、
__pypackages__
を使用したパッケージ管理手法(PEP-582)はリジェクトされてしまいました。PDM公式でも現在はvenvを用いるよう推奨しています。決意に満ちたメッセージは以下。
This is a rejected PEP. However, due to the fact that this feature is the reason for PDM's birth, PDM will retain the support. We recommend using virtual environments instead.
これは拒否されたPEPです。しかしながら、この機能がPDMの誕生の理由であるため、サポートは維持します。仮想環境の使用をお勧めします。
-
ライブラリの開発を行うのか、アプリケーションの開発を行うのか選びます
Noを選ぶと、エントリ部分の存在するアプリと見なされ、パッケージの説明や対応するPythonバージョンの指定などに関する質問がスキップされます(Pyprocjet.tomlでも該当部分が空白になります)
Is the project a library that is installable? If yes, we will need to ask a few more questions to include the project name and build backend [y/n] (n): n
-
あとはライセンスやAutorなどなど一般的な質問が続くので、良しなに回答してください
License(SPDX name) (MIT): Author name (): quag-cactus Author email (): Python requires('*' to allow any) (>=3.9): Changes are written to pyproject.toml.
以上でpyproject.tomlが自動生成され、test-project/配下をpdm管理下のプロジェクトとして使用できます。
Poetryと違い、tests/
やsrc/
は自動生成されません。
今回の題材はスクリプト機能なので割愛しますが、作成したプロジェクト配下ではpdm add
コマンドを用いて、依存関係を追加していくことができます。
スクリプト機能
本題のスクリプト機能を確認していきましょう。
スクリプトはpyproject.toml
の[tool.pdm.scripts]セクションの中に記述します。
定義したスクリプトはpdm run <script_name>
で使用します
基本機能
-
コマンド文字列
<script_name>.cmd = <command>
で、単純なコマンドエイリアスとして使用できます。今回はインストールしていませんが、pytestやflake8のエイリアスに便利ですね。単純に
<script_name> = <cmd>
とした場合も、コマンド文字列として認識されます。[tool.pdm.scripts] test-script.cmd = "echo Hello PDM!"
# スクリプト実行 $ pdm run test-script Hello PDM!
-
シェルモード
<script_name>.shell = "<shellscript>"
とすることで、右辺はシェルスクリプトとして実行されます。より複雑なタスクを定義できます。<script_name>.cmd
をしようした場合も同様ですが、スクリプト機能で実行したPythonスクリプトはPDMで管理構築している環境内で実行されます。毎回pdm run
をはさまなくても良いということです。# 標準出力をリダイレクト [tool.pdm.scripts] echo-redirect.shell = "python -c 'import math; print(round(math.pi, 8))' > ./output.txt"
# 実行結果 $ pdm run echo-redirect $ cat output.txt 3.14159265
-
環境変数
<script_name>.env
を定義することで、そのスクリプト実行時の環境変数を定義できます。複数設定可能です。個人的には、sys.pathにパスを追加するときに重宝しています。
[tool.pdm.scripts] # syspathの追加パスを指定した上で実行 add-syspath.shell = "python -c 'import sys;print(sys.path[1])'" add-syspath.env = {PYTHONPATH = "/FOO/HOGE"}
# 実行結果 $ pdm run add-syspath "/FOO/HOGE"
-
引数の取り扱い
プレースホルダ(
{args}
)を使用することで、スクリプトに引数を与えることができます。プレースホルダとして用意されているキーワードは
{args}
のみです。また、スクリプトに与えられた引数が複数であっても、それをすべて受け取って展開します。そのため、プレースホルダを変数のように扱えるわけではありません。[tool.pdm.scripts] # 基本の記法 check-args.cmd = "echo {args}" # 引数を与えなかった場合のデフォルト値を設定可能 check-args-default.shell = "echo {args:this-is-deafult-value}" # 複数のargsを設定した場合の挙動 check-args-multi.shell = "echo {args} {args}"
# 実行結果 $ pdm run check-args xxx xxx $ pdm run check-args-default this-is-deafult-value $ pdm run check-args-default yyy yyy # 与えられた引数がプレースホルダの位置にそれぞれ展開される $ pdm run check-args-multi zzz zzz zzz # 複数の引数を与えた場合、引数すべてがプレースホルダに展開される $ pdm run check-args-multi aaa bbb aaa bbb aaa bbb
-
Pre/Postスクリプト
定義したスクリプト名に
pre_
及びpost_
の接頭辞をつけたものは、当該スクリプトの前後にhookされ実行されます。※hookされたスクリプトでは、
{args}
プレースホルダは使用できませんpre_main = "echo -> This is PRE-MAIN" main = "echo -> This is MAIN" post_main = "echo -> This is POST-MAIN"
# 実行結果 $ pdm run main -> This is PRE-MAIN -> This is MAIN -> This is POST-MAIN
hookスクリプトは他にも種類があります。以下のタイミングでスクリプトを設定できます。プロジェクト管理で便利なhookも多いですね。
hookスクリプト一覧
- post_init:
pdm init
後に実行します - pre_install: パッケージをインストールする前に実行します
- post_install: パッケージのインストール後に実行します
- pre_lock: 依存関係を解決する前に実行します
- post_lock: 依存関係の解決後に実行します
- pre_build: ディストリビューションを構築する前に実行します
- post_build: ディストリビューションの構築後に実行します
- pre_publish: ディストリビューションを公開する前に実行します
- post_publish: ディストリビューションが公開された後に実行します
- pre_script: スクリプトの前に実行します
- post_script: スクリプトの後に実行
- pre_run: 実行スクリプトを呼び出す前に1回だけ実行します
- post_run: 実行スクリプトの呼び出し後に1回だけ実行します
pre/post_script
とpre/post_run
の違いは、前者がスクリプトが実行される(呼び出される)毎に必ずhookされ、後者は実行全体の前後でのみhookされます。使いどころは次の複合スクリプトを使った場合ですので、そちらの例を確認してください。 - post_init:
-
複合スクリプト
composite
属性を登録したタスクを作成し、その要素としてタスク名の配列を定義することで、定義されたタスクを順番に起動します。# これまで定義したスクリプトを連続して実行 test-runner = {composite = [ "test-script", "add-syspath", "check-args xxx", "check-args {args}" ]}
# 実行結果 $ pdm run test-runner Hello PDM! /FOO/HOGE xxx yyy
途中のスクリプトでエラーが発生した場合は、その時点で実行がストップします。
[pdm.tool.scripts] # Pythonのゼロ除算エラーを送出するタスク test-err.shell = "python -c 'print(10 / 0)'" # 複合スクリプト test-runner-err = {composite = [ "test-script", "test-err", "add-syspath", "check-args xxx", "check-args {args}" ]}
# 実行結果 $ pdm run test-runner-err Hello PDM! Traceback (most recent call last): File "<string>", line 1, in <module> ZeroDivisionError: division by zero
また、復号スクリプトはネストした定義も可能になっています。それぞれに前後スクリプトを設定することもできます。
下の例では複合スクリプトのネストに加え、
pre/post_script
とpre/post_run
がhookされた挙動も示しています。[tool.pdm.scripts] # hookスクリプト pre_script = "echo <This is PRE-SCRIPT>" post_script = "echo <This is POST-SCRIPT>" pre_run = "echo <This is PRE_RUN>" post_run = "echo <This is POST_RUN>" # メインスクリプト pre_main = "echo -> This is PRE-MAIN" main = "echo -> This is MAIN" post_main = "echo -> This is POST-MAIN" # 内部複合スクリプト内の実行スクリプト internal_1 = "echo --> This is INTERNAL-1" internal_2 = "echo --> This is INTERNAL-2" # 内部(ネスト)複合スクリプト pre_internal_runner = "echo --> This is PRE-INTERNAL-RUNNER" internal_runner.composite = ["internal_1", "internal_2"] post_internal_runner = "echo --> This is POST-INTERNAL-RUNNER" # 全体実行複合スクリプト pre_main_runner = "echo This is PRE-MAIN-RUNNER" main_runner.composite = ["main", "internal_runner"] post_main_runner = "echo This is POST-MAIN-RUNNER"
# 実行結果 $ pdm run main_runner <This is PRE_RUN> <This is PRE-SCRIPT> This is PRE-MAIN-RUNNER <This is PRE-SCRIPT> -> This is PRE-MAIN -> This is MAIN -> This is POST-MAIN <This is POST-SCRIPT> <This is PRE-SCRIPT> --> This is PRE-INTERNAL-RUNNER <This is PRE-SCRIPT> --> This is INTERNAL-1 <This is POST-SCRIPT> <This is PRE-SCRIPT> --> This is INTERNAL-2 <This is POST-SCRIPT> --> This is POST-INTERNAL-RUNNER <This is POST-SCRIPT> This is POST-MAIN-RUNNER <This is POST-SCRIPT> <This is POST_RUN>
ちょっと見にくくなってしまいましたが、感覚通りの実行順となっているはずです。
まとめ
PDMはパッケージ開発を前提にしているpoetryと比べ、アプリケーション開発も考慮したツールになっていると感じます。その理由の一つがPDM Scripts
です。
pipenvやRyeにもユーザー定義可能なスクリプト機能は搭載されていますが、機能面ではPDMのそれが最も充実しています。
特に「作ってみた」系のミニアプリや実証スクリプトをPDM仮想環境で管理&PDM Scripts
で実行コマンドを短くまとめてもらっていると、自環境で試すためのハードルがグっと下がるのではないでしょうか。
私自身、アプリ開発では今まで(無理やり)Poetryで管理していたところをPDMに移行しつつあります。移行も簡単なので、Poetryでpoetry install --no-root
をよく実行する方におすすめです。
もっとメジャーにならないかなあ。__pypackages__
こそ採用されませんでしたが、前述したようにアプリケーション開発に対応したpyenv環境・依存関係管理ツールとしてまだまだ可能性があると思います。