Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active January 21, 2024 03:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yano3nora/170ef9832407ef89b7e09d366fb6a978 to your computer and use it in GitHub Desktop.
Save yano3nora/170ef9832407ef89b7e09d366fb6a978 to your computer and use it in GitHub Desktop.
PDM - A modern Python package and dependency manager supporting the latest PEP standards. #python

Overview

github.com/pdm-project/pdm
pdm.fming.dev
PDMによるPythonのプロジェクト管理

相変わらず python の package 管理は地獄な中、わりとモダンな pep 582 の考え方から生まれていて、npm っぽいコマンド体系を持ってる。

pdm は仮想環境 .venv について、その管理と透過的なコマンド実行 (package add, remove, install, run など) を行ってくれるため、venv.backend に PEP 標準の venv を使い、以降は pdm 経由で package の管理や実行をすることで activate / deactivate を省略しつつ仮想環境を運用する のが、標準準拠で楽そうに見えてる。


Getting Started

Installation

# macOS
$ brew install pdm

# or Install via installer script
$ curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 -
$ echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.zshrc
$ exec $SHELL -l

本番環境で pip 使えるなら pip が、パス解決が面倒なら pipx がラク。

# Install via pip
$ pip install --user pdm
$ exec $SHELL -l

# Install via pipx
$ python3 -m pip install --user pipx
$ python3 -m pipx ensurepath
$ exec $SHELL -l
$ pipx install pdm

Settings

動作確認と個人的な初期設定 (任意) 。

# Check Version
$ pdm --version
> 2.6.1

# Update self
$ pdm self update

# To enable install cache
$ pdm config --global install.cache True

# Switch venv backend to official venv
$ pdm config --global venv.backend venv

# Using PEP 582
$ pdm --pep582

.gitignore

init 時に勝手に作ってくれるけど、既存プロジェクトに後から入れるとかで、あえて pdm 関連だけ抜き出すとこんな感じかな。

# pdm
.pdm.toml
.pdm-python
__pypackages__/

VSCode Setting

Using Python environments in VS Code

  • 前提、公式の python extension が必要
  • 当該プロジェクトを開いたら Python: Select interpreter.venv を選択
  • 以降は linter とかうまく動いてくれるはず

Usage

init

New Project

プロジェクトの初期化で pyproject.toml を対話で作ってくれる。以下のことに応える感じ。

  1. python 実体 (pyenv など) => 利用する python version や venv の作成先に利用
  2. .venv 作るかどうか => 基本作る一択だと思う
    • 1 の python cli を利用する前提の .venv を作成してくれる
    • 以降はその .venv に実際に依存を入れていく感じ
  3. ライブラリか否か (pypi 登録する系の project か) => default No なのでお好みで
$ pdm init

add, remove

npm 的な感覚で package add, remove してくと pyproject.tomlpdm.lock が更新されていく。

Manage Dependencies

# add
$ pdm add requests   # add requests
$ pdm add requests==2.25.1   # add requests with version constraint
$ pdm add requests[socks]   # add requests with extra dependency
$ pdm add "git+https://github.com/pypa/pip.git@22.0" # Install pip repo on tag `22.0`

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

# remove
$ pdm remove requests

install, sync, update

Install the packages pinned in lock file

  • install 系も npm ぽい、更新しながら pdm install と、lock 通りに入れる ci 用の pdm sync
  • package の version up & toml と lock の更新も pdm update とかで ok
  • .venv がない (= clone してきた直後状態など) ではこのタイミングで自動的に .venv の作成までやってくれる
    • ここで選択される python は自動選択されるっぽい?(未検証)
    • 手動選択したければ事前に pdm use で「 venv 作成・操作に使うメインの interpreter 」を選択するといいのかな
# install from pyproject.toml with update lock
$ pdm install

# installs packages from the lock file
$ pdm sync

# will update the lock file, then sync
$ pdm update

for Production

  • pip install とは違い venv に package を install することになる
  • そのため本番環境で pdm sync などを使う場合は 必ずエントリーポイントを pdm script 経由など、venv が activate される状態で開始しなければならないことに注意
  • または exportrequirements.txt 作ることも可能
$ pdm export -o requirements.txt

run (PDM Scripts)

User Scripts

npm scripts みたいに add した package に関する shell script を実行できる。

$ pdm add flask
$ pdm run flask run -p 54321

REPL

run に何も引数を渡さないと python interpreter の repl になる。

$ pdm run
> Python 3.x.x ...
> Type "help", "copyright", "credits" or "license" for more information.
>>>

scripts - User Script

# pyproject.toml

[tool.pdm.scripts]
start = "flask run -p 54321"
$ pdm run start
> Flask server started at http://127.0.0.1:54321

shell - Shell Command / Script

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

call - Python Script

# main.py

def helloworld(name):
    print(f"hello world {name}!")
[tool.pdm.scripts]
helloworld = {call = "main:helloworld(sys.argv[-1])"}
$ pdm run helloworld bob #=> hello world bob!

build (Publish to PyPI)

Build and Publish

使ってないので調べてないけど、pypi への publish とその準備用 build コマンドもある。

# Build & Publish
$ pdm publish

# Build and publish separately
$ pdm build
$ pdm publish --no-build

PEP 582 (__pypackages__)

