3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】PDMのスクリプト機能を試してみる

Last updated at Posted at 2023-07-07

PDMについて

PDMはPythonのサードパーティのプロジェクト・パッケージ管理ツールです。

Pythonでは、npm(Node.js)やCargo(Rust)のようなプロジェクトに紐づいたパッケージ管理機能がPython標準で存在しません。そのためいくつかのサードパーティーツールの開発が進んでおり、その中の一つがPDMです。

よりメジャーなものにPoetryがあります。私自身もPoetryをメインに使ってきましたが、惜しい部分としてnpm-scriptsのようなユーザー定義可能なスクリプト機能がないことが挙げられます(アドオンはある)。PythonではFlake8やpytestなど、所定の手順とコマンドで実行してほしい手続きが結構多いです。

今回使用するPDMPoetryと同等(またはそれより速い)依存解決パフォーマンスを、さらに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 -

プロジェクト初期化

  1. test-project配下に同名プロジェクトを作成していきます。

    • PDMでは空ディレクトリを先に作成する必要があるみたいです(?)
    mkdir test-project && cd $_
    pdm init
    
  2. 使用するインタプリタを選択します

    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
    
  3. 選択したインタプリタの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の誕生の理由であるため、サポートは維持します。仮想環境の使用をお勧めします。

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

  4. ライブラリの開発を行うのか、アプリケーションの開発を行うのか選びます

    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
    
  5. あとはライセンスや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>で使用します

基本機能

  1. コマンド文字列

    <script_name>.cmd = <command>で、単純なコマンドエイリアスとして使用できます。今回はインストールしていませんが、pytestやflake8のエイリアスに便利ですね。

    単純に<script_name> = <cmd>とした場合も、コマンド文字列として認識されます。

    [tool.pdm.scripts]
    test-script.cmd = "echo Hello PDM!"
    
    # スクリプト実行
    $ pdm run test-script
    Hello PDM!
    
  2. シェルモード

    <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
    
  3. 環境変数
    <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"
    
  4. 引数の取り扱い

    プレースホルダ({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
    
  5. 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_scriptpre/post_runの違いは、前者がスクリプトが実行される(呼び出される)毎に必ずhookされ、後者は実行全体の前後でのみhookされます。使いどころは次の複合スクリプトを使った場合ですので、そちらの例を確認してください。

  6. 複合スクリプト

    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_scriptpre/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環境・依存関係管理ツールとしてまだまだ可能性があると思います。

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?