🐍

PDM - Python Development Master 【翻訳】

37 min read

はじめに

PEP 582 に対応した Python のパッケージマネージャは Pyflow くらいしか知りませんでした。Pyflow は Rust 製だし〜という理由からか、使用しているという声をあまり聞きませんでしたが、PDM という Python 製の PEP 582 に対応したパッケージマネージャを偶然見つけたのでドキュメントを和訳してみました。現状、Poetry でいいとは思うのですが、PEP 582 も体験してみたいという方のご参考になれば。
PDM はコミュニティードリブンを目指しているので、盛り上がるといいですね。
(なお私は和訳しただけでまだ使っていません)

PDM v1.4.3 時のドキュメントを和訳しています。

PDM - Python Development Master

イントロダクション

PDMは PEP 582 をサポートしたモダンなパッケージマネージャです。vertualenv を作成せずに、npm と似たような方法でパッケージをインストール、管理することが可能です。

機能のハイライト

  • virtualenv を必要としない方法であるPEP 582 [1]を採用したローカルパッケージインストーラとランナー
  • 主に巨大バイナリ配布パッケージに対するシンプルで比較的高速な依存リゾルバ
  • PEP 517 [2]準拠のビルドバックエンド
  • PEP 621 [3]準拠のプロジェクトメタデータ

インストール

PDM のインストールには Python 3.7+ がインストールされていることが必要です。PDM はマルチプラットフォーム対応で、 Windows, Linux, MacOS で動作します。

どの Python のバージョンがプロジェクトで使用されているのかについては問われませんが、PDM のインストールそのものには Python 3.7+ が必要です。

おすすめのインストール方法

MacOS で homebrewを使用しているなら、

$ brew install pdm

そうでなければ、システムの Python 環境がめちゃくちゃになることを避けるために、pipx[4]で PDM をインストールすることをお勧めします。

$ pipx install pdm

他のインストール方法

ユーザー環境に PDM を pip でインストールするなら、

$ pip install --user pdm

PEP 582 をグローバルで有効化

Python インタプリタに PEP 582 パッケージを認識させるには、pdm/pep582/sitecustomize.py を Python のライブラリ検索パスに追加する必要があります。

Windows ユーザー

pdm --pep582 とすれば、環境変数が自動で変更されます。変更を有効化するために、ターミナルセッションを再起動するのを忘れないでください。

Mac, Linux ユーザー

環境変数を変更するコマンドは、pdm --pep582 [<SHELL>] です。<SHELL> が与えられない場合、PDM はシェルを推定します。

ログイン時に有効化するには、.bash_profile (あるいは別のシェルのプロファイル) に設定を記述します。例えば、

$ pdm --pep582 >> ~/.bash_profile

有効化にはターミナルセッションの再起動が必要です。

docker イメージを使う

PDM はデプロイフローを簡単にするために、docker イメージを提供しています。使用するには、Dockerfile に下記のように記述してください。

FROM frostming/pdm

COPY . /app

# -- Replace with the correct path to your app's main executable
CMD ["pdm", "run", "python", "main.py"]

シェル補完

PDM は Bash, Zsh, Fish, Powershell 用の補完生成スクリプトを提供しています。各シェルの一般的な場所は以下のようになっています。

# Bash
$ pdm completion bash > /etc/bash_completion.d/pdm.bash-completion

# Zsh
# Make sure ~/.zfunc is added to fpath, before compinit.
$ pdm completion zsh > ~/.zfunc/_pdm

# Oh-My-Zsh
$ mkdir $ZSH_CUSTOM/plugins/pdm
$ pdm completion zsh > $ZSH_CUSTOM/plugins/pdm/_pdm
# Then make sure pdm plugin is enabled in ~/.zshrc

# Fish
$ pdm completion fish > ~/.config/fish/completions/pdm.fish

# Powershell
# Create a directory to store completion scripts
PS > mkdir $PROFILE\..\Completions
PS > echo @'
Get-ChildItem "$PROFILE\..\Completions\" | ForEach-Object {
    . $_.FullName
}
'@ | Out-File -Append -Encoding utf8 $PROFILE
# Generate script
PS > Set-ExecutionPolicy Unrestricted -Scope CurrentUser
PS > pdm completion powershell | Out-File -Encoding utf8 $PROFILE\..\Completions\pdm_completion.ps1

Unicode と ANSI サポート

ANSI 文字と Unicode 絵文字によって、豪華なターミナル UI を提供します。使用しているターミナルのサポート状況により、自動でオンオフを切り替えることができます。この機能をオフにするには環境変数 DISABLE_UNICODE_OUTPUT=1 としてください。

IDE で使う

今のところ、ほとんどの IDE は PEP 582 に対応したビルトインプラグインをサポートしていません。そのため、手動で設定する必要があります。

PDM はプロジェクトの設定を .pdm.toml に保存するので、これを git の管理から外すために .gitignore 下記のように記入することをお勧めします。

.pdm.toml
__pypackages__/

PyCharm

__pypackages__/<major.minor>/lib をSources Root とします。

また、プロジェクト環境にインストールしたツール (e.g. pytest) を使用する場合、対応する実行/デバッグ構成の環境変数 PATH に、__pypackages__/<major.minor>/bin を追加する必要があります。

VSCode

以下を setting.json に追加します。

{
  ...
  "python.autoComplete.extraPaths": ["__pypackages__/<major.minor>/lib"],
  "python.analysis.extraPaths": ["__pypackages__/<major.minor>/lib"]
}

Task Provider

さらに、PDM の VSCode Task Provider extension がダウンロード可能です。

これをインストールすると、VSCode は自動的にpdm scripts を検知できるようになり、VSCode Tasks としてネイティブに実行できます。

依存関係を管理する

PDM はプロジェクトのとその依存関係の管理を助けるコマンド群を提供します。以下の例は Ubuntu 18.04 で実行しており、Windows をお使いの場合、多少の変更が必要となります。

プロジェクトの初期化

$ mkdir pdm-test && cd pdm-test
$ pdm init

PDM が表示するいくつかの質問に答えると、pyproject.toml がプロジェクトルートに生成されます。

[project]
name = "pdm-test"
version = "0.0.0"
description = ""
authors = [
    {name = "Frost Ming", email = "mianghong@gmail.com"}
]
license = {text = "MIT"}
requires-python = ">=3.7"

dependencies = []
dev-dependencies = []

もし、pyproject.toml がすでに存在していたら、メタデータとともにアップデートされます。メタデータのフォーマットはPEP 621 specification に従っています。

それぞれのフィールドの詳細については、Project File を参照してください。

依存関係を追加する

$ pdm add requests
$ pdm add -d pytest

pdm add コマンドの後には複数の依存関係を入力することができ、依存関係の仕様は PEP 508 で説明されています。依存関係には 2 パターンあり、デフォルトではproject.dependencies に、pdm add-d/--dev オプションが渡されるとproject.dev-dependencies に追加されます。

また、PDM では -s/--section <name> オプションを指定することで追加の依存関係グループを作れ、その依存関係はそれぞれプロジェクファイルのproject.optional-dependencies.<name> に追加されます。

その後、依存関係やサブ依存関係は適切に解決、インストールされます。全ての依存関係の解決結果は、pdm.loc で確認できます。

ローカルパッケージは pdm add -e/--editable <local project path> を使うことで editable mode (pip install -e <local project path> のように) インストール可能です。

バージョン指定子を保存する

pdm add requests のように、パッケージのバージョンが指定されない場合、3 つのバージョン指定の方法を提供します。指定には、--save-<strategy> を使用します(下記の例では2.21.0 が依存関係の最新バージョンとします)。

  • compatible : compatible なバージョン指定子を保存します: >= 2.21.0,<3.0.0 (デフォルト)
  • exact : 正確なバージョン指定子を保存します: ==2.21.0
  • wildcard : バージョン制約を外し、指定子をワイルドカードのままにします: *

依存関係をアップデートする

ロックファイルに存在する全ての依存関係をアップデートするには、

$ pdm update

特定のパッケージをアップデートするには、

$ pdm update requests

アップデート形式について

同様に、PDM は、依存関係とサブ依存関係のアップデートに 2 つの異なる挙動を提供します。これは、--update-<strategy> オプションで指定します。

  • reuse : コマンドラインで指定された物を除いて、全てのロックされた依存関係を保持します
  • eager : コマンドラインで指定されたパッケージの新しいバージョンとその再帰的なサブ依存関係をロックし、それ以外の依存関係は保持します

依存関係の削除

プロジェクトファイルとライブラリのディレクトリに存在する依存関係を削除するには、

$ pdm remove requests

プロジェクトパッケージをロックファイルと同期する

2 つの似た動作をするコマンドがありますが、わずかに異なります。

  • pdm install はロックファイルをチェックし、プロジェクトファイルと一致しない場合は再ロックを行い、パッケージをインストールします。
  • pdm sync はロックファイルの依存関係をインストールし、ロックファイルが存在しなければエラーを吐きます。さらに、--clean オプションが指定された場合、不要なパッケージを削除します。

インストールしたパッケージを表示する

pip list のように、インストールされている全てのパッケージを一覧表示できます。

$ pdm list

また、依存関係のグラフを表示できます。

$ pdm list --graph
tempenv 0.0.0
└── click 7.0 [ required: <7.0.0,>=6.7 ]
black 19.10b0
├── appdirs 1.4.3 [ required: Any ]
├── attrs 19.3.0 [ required: >=18.1.0 ]
├── click 7.0 [ required: >=6.5 ]
├── pathspec 0.7.0 [ required: <1,>=0.6 ]
├── regex 2020.2.20 [ required: Any ]
├── toml 0.10.0 [ required: >=0.9.4 ]
└── typed-ast 1.4.1 [ required: >=1.4.0 ]
bump2version 1.0.0

PyPI インデックス URL を設定する

PyPI ミラー URL を以下のように設定できます。

$ pdm config set pypi.url https://testpypi.org/simple

デフォルトでは、PyPI URL を pip の設定ファイルから読み込みますが、見つからない場合は https://pypi.org/simple に設定します。

パッケージのソースを追加する

PyPI もしくはそのミラーよりプライベートなリポジトリからパッケージをインストールしたい場合には、その場所を pyproject.toml に保存し、デプロイ時にプロジェクトと一緒に同梱する必要があります。

[[tool.pdm.source]]
url = "http://example.com/private/index"
verify_ssl = false  # Don't verify SSL, it is required when you are using `HTTP` or the certificate is trusted.
name = "private"

プレリリースバージョンのインストールを許可する

プレリリースバージョンのインストールを許可するには、以下の設定を pyproject.toml に保存します。

[[tool.pdm]]
allow_prereleases = true

環境変数の展開

PDM は、依存関係の指定のため環境変数の展開をサポートします。

  • URL の認証部分の環境変数が展開されます:

    https://${USERNAME}:${PASSWORD}/artifacts.io/Flask-1.1.2.tar.gz . URL に直接 auth 部分を指定しなくても問題ありません。-v/--vervose オプションが指定されていれば、PDM は auth 部分を要求します。

  • ${PROJECT_ROOT} は、POSIX スタイル(i.e. 前方スラッシュ /, Windows でも) でプロジェクトルートの絶対パスに拡張されます。一貫性のため、${PROJECT_ROOT} の下のローカルパスを参照する URL は file:/// (3 つのスラッシュ) で始まる必要があります。e.g. file:///${PROJECT_ROOT}/artifacts/Flask-1.1.2.tar.gz.

資格情報の漏洩について心配する必要はありません。環境変数は必要に応じて展開され、ロックファイルでは変更されません。

プロジェクトの管理

PDM は PEP 517 ビルドバックエンドとして動くことができます。有効化するには、下記のように pyproject.toml に追記してください。プロジェクト作成時に pdm init を使った場合にはデフォルトで書き込まれています。

[build-system]
requires = ["pdm-pep517"]
build-backend = "pdm.pep517.api"

pip コマンドはバックエンド設定を読み込み、パッケージのインストールやビルドを行います。

Python インタープリタを選ぶ

pdm init を使った場合、PDM がどうやって Python インタープリタを検出し選択するか、すでにご存知のはずです。初期化後、pdm use <python_version_or_path> で設定を変更することもできます。この引数には任意の長さのバージョン指定子か、python インタープリタの相対あるいは絶対パスを指定できますが、Python インタープリタはプロジェクトファイル中の python_requires 制約に適合していなければいけません。

requires-pytyhon によるプロジェクトのコントロール

PDM は requires-python で指定した値に含まれる全てのバージョンの Python で動作するパッケージ候補を選択しようとします。例えば requires-python の値が >= 2.7 であったら、PDM はバージョン範囲が >= 2.7 のスーパーセットであるパッケージ foo の最新バージョンを探そうとします。

そのため、古いバージョンパッケージでロックされたくないなら、requires-python を適切に指定してください。

配布アーティファクトをビルドする

$ pdm build
- Building sdist...
- Built pdm-test-0.0.0.tar.gz
- Building wheel...
- Built pdm_test-0.0.0-py3-none-any.whl

アーティファクトはtwine によってPyPI にアップロードできます。pdm build --help で利用可能なオプションを確認できます。

現在の Python 環境を表示する

$ pdm info
Python Interpreter: D:/Programs/Python/Python38/python.exe (3.8.0)
Project Root:       D:/Workspace/pdm
                                                                                                                                   [10:42]
$ pdm info --env
{
  "implementation_name": "cpython",
  "implementation_version": "3.8.0",
  "os_name": "nt",
  "platform_machine": "AMD64",
  "platform_release": "10",
  "platform_system": "Windows",
  "platform_version": "10.0.18362",
  "python_full_version": "3.8.0",
  "platform_python_implementaiton": "CPython",
  "python_version": "3.8",
  "sys_platform": "win32"
}

プロジェクトを設定する

PDM の comfig コマンドは、git config のように動作します。ただし、設定の表示に --list は必要ありません。

現在の設定を表示するには、

$ pdm config

設定のうち一つを表示するには、

$ pdm config pypi.url

設定値を変更し、ホームの設定ファイルに保存するには、

$ pdm config pypi.url "https://testpypi.org/simple"

デフォルトでは、設定値はグローバルに設定されてしまうため、プロジェクトオンリーで設定するには --local フラグを追加します。

$ pdm config --local pypi.url "https://testpypi.org/simple"

ローカルな設定はプロジェクトルートディレクトリの .pdm.toml ファイルに保存されます。

設定ファイルは以下の順番で検索されます。

  1. <PROJECT_ROOT>/.pdm.toml - プロジェクトの設定

  2. ~/.pdm/config.toml - ホームの(グローバルな)設定

-g/--global オプションが使われた場合、最初に読み込まれる設定ファイルは ~/.pdm/global-project/.pdm.toml に置換されます。

全ての利用可能な設定は、Configration Page を参照してください。

グローバルなプロジェクトを管理する

グローバルな Python インタープリタの依存関係を追跡したい場合でも、PDM を -g/--global オプションとともに使えば簡単です。-g/--global オプションはほとんどのサブコマンドをサポートしています。

オプションが渡されると、~/.pdm/global-project がプロジェクトのディレクトリとして使用されます。このディレクトリはほとんど通常のプロジェクトと同じですが、pyproject.toml は自動で作成され、ビルド機能はサポートされません。このアイデアは、Haskell の stack から拝借しました。

しかし、stack とは異なり、PDM はデフォルトでは、ローカルプロジェクトが見つからない場合でもグローバルプロジェクトを使用しないようにしています。パッケージが間違った場所に配置されるとあまりよろしくないためです。-g/--global オプションを明示的に指定すれば、この機能を有効化できます。

もし、グローバルプロジェクトを ~/.pdm/global-project ではなく別のプロジェクトファイルで追跡したい場合、-g/--global の後に、別のプロジェクトパスを指定します。

グローバルプロジェクト使用中にremovesync --clean コマンドを使う場合は気をつけてください。システムにインストールされたパッケージを削除してしまうかもしれません。

virtualenv とともに使う

PDM はデフォルトでは PEP 582 をオススメしていますが、virtualenv 下にパッケージをインストールすることもできます。use_venvTrue に設定することで、下記条件の時、PDM は virtualenv を使用します。

  • virtualenv がすでに有効化されている
  • 有効な virtualenv のフォルダ がvenv, .venv, env のいずれか

また、use-venv がオンで、インタープリタのパスが venv-like なパスであった場合、PDM はその venv ディレクトリを再利用します。

既存のプロジェクトファイルから、プロジェクトメタデータをインポートする

Pipenv や Poetry といった他のパッケージマネージャを使っていた場合、PDM に移行するのは簡単です。PDM は import コマンドを用意しており、手動でプロジェクトを初期化することはありません。現在のサポートは、

  1. Pipenv の Pipfile
  2. Poetry の pyproject.toml のセクション
  3. Flit の pyproject.toml のセクション
  4. Pip で使われる requirements.txt

また、PDM プロジェクトがまだ初期化されていない場合、pdm initpdm install を実行する際に、PDM はインポート可能なファイルを自動検出できます。

ロックされたパッケージを別のフォーマットにエクスポートする

CI フローやイメージ構築プロセスを簡略化するため、pdm.lock をエクスポートすることもできます。現在は、requirements.txtsetup.py フォーマットのみをサポートしています。

$ pdm export -o requirements.txt
$ pdm export -f setuppy -o setup.py

資格情報を pyproject.toml から隠す

PyPI サーバへのログイン認証や、VCS[5] リポジトリのユーザー名やパスワードといった機微情報を使用する時が多々ありますが、そういった情報を pyproject.toml 中で公開したり git にアップロードすることは避ける必要があります。

PDM はこれを実現するために、いくつかの方法を用意しています。

  1. 認証情報を環境変数に格納しておき、URL に展開する

    [[tool.pdm.source]]
    url = "http://${INDEX_USER}:${INDEX_PASSWD}@test.pypi.org/simple"
    name = "test"
    verify_ssl = false
    
    [project]
    dependencies = [
      "mypackage @ git+http://${VCS_USER}:${VCS_PASSWD}@test.git.com/test/mypackage.git@master"
    ]
    

    環境変数は ${ENV_NAME} という形式でエンコードされなくてはいけません。他の形式はサポートされておりません。また、auth 部分のみが展開されます。

  2. PDM が -v/--verbose 付きで実行されていた場合に、認証情報が URL で与えられず、サーバが 401 レスポンスを返したならば、PDM はユーザー名とパスワードの入力を要求します。そうでない場合、何が起こったかを示すエラーを返し終了します。ユーザーは確認の質問の後、認証情報をキーリングに保存するかどうかを選択できます。

  3. VCS リポジトリは最初の方法(1)を適用し、インデックスサーバーは両方の方法を適用します。

隔離された環境でスクリプトを実行する

PDM を使えば、ローカルパッケージを読み込んだ状態で、任意のスクリプトやコマンドを実行できます。

$ pdm run flask run -p 54321

また、pyproject.toml 中の [tool.pdm.scripts] セクションでカスタムスクリプトショートカットをサポートしています。

pdm run <shortcut_name> でスクリプトを呼び出すことができます。例えば、

[tool.pdm.scripts]
start_server = "flask run -p 54321"

としておき、ターミナルで

[tool.pdm.scripts]
start_server = "flask run -p 54321"

コマンドに引数を追加することもできます。

$ pdm run start_server -h 0.0.0.0
Flask server started at http://0.0.0.0:54321

PDM は 3 タイプのスクリプトをサポートしています。

ノーマルコマンド

プレーンテキストスクリプトは通常のコマンドとしてみなされますが、明示的に指定することもできます。

[tool.pdm.scripts]
start_server = {cmd = "flask run -p 54321"}

パラメータの間にコメントを入れたい場合などは、文字列の代わりに配列を使ってコマンドを指定すると便利です。

[tool.pdm.scripts]
start_server = {cmd = [
    "flask",
    "run",
    # Important comment here about always using port 54321
    "-p", "54321"
]}

シェルスクリプト

パイプラインやリダイレクトといった、シェル特有のタスクを実行したい時にはシェルスクリプトを利用すると便利です。基本的に、shell=True とした subprocess.Popen() を介して実行されます。

[tool.pdm.scripts]
filter_error = {shell = "cat error.log|grep CRITICAL > critical.log"}

Python の関数を呼び出す

<module_name>:<func_name> という形式でPython の関数を呼ぶようにスクリプトを定義できます。

[tool.pdm.scripts]
foobar = {call = "foo_package.bar_module:main"}

関数にはリテラルの引数を与えることができます。

[tool.pdm.scripts]
foobar = {call = "foo_package.bar_module:main('dev')"}

環境変数のサポート

実行中のシェルでセットされた環境変数は全て pdm run で見ることができ、実行時に展開されます。また、 pyproject.toml で固定の環境変数を定義できます。

[tool.pdm.scripts]
start_server.cmd = "flask run -p 54321"
start_server.env = {FOO = "bar", FLASK_ENV = "development"}

TOML's syntax を使って複合辞書を定義していることに注意してください。