Working with PEP 582
About PEP 582 __pypackages__ directory

npm の node_modules みたいなパッケージ管理ができるやつ。PEP 582 拒否されて非推奨ではあるが、現時点で python のパッケージ管理 & 仮想化まわりは標準があってないようなものなので、あんまり気にしてない。(pip, venv 以外はどれも同じくらい不安定)

  • PEP 582 機能 __pypackages__ を使う設定が global or project で構成可能
  • PEP 582 を有効にすると pdm add__pypackages__ に依存が入る
  • vscode で上記を .vscode/settings.json などで読み込んでやれば linter 解決できる
  • 格納される packages は python interpreter との紐づきはないため pyenv などで別途 python version 管理が必要

lambda の container image 開発 など、完全に docker で完結させる (local 実行をしない) 場合、仮想環境自体が不要 & vscode など editor 側で依存解決ができれば十分なケースでは有用に思う。

逆に local ではこれを使う利点はほぼないと思うので、以下 docker setup 時の構成だけメモする。

Set python.use_venv to false in Project

ざっと調べた感じ python.use_venv false である or __pypackages__ が存在すると PEP 582 プロジェクトとして認識されるよう。後者は ignore されるはずなので前者で構成し pdm.toml (pdm project setting file) で明示してあげる感じぽい。

# pdm.toml (git 管理) に仮想環境使わないよ設定 (pep 582 使うよ設定) が追加される
$ pdm config -l python.use_venv false
# pdm.toml (当然、このファイル置いとくだけでもよき)
[python]
use_venv = false
# ↑ pdm.toml 見て init 時に .venv が生成されなくなる
$ pdm init

# add, remove, sync なども __pypackages__ 依存になる
$ pdm add numpy

Dcokerized Python Project Example

例えば lambda の python image を使った開発で、container 内で生成した __pypackages__ を host os の vscode に読ませるような構成なら。

# https://hub.docker.com/r/amazon/aws-lambda-python
FROM public.ecr.aws/lambda/python:3.10 as base
COPY ./ ./
RUN pip install pdm # pdm いれる

# docker-compose x watchmedo で hot reload 開発用の stage を構成
#
# awslambdaric を含む依存を compose up 後に pdm sync することで
# __pypackages__ を host os に反映して vscode から読み取る想定で組まれている
#
FROM base as docker-compose
RUN pip install "watchdog[watchmedo]"
ENTRYPOINT ["watchmedo", "auto-restart", "-d", ".", "-p", "*.py", "--", "aws-lambda-rie", "pdm", "run", "python", "-m", "awslambdaric"]
CMD ["app.lambda_handler"]

# sam local でのテストや本番 deploy 用の stage を構成
#
# 本番も pdm sync にしてもいいけど、そうすると package 解決のために pdm run 経由で
# entrypoint を組む必要があるため、シンプルに global に pip install させる
#
FROM base as main
RUN pdm export -o requirements.txt # pdm.lock から requirements.txt 生成
RUN pip install -r requirements.txt
CMD ["app.lambda_handler"]

↑ みたいに docker 組んで docker-compose stage で docker-compose から build => up して、bash で入って python.use_env false な (= PEP 582 が有効な) 状態の pdm で pdm initpdm add していく。

docker-compose.yml はこんな感じ。(sam local 実行では、都度 build して変更を取り込むので、ここでは特に触れない)

# docker-compose.yml
services:
  lambda:
    ports:
      - "8080:8080"
    build:
      context: ./lambda/src
      dockerfile: Dockerfile
      target: docker-compose
    volumes:
      - ./lambda/src:/var/task
      # __pypackages__ は volume trick せず build => up 以降に sync して
      # host 側に持ってこさせて vscode など editor に依存を読み込ませる想定

dip で開発用のコマンド類や、開発用の provisioning script を組むならこう。

# dip.yml

interaction:
  lambda:
    service: lambda
    compose:
      # lambda 用 image は entrypoint を override しないと bash で入れない
      run_options: [tty, interactive, rm, entrypoint bash]
  pdm:
    service: lambda
    compose:
      run_options: [tty, interactive, rm, entrypoint pdm]

provision:
  - dip down
  - dip compose build
  - dip pdm sync
  - dip down

運用としては以下のような感じ。

# setup
$ git clone ...
$ dip provision # container build => up => pdm sync して __pypackages__ を local に

# repl console
$ dip pdm run

# add, remove package
$ dip pdm add numpy
$ dip pdm remove numpy

# re-install packages
$ dip pdm sync

最後に、vscode は python extension と、project 設定として .vscode/settings.json を以下のようにしてやれば、勝手に __pypackages__ 配下に依存を解決しにいってくれる。 __pypackages__/ から /lib までのパスは image や python version によるので bash で入って確認してやる。

{
  // for python extensions
  "python.autoComplete.extraPaths": [
    "./lambda/src/__pypackages__/3.10/lib"
  ],
  // for pylance
  "python.analysis.extraPaths": [
    "./lambda/src/__pypackages__/3.10/lib"
  ]
}
@alpineyahoo
Copy link

最近pdmを使い始めた者です。
初心者で申し訳ないのですが、.venvを使うのと__pypackages__を使うのとではどう違いますか?
あるいはそれぞれの長所短所を教えて頂くことはできますか?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment