Poetryを使ってみる

背景

以前の記事では、Dockerコンテナ上でPipenvを用いてパッケージ管理をしようとしていました。

利用していて、難点だったのが、仮想環境を用いずPipfile.lockを利用してパッケージをインストールできないという点でした。

--systemというオプションを指定しても、Pipfile.lockを利用する場合は以下のようになってしまっていました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker-compose run --rm web python -m pipenv sync --system      
Creating a virtualenv for this project...
Pipfile: /code/Pipfile
Using /usr/local/bin/python3 (3.10.0) to create virtualenv...
⠦ Creating virtual environment...created virtual environment CPython3.10.0.final.0-64 in 391ms
creator CPython3Posix(dest=/root/.local/share/virtualenvs/code-_Py8Si6I, clear=False, no_vcs_ignore=False, global=False)
seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/root/.local/share/virtualenv)
added seed packages: pip==21.3.1, setuptools==58.3.0, wheel==0.37.0
activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

✔ Successfully created virtual environment!
Virtualenv location: /root/.local/share/virtualenvs/code-_Py8Si6I
Installing dependencies from Pipfile.lock (d9bd7e)...
🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 15/15 — 00:00:02
All dependencies are now up-to-date!

しかし、やはりlockファイルを利用してバージョンを固定したいと思いました。後輩に相談してみると、poetryというパッケージ管理ツールがあるので試してみてはどうか?と教えてもらったので試してみます。

既存のパッケージの削除

パッケージはDocker volume内に残っていますので、一旦既存パッケージを削除します。

1
2
$ docker-compose run --rm web python -m pip freeze > pip_list
$ docker-compose run --rm web python -m pip uninstall -r pip_list -y

-yをつけることで、uninstallしていいかどうかの確認を省略できます。

アンインストールできたかを確認します

1
2
3
4
5
6
$ docker-compose run --rm web python -m pip list
Package Version
---------- -------
pip 21.3.1
setuptools 57.5.0
wheel 0.37.0

アンインストールできていることが確認できました。

poetryのインストール

ドキュメントを確認すると、いろんな仮想環境でpoetryを用いる場合は、インストーラーを使った方がよいと書いてあります。

しかし、わたしはDockerを利用するため、特定のPythonのバージョンに依存してよいかと思いますので、非推奨になっているpipを用いてインストールさせてもらいます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
$ docker-compose run --rm web python -m pip install poetry
Collecting poetry
Downloading poetry-1.1.11-py2.py3-none-any.whl (175 kB)
|████████████████████████████████| 175 kB 3.3 MB/s
Collecting cachy<0.4.0,>=0.3.0
Downloading cachy-0.3.0-py2.py3-none-any.whl (20 kB)
Collecting clikit<0.7.0,>=0.6.2
Downloading clikit-0.6.2-py2.py3-none-any.whl (91 kB)
|████████████████████████████████| 91 kB 4.6 MB/s
Collecting pkginfo<2.0,>=1.4
Downloading pkginfo-1.7.1-py2.py3-none-any.whl (25 kB)
Collecting crashtest<0.4.0,>=0.3.0
Downloading crashtest-0.3.1-py3-none-any.whl (7.0 kB)
Collecting html5lib<2.0,>=1.0
Downloading html5lib-1.1-py2.py3-none-any.whl (112 kB)
|████████████████████████████████| 112 kB 6.9 MB/s
Collecting requests-toolbelt<0.10.0,>=0.9.1
Downloading requests_toolbelt-0.9.1-py2.py3-none-any.whl (54 kB)
|████████████████████████████████| 54 kB 3.4 MB/s
Collecting poetry-core<1.1.0,>=1.0.7
Downloading poetry_core-1.0.7-py2.py3-none-any.whl (424 kB)
|████████████████████████████████| 424 kB 6.8 MB/s
Collecting shellingham<2.0,>=1.1
Downloading shellingham-1.4.0-py2.py3-none-any.whl (9.4 kB)
Collecting virtualenv<21.0.0,>=20.0.26
Downloading virtualenv-20.10.0-py2.py3-none-any.whl (5.6 MB)
|████████████████████████████████| 5.6 MB 7.8 MB/s
Collecting keyring<22.0.0,>=21.2.0
Downloading keyring-21.8.0-py3-none-any.whl (32 kB)
Collecting cachecontrol[filecache]<0.13.0,>=0.12.4
Downloading CacheControl-0.12.10-py2.py3-none-any.whl (20 kB)
Collecting pexpect<5.0.0,>=4.7.0
Downloading pexpect-4.8.0-py2.py3-none-any.whl (59 kB)
|████████████████████████████████| 59 kB 3.6 MB/s
Collecting tomlkit<1.0.0,>=0.7.0
Downloading tomlkit-0.7.2-py2.py3-none-any.whl (32 kB)
Collecting cleo<0.9.0,>=0.8.1
Downloading cleo-0.8.1-py2.py3-none-any.whl (21 kB)
Collecting requests<3.0,>=2.18
Downloading requests-2.26.0-py2.py3-none-any.whl (62 kB)
|████████████████████████████████| 62 kB 941 kB/s
Collecting packaging<21.0,>=20.4
Downloading packaging-20.9-py2.py3-none-any.whl (40 kB)
|████████████████████████████████| 40 kB 3.8 MB/s
Collecting msgpack>=0.5.2
Downloading msgpack-1.0.2.tar.gz (123 kB)
|████████████████████████████████| 123 kB 7.5 MB/s
Preparing metadata (setup.py) ... done
Collecting lockfile>=0.9
Downloading lockfile-0.12.2-py2.py3-none-any.whl (13 kB)
Collecting pylev<2.0,>=1.3
Downloading pylev-1.4.0-py2.py3-none-any.whl (6.1 kB)
Collecting pastel<0.3.0,>=0.2.0
Downloading pastel-0.2.1-py2.py3-none-any.whl (6.0 kB)
Collecting six>=1.9
Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting webencodings
Downloading webencodings-0.5.1-py2.py3-none-any.whl (11 kB)
Collecting jeepney>=0.4.2
Downloading jeepney-0.7.1-py3-none-any.whl (54 kB)
|████████████████████████████████| 54 kB 792 kB/s
Collecting SecretStorage>=3.2
Downloading SecretStorage-3.3.1-py3-none-any.whl (15 kB)
Collecting pyparsing>=2.0.2
Downloading pyparsing-3.0.5-py3-none-any.whl (97 kB)
|████████████████████████████████| 97 kB 5.9 MB/s
Collecting ptyprocess>=0.5
Downloading ptyprocess-0.7.0-py2.py3-none-any.whl (13 kB)
Collecting certifi>=2017.4.17
Using cached certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
Collecting charset-normalizer~=2.0.0
Downloading charset_normalizer-2.0.7-py3-none-any.whl (38 kB)
Collecting idna<4,>=2.5
Downloading idna-3.3-py3-none-any.whl (61 kB)
|████████████████████████████████| 61 kB 5.1 MB/s
Collecting urllib3<1.27,>=1.21.1
Downloading urllib3-1.26.7-py2.py3-none-any.whl (138 kB)
|████████████████████████████████| 138 kB 8.5 MB/s
Collecting distlib<1,>=0.3.1
Using cached distlib-0.3.3-py2.py3-none-any.whl (496 kB)
Collecting filelock<4,>=3.2
Downloading filelock-3.3.2-py3-none-any.whl (9.7 kB)
Collecting platformdirs<3,>=2
Using cached platformdirs-2.4.0-py3-none-any.whl (14 kB)
Collecting backports.entry-points-selectable>=1.0.4
Downloading backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl (6.2 kB)
Collecting cryptography>=2.0
Downloading cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl (3.5 MB)
|████████████████████████████████| 3.5 MB 8.5 MB/s
Collecting cffi>=1.12
Downloading cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (446 kB)
|████████████████████████████████| 446 kB 10.2 MB/s
Collecting pycparser
Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
|████████████████████████████████| 118 kB 8.8 MB/s
Building wheels for collected packages: msgpack
Building wheel for msgpack (setup.py) ... done
Created wheel for msgpack: filename=msgpack-1.0.2-cp310-cp310-linux_x86_64.whl size=15833 sha256=fee9473ca391ef73f69dde97df9a6829fdd66addbd0cfa04b1cc7cd40600d620
Stored in directory: /root/.cache/pip/wheels/c6/4d/b6/98ae445826f0944cb9a6a963bceb7259c3b7467fd4cb8df818
Successfully built msgpack
Installing collected packages: pycparser, urllib3, idna, charset-normalizer, cffi, certifi, requests, pylev, pastel, msgpack, jeepney, cryptography, crashtest, webencodings, six, SecretStorage, pyparsing, ptyprocess, platformdirs, lockfile, filelock, distlib, clikit, cachecontrol, backports.entry-points-selectable, virtualenv, tomlkit, shellingham, requests-toolbelt, poetry-core, pkginfo, pexpect, packaging, keyring, html5lib, cleo, cachy, poetry
Successfully installed SecretStorage-3.3.1 backports.entry-points-selectable-1.1.1 cachecontrol-0.12.10 cachy-0.3.0 certifi-2021.10.8 cffi-1.15.0 charset-normalizer-2.0.7 cleo-0.8.1 clikit-0.6.2 crashtest-0.3.1 cryptography-35.0.0 distlib-0.3.3 filelock-3.3.2 html5lib-1.1 idna-3.3 jeepney-0.7.1 keyring-21.8.0 lockfile-0.12.2 msgpack-1.0.2 packaging-20.9 pastel-0.2.1 pexpect-4.8.0 pkginfo-1.7.1 platformdirs-2.4.0 poetry-1.1.11 poetry-core-1.0.7 ptyprocess-0.7.0 pycparser-2.21 pylev-1.4.0 pyparsing-3.0.5 requests-2.26.0 requests-toolbelt-0.9.1 shellingham-1.4.0 six-1.16.0 tomlkit-0.7.2 urllib3-1.26.7 virtualenv-20.10.0 webencodings-0.5.1
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