env_file = "<file_path>"` の設定で dotenv ファイルもサポートされています。

全てのスクリプトで共有される環境変数や dotenv ファイルについては、tool.pdm.scripts テーブルの _ という特別なキーに続く envenv_file で設定できます。

[tool.pdm.scripts]
_.env_file = ".env"
start_server = "flask run -p 54321"
migrate_db = "flask db upgrade"

スクリプトショートカット一覧を表示する

pdm run --list/-l で利用可能なスクリプトショートカットの一覧を表示できます。


$ pdm run --list
Name        Type  Script           Description
----------- ----- ---------------- ----------------------
test_cmd    cmd   flask db upgrade
test_script call  test_script:main call a python function
test_shell  shell echo $FOO        shell command

キャッシュの管理

キャッシュ管理のための便利なコマンドグループを提供します。4 種類のキャッシュが存在しています。

  1. wheels/ : non-wheel な配布物、ファイルのビルド結果を格納します
  2. http/ : HTTP のレスポンスコンテンツを格納します
  3. metadata/ : リゾルバが取得したパッケージメタデータを格納します
  4. hashes/ : パッケージのインデックスから取得したハッシュやローカルで計算したハッシュを格納します

現在のキャッシュの使用状況を確認するには pdm cache info を使ってください。また、キャッシュコンテンツの管理のため、add, remove, list といったサブコマンドも利用できます。使い方はそれぞれのコマンドで --help オプションを指定すれば確認できます。

どうやって Python インタープリタに PEP 582 パッケージを利用可能にさせたか

Python のスタートアップ時に site-packages がロードされるようになったおかげです。PDM に同梱されている sitecustomize.py を実行することで、 sys.path にパッチを当てることができます。インタープリタはディレクトリを探索して最も近傍の __pypackage__ フォルダを探し、それを sys.path に追加します。

プロジェクトファイルのシンタックス

プロジェクトメタデータ

PDM は PEP 621 の標準形式に基づいてプロジェクトメタデータを読み込みます。詳細は PEP をみてください。

このドキュメントの以下の部分では、[project] テーブルが明示的に与えられていない場合、[project] テーブルの下に書かれていると考えててください

パッケージのバージョンを動的に判断する

version = {from = "pdm/__init__.py"} のように、 フィールドにファイルソースを指定できます。この形式では、バージョンはそのファイル中の __version__ 変数の値が読み込まれます。

PDM は SCM[6] タグからバージョンを読み込むことができます。git や hg といったバージョンコントロールシステムを使っているなら、[version] にこんな感じに定義してください。

version = {use_scm = true}

いずれの場合でも、dynamic フィールドに version を含めなければなりません。そうでなければバックエンドがエラーになります。

dynamic = ["version"]

パッケージファイルのインクルード、エクスクルード

ファイルのインクルード、エクスクルードはグロブパターンを使ってシンプルに指定できます。

includes = [
    "**/*.json",
    "mypackage/",
]
excludes = [
    "mypackage/_temp/*"
]

includesexcludes のどちらも与えられていない場合、PDM はトップレベルパッケージとその中の全てのデータファイルをインクルードします。PDM が見つけられるように、パッケージを src ディレクトリに配置することもできます。

パッケージ検索のためのパッケージディレクトリ選択

setuptoolspackage_dir 設定のように、別のパッケージディレクトリを指定することができます。pyproject.tomlsrc のように簡単にディレクトリを指定できます。

package-dir = "src"

パッケージディレクトリが与えられない場合、以下の条件で暗黙に srcpackage-dir として認識します。

  1. src/__init__.py が存在しない場合、つまり有効な Python パッケージでないという意味
  2. src/* の下に幾つかのパッケージが存在する

暗黙のネームスペースパッケージ

PEP 420 に指定されている通り、以下の場合にディレクトリはネームスペースパッケージとして認識されます。

  1. <package>/__init__.py が存在せず、
  2. <package>/* の下に、通常のパッケージや他のネームスペースパッケージが存在する場合
  3. <package>package-dir として指定されない

依存関係指定

project.dependenciesPEP 404PEP 508 に従う、依存関係指定文字列配列です。

例えば

dependencies = [
    # Named requirement
    "requests",
    # Named requirement with version specifier
    "flask >= 1.1.0",
    # Requirement with environment marker
    "pywin32; sys_platform == 'win32'",
    # URL requirement
    "pip @ https://github.com/pypa/pip.git@20.3.1"
]

editable な要件

通常の依存関係指定の他に、エディタブルモードでパッケージをインストールすることができます。エディタブル指定形式は、Pip's editable install mode と同じです。

例えば

dependencies = [
    ...,
    "-e path/to/SomeProject",
    "-e git+http://repo/my_project.git#egg=SomeProject"
]

エディタブルインストールについて

同じパッケージでも、エディタブルモードと通常モードのインストールがあります。最後に指定したモードが優先されます。しかし、エディタブルな依存関係は有効な PEP 508 文字列ではないのでビルドアーティファクトのメタデータには含まれません。開発目的にのみ存在することになります。

任意の依存関係

setuptoolsextras_require パラメータと同様、幾つかの要件をオプションにすることができます。

[project.optional-dependencies]
socks = [ 'PySocks >= 1.5.6, != 1.5.7, < 2' ]
tests = [
  'ddt >= 1.2.2, < 2',
  'pytest < 6',
  'mock >= 1.0.1, < 4; python_version < "3.4"',
]

任意の依存関係グループをインストールするには、

$ pdm install -s socks

-s オプションを複数回し丁すると、1 グループ以上含めることができます。

開発のための依存関係

package.jsondev-dependencies と同様、開発のための依存関係も指定可能です。

[project]

dev-dependencies = [
    "pytest",
    "flake8",
    "black"
]

インストールするには、

$ pdm install -d

コンソールスクリプト

[project.scripts]
mycli = "mycli.__main__:main"

この指定はsetuptools スタイルに翻訳されます

entry_points = {
    'console_scripts': [
        'mycli=mycli.__main__:main'
    ]
}

また、[project.gui-scripts]setuptools スタイルの gui_scripts エントリーポイントグループに翻訳されます。

エントリーポイント

エントリーポイントのスタイルは、[project.entry-points.<type>] セクションで [project.scripts] と同じ形式で指定できます。

[project.entry-points.pytest11]
myplugin = "mypackage.plugin:pytest_plugin"

C 拡張をビルドする

現在でも、C 拡張のビルドは setuptools に依存しています。setup() のパラメータの辞書を唯一の引数として受け取る build という関数を含む python スクリプトを作成してください。関数内の ext_module 設定で辞書を更新します。

MarkupSafe から引用した例はこちら。

# build.py
from setuptools import Extension

ext_modules = [Extension("markupsafe._speedups", ["src/markupsafe/_speedups.c"])]

def build(setup_kwargs):
    setup_kwargs.update(ext_modules=ext_modules)

そして、project.tomlbuild にビルドスクリプトパスを指定してください。

# pyproject.toml
[project]
build = "build.py"

発展的な使い方

Tox による自動テスト

Tox は複数の Python バージョンや依存関係のセットに対してテストを行う偉大なツールです。以下のように、tox.ini 中でPDM を使ったテストを埋め込むことができます。

[tox]
env_list = py{36,37,38},lint
isolated_build = true
passenv = PDM_IGNORE_SAVED_PYTHON=1

[testenv]
deps = pdm
commands =
    pdm install --dev
    pytest tests

[testenv:lint]
deps = pdm
commands =
    pdm install -s lint
    flake8 src/

Tox によって作られた virtualenv を使う場合、pdm config use_venv ture としていなければいけません。PDM は pdm.lock から virtualenv に 依存関係をインストールします。専用の venv 環境であれば、pdm run pytest tests/ の代わりに pytest tests/ で直接ツールを実行できます。

テストコマンドにおいて、pdm add/pdm remove/pdm update/pdm lock を実行しないように。そうでないと、pdm.lock ファイルが予期せず変更されてしまいます。

追加の依存関係は deps 設定で行えます。また、PDM を正常に動作させるために、上記で示したように、isolated_buildpassenv の設定を行ってください。

このような制約を容易に解消するたに、Tox プラグインである tox-pdm が用意されています。インストールは

$ pip install tox-pdm

または

$ pdm add --dev tox-pdm

すると、以下のように tox.ini をより簡潔に記述できます。

[tox]
env_list = py{36,37,38},lint

[testenv]
sections = dev
commands =
    pytest tests

[testenv:lint]
sections = lint
commands =
    flake8 src/

詳細なガイダンスはproject's README を参照してください。

Nox を使った自動テスト

Nox もまた、自動テストのための偉大なツールです。tox と異なり、Nox は設定に標準の Python ファイルを用います。

Nox で PDM を使用するのはとても簡単で、以下に noxfile.py を示します。

import os
import nox

os.environ.update({"PDM_IGNORE_SAVED_PYTHON": "1"})

@nox.session
def tests(session):
    session.run('pdm', 'install', '-s', 'test', external=True)
    session.run('pytest')

@nox.session
def lint(session):
    session.run('pdm', 'install', '-s', 'lint', external=True)
    session.run('flake8', '--import-order-style', 'google')

PDM が virtualenv の Python を正しく選択できるように、PDM_IGNORE_SAVED_PYTHON を設定してください。また、pdmPATH に含まれていることを確認してくだい。nox を実行する前に、venv の再利用の有効化のため pdm confing use_venvtrue に設定してください。

継続的インテグレーションにおける PDM の使用

PDM は Python < 3.7 ではインストールできないので、もしプロジェクトを Python 3.7 以下でテストする場合、PDM が正しい Python のバージョンでインストールされているか確認する必要があります。特定のジョブやタスクが実行されるターゲットの Python のバージョンとは異なります。

もし、GitHub Action を使用しているならば、pdm-project/setup-pdm がこのプロセスを簡単に実行できます。GitHub Actions のワークフローを以下に示しますが、他の CI プラットフォームにも適用可能です。

Testing:
  runs-on: ${{ matrix.os }}
  strategy:
    matrix:
    python-version: [3.6, 3.7, 3.8, 3.9]
    os: [ubuntu-latest, macOS-latest, windows-latest]

  steps:
    - uses: actions/checkout@v1
    - name: Set up PDM
      uses: pdm-project/setup-pdm@v1.1
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install dependencies
      run: |
        pdm sync -d -s testing
    - name: Run Tests
      run: |
        pdm run -v pytest tests

Tips
GitHub Action ユーザーは、Ubuntu 仮想環境下での 既知の互換性の問題 [7] が存在します。そのマシンで PDM のパラレルインストールが失敗する場合、paralell_installfalse にするか、環境変数 LD_PRELOAD=/lib/x86_64-linux-gnu/libgcc_s.so.1 を設定してください。この問題は、pdm-project/setup-pdm アクションでは対応済みとなっています。

設定

利用可能な設定

設定 説明 デフォルト プロジェクトで利用可能か 環境変数
cache_dir キャッシュファイルのルートディレクトリ OS のデフォルト位置 No
auto_global ローカルプロジェクトが存在しない場合、暗黙にグローバルパッケージを使用するか False No PDM_AUTO_GLOBAL
use_venv PEP 582 の代わりに、アクティベートされた venv 環境の site-packages にパッケージをインストールする False Yes PDM_USE_VENV
parallel_install インストールやアンインストールをパラレルに実行するかどうか True Yes PDM_PARALLEL_INSTALL
python.path Python インタープリタパス Yes PDM_PYTHON_PATH
python.use_pyenv pyenv インタープリタを使う True Yes
pypi.url PyPI ミラーの URL pip.confindex-url を読み込むか、見つからなければ https://pypi.org/simple を使用する Yes PDM_PYPI_URL
pypi.verify_ssl PyPI への問い合わせ時に SSL 証明書を確認する pip.conftrusted-hosts を読み込む。デフォルトは True Yes
pypi.json_api パッケージメタデータについては PyPI の JSON API を参照してください False Yes PDM_PYPI_JSON_API
strategy.save パッケージが追加された際に、バージョンの保存方法を指定 compatible(または exact, wildcard) Yes
strategy.update パッケージのアップデートのデフォルトの挙動 reuse(または eager) Yes
strategy.resolve_max_rounds 解決プロセスの最大ラウンド数 1000 Yes PDM_RESOLVE_MAX_ROUNDS

対応する環境変数が設定されている場合、その値は設定ファイルに保存されているものよりも優先されます。

プラグインを書く

PDM はコミュニティードリブンなパッケージマネージャを目指しています。フル機能のプラグインシステムを搭載しており、以下のようなことが可能です。

  • PDM の新しいコマンドを開発する
  • 既存の PDM のコマンドに新しいオプションを追加する
  • 追加の設定項目の読み込みによって、PDM の挙動を変更する
  • 依存関係解決やインストールプロセスをコントロールする

プラグインは何をすべきか

PDM プロジェクトは依存関係の管理とパッケージの公開にフォーカスしています。PDM に統合したい他の機能については独自のプラグインを作成し、スタンドアロンな PyPI プロジェクトとしてリリースすることが望ましいです。プラグインがコアプロジェクトにとって有益であると判断された場合、PDM 本体に吸収される可能性があります。

独自のプラグインを書く

以下のセクションでは、hello.name という設定を読み込む hello コマンドを追加する例を紹介します。

コマンドを書く

PDM の CLI モジュールは、ユーザーが簡単に「継承と修正」ができるように設計されています。新しいコマンドはこんな感じに書きます。

from pdm.cli.commands.base import BaseCommand

class HelloCommand(BaseCommand):
    """Say hello to the specified person.
    If none is given, will read from "hello.name" config.
    """

    def add_arguments(self, parser):
        parser.add_argument("-n", "--name", help="the person's name to whom you greet")

    def handle(self, project, options):
        if not options.name:
            name = project.config["hello.name"]
        else:
            name = options.name
        print(f"Hello, {name}")

まずは、pdm.cli.command.base.BaseCommand を継承した HolloCommand クラスを作りましょう。クラスには 2 つの主要な関数を実装します。

  • コマンドライン引数を追加するための、引数パーサーを唯一の引数として受け取る add_arguments()
  • サブコマンドがマッチした時に何かしらの動作をする handle()pass を書くと何もしません。 handleは 2 つの引数を受け取ります。pdm.project.Project オブジェクトと、 パースされた argmarse.Namespace オブジェクトです。

ドキュメントストリングはコマンドヘルプテキストとして使用され、 pdm --help で表示されます。

また、PDM のサブコマンドには 2 つのデフォルトオプションがあります。-v/--verbose 冗長表示レベルと-g/--global グローバルプロジェクトを有効化するオプションです。これらのオプションが必要でない場合、 クラスアトリビュートの argumentspdm.cli.options.Option オブジェクトのリストで上書きするか、デフォルトオプションを持たないように空のリストを設定してください。

class HelloCommand(BaseCommand):

    arguments = []

NOTE
デフォルトオプションは最初に呼び出され、次に add_arguments() が呼ばれます。

コアオブジェクトにコマンドを登録する

作成したプラグインプロジェクトのどこかに PDM コアオブジェクトをただ一つの引数として受け取る関数を書いてください。ファンクション名はどんなものでも構いません。

def hello_plugin(core):
    core.register_command(HelloCommand, "hello")

コマンドを登録するために、core.register_comman() を呼び出してください。第二引数はサブコマンドの名前で、任意です。サブコマンドの名前が渡されない場合、PDM は HelloCommandname アトリビュートを使用します。

新しい設定項目を追加する

最初のコードスニペットを思い出してください。hello.name コンフィグキーはコマンドラインから渡されない場合に参照されます。

class HelloCommand(BaseCommand):
    """Say hello to the specified person.
    If none is given, will read from "hello.name" config.
    """

    def add_arguments(self, parser):
        parser.add_argument("-n", "--name", help="the person's name to whom you greet")

    def handle(self, project, options):
        if not options.name:
            name = project.config["hello.name"]
        else:
            name = options.name
        print(f"Hello, {name}")

これまでは、pdm config get hello.name で設定値を確認しようとしても、有効なコンフィグキーではない、とエラーが発生していました。コンフィグ項目の登録も必要です。

from pdm.project.config import ConfigItem

def hello_plugin(core):
    core.register_command(HelloCommand, "hello")
    core.add_config("hello.name", ConfigItem("The person's name", "John"))

ConfigItem クラスは 4 つのパラメータを受け取ります。順番に、

  • description : 設定項目の説明
  • default : 設定項目のデフォルト値
  • global_only : 設定がグローバル設定でのみ有効化どうか
  • env_var : 設定値として読み込まれる環境変数の名前

その他のプラグインのポイント

コマンドや設定の他にも、PDM には上記の例ではカバーしきれないいくつかの機能があります。

  • core.project_class : プロジェクトオブジェクトのクラスを変更します。
  • core.repository_class : リポジトリオブジェクトのクラスを変更し、パッケージの候補やパッケージメタデータをどのように探すかを制御します。
  • core.resolver_class : 解決プロセスを制御するために、リゾルバクラスを変更します。
  • core.synchronizer_class : インストールプロセスを制御するために、synchronizer_class を変更します。
  • core.parser : 大元の 引数パーサーに引数を追加します。

PDM プラグイン開発のための Tips

プラグイン開発の際には、開発中のものをアクティベートしてプラグインして、コードが変更されたらアップデートされると嬉しいです。pip install -e . で通常実現可能です。或いは python setup.py develop でも可能です。伝統的な Python パッケージングの世界では setup.py がこの役目を負っています。しかし、PDM プロジェクトでは setup.py なんてありません。どのようにすれば良いでしょうか?

幸運なことに、PDM と PEP 582 により簡単に実現できます。まず、ドキュメントに従って PEP 582 をグローバルで有効化します。そして __pypackage__ ディレクトリに全ての依存関係をインストールするだけです。

$ pdm install -d

すると、全ての依存関係は、互換性のある Python インタープリタとともに、プラグインが含まれた状態で、エディタブルモードで利用可能になります。これは、再インストールすることなしに、コード変更が直ちに影響を及ぼすことを意味します。pdm 実行ファイルは Python インタープリタを使用しています。プラグインプロジェクト下で pdm を実行すると、開発中のプラグインは自動的に有効化されどのように動くかテストできます。これは、開発ワークフローに PEP 582 がどのように利益をもたらしているかを示すものです。

プラグインを公開する

プラグインが完成したら、PyPI で配布しましょう。 PDM のプラグインはエントリーポイントタイプによって発見されます。pdm.plugin エントリーポイントを作成し、プラグインが呼び出し可能になるようにしましょう (関数である必要はなく、callable オブジェクトであればなんでも構いません)。

PEP 621

# pyproject.toml

[project.entry-points.pdm]
hello = "my_plugin:hello_plugin"

setuptools

# setup.py

setup(
    ...
    entry_points={"pdm": ["hello = my_plugin:hello_plugin"]}
    ...
)

プラグインのアクティベート

エントリーポイントからプラグインがロードされるため、プラグインをインストールするだけで有効化されます。

pdm-hello としてプラグインが公開されたら、例えば pipx 経由でpdm をインストールをした場合には

$ pipx inject pdm pdm-hello

或いは pdmhomebrew でインストールした場合は

$ $(brew --prefix pdm)/libexec/bin/python -m pip install pdm-hello

または、pip installpdm をインストールした場合は

$ pip install --user pdm-hello

中心となる考え方は、pdm と同じ site-packages ディレクトリにプラグインをインストールする、ということです。

ターミナルで pdm --help を実行してみましょう。hello コマンドが追加されていることが確認でき、以下のように実行可能です。

$ pdm hello Jack
Hello, Jack

PDM にコントリビュートする

まず最初に、コントリビュートしてくれることに感謝します。コントリビュートには以下のものが含まれますが、これに制限されるものではありません。

  • バグの報告
  • コードへのコントリビュート
  • テストの作成
  • ドキュメントの作成

以下は、コントリビュートについてのガイドラインです。

オープンソースプロジェクトへコントリビュートする際に推奨されるフロー

このガイドラインは OSS ビギナーへのガイドラインになっています。もし、あなたが OSS 開発者の経験があるなら、このセクションはスキップできます。

  1. 最初に、リポジトリページの右上のフォークボタンを押して、自身のネームスペースへプロジェクトをフォークしてください。

  2. ローカルに、上流の リポジトリをクローンしてください。

    $ git clone https://github.com/pdm-project/pdm.git
    # Or if you prefer SSH clone:
    $ git clone git@github.com:pdm-project/pdm.git
    
  3. フォークを新しいリモートに設定してください。

    $ git remote add fork https://github.com/yourname/pdm.git
    $ git fetch fork
    

    ここで、fork とあるのは、フォークリポジトリのリモートの名前です。

ProTips

  1. マスターブランチのコードを変更しないでください。マスターブランチは、常に origin/master を追跡する必要があります。

    マスターブランチを最新にアップデートするには、

    $ git pull origin master
    # In rare cases that your local master branch diverges from the remote master:
    $ git fetch origin && git reset --hard master
    
  2. アップデートしたマスターブランチを元に、パッチ作成のための新しいブランチを作成します。

  3. パッチのブランチからプルリクエストを作成します。

ローカルでの開発

テストスイートを正しく実行するには、Git LFS をインストールする必要があります。

$ git lfs install

次に、venv 環境に基本的な依存関係をインストールする必要があります。PDM は依存関係のインストールにローカルパッケージディレクトリを使うにも関わらず、最初の PDM のスタートアップには venv 環境が必要です。

$ python setup_dev.py

全ての依存関係が、ローカルの __pypackages__ ディレクトリにインストールされたので、この時点で開発環境として使用できます。__pypackage__/<VERSION>/bin に存在する pdm の実行ファイルは、エディタブルモードでインストールされている外側から直接実行することもできますし、venv 環境の中から python -m pdm として実行することもできます。

テストの実行

$ pdm run test

テストスイートはまだシンプルで追加する必要があるので、テストケースの作成にご協力ください。

コードスタイル

PDM はリントに pre-commit を使用するので、まずはこれをインストールします。

$ pre-commit install
$ pdm run lint

PDM はblack コーディングスタイルとインポート宣言に isort を使用します。もしこれに従わなければ CI は失敗し、プルリクエストはマージされません。

ドキュメントのプレビュー

もし docs/ を変更をし、ビルド結果を確認したいなら、以下を実行してください。

$ pdm run doc
脚注
  1. PEP 582で仮想環境が不要になる? ↩︎

  2. Python プロジェクトをビルドするビルドシステムを定義する方法 ↩︎

  3. いわゆる pyproject.toml ↩︎

  4. Python製のCLIアプリのインストールはpipx使っとけばOK (2020年時点) ↩︎

  5. version control system ↩︎

  6. source control management ↩︎

  7. 404 だった ↩︎

Discussion

ログインするとコメントできます