こんにちは。
本記事は株式会社インティメート・マージャーのAdvent Calendar 2023の7日目の記事になります。
突然ですがみなさん、Poetry使ってますか?とても便利ですよね!
アドベントカレンダーの2日目の記事でも出てきた話題ですが、今回はそのPoetryを使っているとよく違いが分からなくなるDependency groupとExtrasという概念について、環境構築の仕方から備忘録的な意味も込めて軽く記事にまとめたので、Poetryを使ってアプリケーション開発をする際の参考にしていただければと思います!
忙しい人のため
- Dependency group
- パッケージ開発時に使うもの
- poetryでしかインストール出来ない
- 例)
pytestやruff (flake8)などのツール
- Extras
- エンドユーザーがpipインストール時にも指定出来るようにするためのもの
- 例)
pip install fastapi[standard]などの[]内の設定
前提
- Ubuntu: 22.04.2 LTS
- Docker: 24.0.7, build afdd53b
- VSCode: 1.84.2
- DevContainer: v0.321.0
- Poetry: 1.7.1
Poetryとは
公式ドキュメントによると、PoetryとはPythonの「依存管理」と「パッケージング」のためのツールとあります。
Poetry is a tool for dependency management and packaging in Python.
今回の記事では主に「依存管理」の用途で使うので、パッケージングについてあまり詳しくは触れません。(パッケージングに関しては、少し古い記事になりますが以下の記事が詳しくまとまっていると思います。)
環境構築
今回はアドベントカレンダーの2日目の記事に近い形でVSCode+DevContainer+Poetryを使って環境を構築していきたいと思います。
実は最低限のpyproject.tomlファイルさえあればDockerfileが無くても開発が出来ます(もちろんDevContainerを使うのでdockerコマンド自体は必要です)。
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
{
"python.analysis.extraPaths": [
"/usr/local/py-utils/venvs/poetry/lib/python3.11/site-packages",
],
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true
}
{
"name": "Python 3",
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
"postCreateCommand": "pipx install poetry && poetry install --no-root",
"waitFor": "postCreateCommand",
"containerUser": "vscode",
"containerEnv": {
"PYTHONUNBUFFERED": "1",
"PYTHONDONTWRITEBYTECODE": "1",
"POETRY_VERSION": "1.7.1",
"POETRY_HOME": "/etc/poetry",
"POETRY_NO_INTERACTION": "1",
"POETRY_VIRTUALENVS_CREATE": "false"
},
"customizations": {
"vscode": {
"extensions": ["ms-python.python"]
}
}
}
※最新のDevContainerであればmount等の設定を書かなくてもデフォルトでプロジェクトフォルダが/workspaces/$projectにマウントされます。
動かしてみる
上記の設定でDevContainerを立ち上げてアタッチすると以下のような構成でlockファイルが新たに作成された状態で開発環境が構築されます。
.
├── .devcontainer
│ └── devcontainer.json
├── .vscode
│ └── setting.json
├── poetry.lock
└── pyproject.toml
あとはapp/main.pyなどを作成して、poetry runコマンドで実行してあげることでpythonのコードを実行することができます。
$ mkdir app
$ echo 'print("Hello, World!")' > app/main.py
$ poetry run python app/main.py
Hello, World!
パッケージインストール
まだこのままだと標準ライブラリしか入っていないため、外部ライブラリを使うことが出来ません。
Poetryではライブラリを追加するために、以下の方法があります。
-
pyproject.tomlファイルを直接編集する方法 -
poetry addコマンドで追加する方法
1つ目のpyproject.tomlファイルを編集する方法では、poetry lockコマンド等を実行しないとpoetry.lockファイルに反映されないためpoetry addコマンドをお勧めします。
ここでは実際にpoetry addコマンドでpendulumを追加してみます。
$ poetry add pendulum
Using version ^2.1.2 for pendulum
Updating dependencies
Resolving dependencies... (0.6s)
Package operations: 4 installs, 0 updates, 0 removals
• Installing six (1.16.0)
• Installing python-dateutil (2.8.2)
• Installing pytzdata (2020.1)
• Installing pendulum (2.1.2)
Writing lock file
$ cat << EOS > app/main.py
> import pendulum
>
> print(pendulum.Date.today())
> EOS
$ poetry run python app/main.py
2023-12-07
今日の日付が出力されていることから正しく追加されていることが分かります。
このときpyproject.tomlファイルには以下のように依存関係が追記されます。
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
+ pendulum = "^2.1.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Dependency groupsとExtras
ここで先ほどのpoetry addコマンドのオプションを見てみると、--group,--extras,--optionalというオプションが見つかります。
--group (-G): The group to add the dependency to.
--extras (-E): Extras to activate for the dependency. (multiple values allowed)
--optional: Add as an optional dependency.
optionalは一旦置いておき、groupとextrasの違いは一体何なのか見てみましょう。これは公式ドキュメントに書いてあり、
Dependency groups, other than the implicit main group, must only contain dependencies you need in your development process. Installing them is only possible by using Poetry.
To declare a set of dependencies, which add additional functionality to the project during runtime, use extras instead. Extras can be installed by the end user using pip.
とのことで、つまり以下ような違いがあります。
- group
- poetryからのみインストール出来る
- 開発時に使うもの
- extras
- poetry、pipインストール時にも指定出来る
- 主にエンドユーザーが使うもの
言い換えると、groupには開発時にテスト環境のみにインストールしたいpytestやflake8などを指定し、extrasにはプラグインのようなインストール時に取捨選択させたいものを指定するのが正しい使い方になります。
poetry addコマンド実行結果
group
実際にgroupに追加するコマンドを実行してみると以下のようになります。
$ poetry add --group dev pytest
Using version ^7.4.3 for pytest
Updating dependencies
Resolving dependencies... (0.7s)
Package operations: 3 installs, 0 updates, 0 removals
• Installing iniconfig (2.0.0)
• Installing pluggy (1.3.0)
• Installing pytest (7.4.3)
Writing lock file
最新のPoetryではpoetry addコマンドの--dev (-D)オプションは非推奨になっています。
pyoroject.tomlファイルには[tool.poetry.group.<group名>.dependencies]というテーブルが追加されます。
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
pendulum = "^2.1.2"
+ [tool.poetry.group.dev.dependencies]
+ pytest = "^7.4.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
extras
extrasについてはgroupへの追加とは少し異なり、cryptoというextrasにcryptographyを追加しようとしてgroupのときと同様に以下のコマンドを実行すると、
$ poetry add --extras crypto cryptography
Using version ^41.0.7 for cryptography
Updating dependencies
Resolving dependencies... (0.6s)
No dependencies to install or update
Writing lock file
以下のように、pyproject.tomlファイルのメインの依存関係[tool.poetry.dependencies]に特殊な形でcryptography[crypto]をインストールする設定として追加されます。
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
pendulum = "^2.1.2"
+ cryptography = {version = "^41.0.7", extras = ["crypto"]}
[tool.poetry.group.dev.dependencies]
pytest = "^7.4.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
実際にpoetry add cryptography[crypto]でも同じように追記されます。
ではどうすればextrasに追加出来るかというと、ドキュメントにある通りoptionalというものを使います。
Poetry supports extras to allow expression of:
- optional dependencies, which enhance a package, but are not required; and
- clusters of optional dependencies.
一旦、poetry removeコマンドで余計に入れたものを取り除いておきます。
$ poetry remove cryptography
Updating dependencies
Resolving dependencies... (0.2s)
Package operations: 0 installs, 0 updates, 3 removals
• Removing cffi (1.16.0)
• Removing cryptography (41.0.7)
• Removing pycparser (2.21)
Writing lock file
正しくextrasに設定するには、まずpoetry addコマンドでメインの依存関係にoptionalな設定を追加します。
$ poetry add --optional cryptography
Using version ^41.0.7 for cryptography
Updating dependencies
Resolving dependencies... (0.3s)
No dependencies to install or update
Writing lock file
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
pendulum = "^2.1.2"
+ cryptography = {version = "^41.0.7", optional = true}
[tool.poetry.group.dev.dependencies]
pytest = "^7.4.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
そしてpyproject.tomlを直接編集して[tool.poetry.extras]の設定を追加します。
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.11"
pendulum = "^2.1.2"
cryptography = {version = "^41.0.7", optional = true}
[tool.poetry.group.dev.dependencies]
pytest = "^7.4.3"
+
+ [tool.poetry.extras]
+ crypto = ["cryptography"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
追加したらpoetry lockコマンドでpoetry.lockファイルも更新しておきます。
$ poetry lock
Updating dependencies
Resolving dependencies... (0.4s)
Writing lock file
実際にインストールしてみる
最後にpip installコマンドでextrasが正しく設定出来ていることを確認しておきます。
まずpoetry buildコマンドでここまでのコードをパッケージングします。
$ poetry build
Building app (0.1.0)
- Building sdist
fatal: not a git repository (or any parent up to mount point /workspaces)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
- Built app-0.1.0.tar.gz
- Building wheel
fatal: not a git repository (or any parent up to mount point /workspaces)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
- Built app-0.1.0-py3-none-any.whl
成果物はdist/ディレクトリに作成されるので、pip installコマンドでインストールします。
$ pip install dist/app-0.1.0.tar.gz
Defaulting to user installation because normal site-packages is not writeable
Processing ./dist/app-0.1.0.tar.gz
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Collecting pendulum<3.0.0,>=2.1.2 (from app==0.1.0)
Downloading pendulum-2.1.2.tar.gz (81 kB)
...
$ pip list
Package Version
--------------- -------
app 0.1.0
pendulum 2.1.2
pip 23.2.1
python-dateutil 2.8.2
pytzdata 2020.1
setuptools 68.2.2
six 1.16.0
wheel 0.41.3
pip install dist/app-0.1.0.tar.gz[crypto]も実行してみると違いが分かります。(pip uninstallコマンドではarchiveファイルパスが指定出来ないので、DevContainerにアタッチし直すなどしてお試しください。)
$ pip install dist/app-0.1.0.tar.gz[crypto]
Defaulting to user installation because normal site-packages is not writeable
Processing ./dist/app-0.1.0.tar.gz
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Collecting cryptography<42.0.0,>=41.0.7 (from app==0.1.0)
Obtaining dependency information for cryptography<42.0.0,>=41.0.7 from https://files.pythonhosted.org/packages/62/bd/69628ab50368b1beb900eb1de5c46f8137169b75b2458affe95f2f470501/cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl.metadata
Downloading cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl.metadata (5.2 kB)
...
$ pip list
Package Version
--------------- -------
app 0.1.0
cffi 1.16.0
cryptography 41.0.7
pendulum 2.1.2
pip 23.2.1
pycparser 2.21
python-dateutil 2.8.2
pytzdata 2020.1
setuptools 68.2.2
six 1.16.0
wheel 0.41.3
これでextrasが正しく設定されていることが分かりました。
まとめ
本記事ではPoetryのDependency groupとExtras周りの違いや扱い方などを実際に触れながら見てきました。
Dependency groupに関してはテスト環境などを分離するために使えて、Extrasは機能を分けるために使えるというものでした。
このExtrasを上手く使うことでただのパッケージングだけでなく、Monorepoでの開発を行ったり、一部機能を共有したサービスの開発などを一つのpyproject.tomlファイルで管理したりなどすることも出来たりします。(Dependency groupにもoptional groupsというものがありこちらを使うのが正しそうですが、サービスが増えていくとどうしても記述が汚くなっていってしまうので悩ましいです。)
あとがき
ここまで読んでいただいてありがとうございました!
明日はstrapiというCMSについてのお話だそうです!
ぜひお楽しみに!