たくさんのパッケージがインストールされました。確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ docker-compose run --rm web python -m pip list
Package Version
--------------------------------- ---------
backports.entry-points-selectable 1.1.1
CacheControl 0.12.10
cachy 0.3.0
certifi 2021.10.8
cffi 1.15.0
charset-normalizer 2.0.7
cleo 0.8.1
clikit 0.6.2
crashtest 0.3.1
cryptography 35.0.0
distlib 0.3.3
filelock 3.3.2
html5lib 1.1
idna 3.3
jeepney 0.7.1
keyring 21.8.0
lockfile 0.12.2
msgpack 1.0.2
packaging 20.9
pastel 0.2.1
pexpect 4.8.0
pip 21.3.1
pkginfo 1.7.1
platformdirs 2.4.0
poetry 1.1.11
poetry-core 1.0.7
ptyprocess 0.7.0
pycparser 2.21
pylev 1.4.0
pyparsing 3.0.5
requests 2.26.0
requests-toolbelt 0.9.1
SecretStorage 3.3.1
setuptools 57.5.0
shellingham 1.4.0
six 1.16.0
tomlkit 0.7.2
urllib3 1.26.7
virtualenv 20.10.0
webencodings 0.5.1
wheel 0.37.0

ショートカットも確認します

1
2
$ docker-compose run --rm web which poetry
/usr/local/bin/poetry

無事インストールできました。

プロジェクトの設定

公式のドキュメントを読むと、まずプロジェクトの設定を行なっています。

プロジェクトのディレクトリはすでに存在しているので、ドキュメントにあるこちらの方法で雛形を作成します。

現在プロジェクトのルートにいるので、そのままコマンドを実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$ docker-compose run --rm web poetry init

This command will guide you through creating your pyproject.toml config.

Package name [code]:
Version [0.1.0]:
Description []:
Author [None, n to skip]:
expected string or bytes-like object
Author [None, n to skip]: n
License []:
Compatible Python versions [^3.10]:

Would you like to define your main dependencies interactively? (yes/no) [yes]
You can specify a package in the following forms:
- A single name (requests)
- A name and a constraint (requests@^2.23.0)
- A git url (git+https://github.com/python-poetry/poetry.git)
- A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
- A file path (../my-package/my-package.whl)
- A directory (../my-package/)
- A url (https://example.com/packages/my-package-0.1.0.tar.gz)

Search for package to add (or leave blank to continue):

Would you like to define your development dependencies interactively? (yes/no) [yes]
Search for package to add (or leave blank to continue):

Generated file

[tool.poetry]
name = "code"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.10"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"


Do you confirm generation? (yes/no) [yes]

特になにも入力せずデフォルト値で作成しました。あとから記載する形でよいかと思います。

パッケージの追加

パッケージの追加はpoetry addコマンドで行います。もともと入っていたboto3をインストールしてみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker-compose run --rm web poetry add boto3
Creating virtualenv code-MATOk_fk-py3.10 in /root/.cache/pypoetry/virtualenvs
Using version ^1.20.1 for boto3

Updating dependencies
Resolving dependencies... (2.7s)

Writing lock file

Package operations: 11 installs, 0 updates, 0 removals

• Installing six (1.16.0)
• Installing jmespath (0.10.0)
• Installing python-dateutil (2.8.2)
• Installing urllib3 (1.26.7)
• Installing botocore (1.23.1)
• Installing certifi (2021.10.8)
• Installing charset-normalizer (2.0.7)
• Installing idna (3.3)
• Installing s3transfer (0.5.0)
• Installing boto3 (1.20.1)
• Installing requests (2.26.0)

virtualenvを作成してしまっているのでインストールされません…
virtualenvを利用しないようにします。

poetryの設定

poetryの設定はpoetry configコマンドで行います。

設定項目については、こちらに記載があります。

virtualenvを利用しないようにするには以下のようにします

1
$ docker-compose run --rm web poetry config virtualenvs.create false --local

--localのオプションがないと反映されませんでした)

実行後の設定を確認します

1
2
3
4
5
6
7
$ docker-compose run --rm web poetry config --list
cache-dir = "/root/.cache/pypoetry"
experimental.new-installer = true
installer.parallel = true
virtualenvs.create = false
virtualenvs.in-project = null
virtualenvs.path = "{cache-dir}/virtualenvs" # /root/.cache/pypoetry/virtualenvs

再度パッケージを追加してみる

ではこの設定でパッケージの追加を行います。同じようにboto3を追加してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker-compose run --rm web poetry add boto3
Skipping virtualenv creation, as specified in config file.
Using version ^1.20.3 for boto3

Updating dependencies
Resolving dependencies... (0.8s)

Package operations: 5 installs, 0 updates, 0 removals

• Installing jmespath (0.10.0)
• Installing python-dateutil (2.8.2)
• Installing botocore (1.23.3)
• Installing s3transfer (0.5.0)
• Installing boto3 (1.20.3)
$

インストール作業は完了しました。念のため、pip listコマンドで確認します

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ docker-compose run --rm web python -m pip list        
Package Version
--------------------------------- ---------
backports.entry-points-selectable 1.1.1
boto3 1.20.3
botocore 1.23.3
CacheControl 0.12.10
cachy 0.3.0
certifi 2021.10.8
cffi 1.15.0
charset-normalizer 2.0.7
cleo 0.8.1
clikit 0.6.2
crashtest 0.3.1
cryptography 35.0.0
distlib 0.3.3
filelock 3.3.2
html5lib 1.1
idna 3.3
jeepney 0.7.1
jmespath 0.10.0
keyring 21.8.0
lockfile 0.12.2
msgpack 1.0.2
packaging 20.9
pastel 0.2.1
pexpect 4.8.0
pip 21.3.1
pkginfo 1.7.1
platformdirs 2.4.0
poetry 1.1.11
poetry-core 1.0.7
ptyprocess 0.7.0
pycparser 2.21
pylev 1.4.0
pyparsing 3.0.5
python-dateutil 2.8.2
requests 2.26.0
requests-toolbelt 0.9.1
s3transfer 0.5.0
SecretStorage 3.3.1
setuptools 57.5.0
shellingham 1.4.0
six 1.16.0
tomlkit 0.7.2
urllib3 1.26.7
virtualenv 20.10.0
webencodings 0.5.1
wheel 0.37.0

正しくインストールされていることを確認しました。

Djangoもインストールしておきます

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker-compose run --rm web poetry add django
Skipping virtualenv creation, as specified in config file.
Using version ^3.2.9 for Django

Updating dependencies
Resolving dependencies... (1.7s)

Writing lock file

Package operations: 4 installs, 0 updates, 0 removals

• Installing asgiref (3.4.1)
• Installing pytz (2021.3)
• Installing sqlparse (0.4.2)
• Installing django (3.2.9)

devパッケージの追加

linterなどdevelopment環境でしか使用しないパッケージを追加します。

development dependencyに設定するには--devを指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker-compose run --rm web poetry add flake8 --dev
Skipping virtualenv creation, as specified in config file.
Using version ^4.0.1 for flake8

Updating dependencies
Resolving dependencies... (3.2s)

Writing lock file

Package operations: 4 installs, 0 updates, 0 removals

• Installing mccabe (0.6.1)
• Installing pycodestyle (2.8.0)
• Installing pyflakes (2.4.0)
• Installing flake8 (4.0.1)

インストールできました。pyproject.tomlを確認すると、tool.poetry.dev-dependenciesに記載されいていることがわかります。

1
2
[tool.poetry.dev-dependencies]
flake8 = "^4.0.1"

その他、isort, blackもインストールしておきます。

依存関係の設定

インストールしたパッケージはpoetry.lockに記載されています。これをバージョン管理することによって、いつでも環境を再現できるようになります。

再現するには

1
$ docker-compose run --rm web poetry install

とします。dev-dependenciesをインストールしたくない場合は--no-devをオプションに指定します。

まとめ

今回pythonのパッケージマネージャとしてpoetryを試してみました。Pipenvのときのような、lockファイルの作成でハマったりしなかったですし、とてもシンプルで使いやすいという印象でした。

今後しばらくはpoetryを使っていきたいと思います。