これまでpythonプロジェクトにおいては、pipでパッケージ管理しvenvで仮想環境を構築するというスタンスだったのですが、いい加減もっと便利なツールに乗り換えることにしました。
調べてみるとpipenvとpoetryという二つの良さそうな候補があり、ちょっと迷いましたがpoetryに決めました。
ドキュメントが分かりやすいので不要かもしれませんが、この記事では知っておいた方が良さそうな部分を抜粋して動作確認しつつ、poetryの使い方をまとめています。
poetryを選んだ理由
pipenvもpoetryも調べた範囲では、担当プロジェクトでやりたいことを考えると、機能的には不足を感じませんでしたが、先駆者の皆さんのブログを読むと、以下のような点でpoetryの方が優位性が高く、githubのstar数の推移を見ても、将来的にはpoetryがはやる可能性が高そうな気がするので、poetryを採用することにしました。
- poetryの方はPEP-0518で提案されたfile formatである
pyproject.toml
を使うのに対し、pipenvは独自のPipfile
を使う - パッケージ公開に必要な情報を、poetryは
pyproject.toml
に書けるが、pipenvは別でsetup.py
/setup.cfg
に書く必要がある - pipenvは
pipfile.lock
の生成にとても時間がかかるらしい - pipenvでは解決できない依存関係をpoetryは解決できるらしい
セットアップ
インストール
pipでインストール
昔はできなかったですが、いつのまにかpipでインストールできるようになってました。
pip install --user poetry
get-poetry.py
でインストール
curlで取ってきたget-poetry.py
をpythonコマンドで実行する形でインストールします。
pyenv local 3.7.5 # 3系のpythonで実行した方が無難そう
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
また、.bash_profile
や.bashrc
に以下を記載して、poetry
コマンドにPATHを通します。
export PATH=~/.poetry/bin:$PATH
poetryはユーザのHOMEディレクトリにインストールされるので、LINUXでもユーザ毎にインストールする想定のようです。
アンインストール
# get-poetry.pyをダウンロード
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py > get-poetry.py
# 以下のいずれかを実行
python get-poetry.py --uninstall
POETRY_UNINSTALL=1 python get-poetry.py
# 上記を実行すると、以下が行われているっぽい
rm -rf ~/.poetry
rm -rf ~/Library/Caches/pypoetry # macの場合
rm -rf ~/.cache/pypoetry # linuxの場合
アップデート
# 通常
poetry self update
# バージョン指定
poetry self update 1.0.9
# pre-releaseバージョンをinstallしたい場合
poetry self update --preview
TAB補完
こちらにTAB補完の定義の設定方法が載っています。
すでにシェルのTAB補完を使っている人は自分の使っているシェルに対応する物を選んで設定するといいと思います。
自分はbashを使っていますがタブ補完は使ってなかったのでそこから設定しました。
# poetryのTAB補完定義を出力
poetry completions bash > $(brew --prefix)/etc/bash_completion.d/poetry.bash-completion
### ここから下はpoetry関係ありません ###
# bashをbrewでinstall
brew install bash
# ログインシェルで指定可能なシェルを追加
sudo vi /etc/shells
+ /usr/local/bin/bash
# ログインシェルを変更
chsh -s /usr/local/bin/bash
# タブ補完機能追加
brew install bash-completion
# タブ補完の有効化
vi ~/.bash_profile
+ # bash-completions
+ if [ -f $(brew --prefix)/etc/bash_completion ]; then
+ source $(brew --prefix)/etc/bash_completion
+ fi
# ついでに、VSコードのTerminalの起動シェルもbrewのbashに変更
# Code > Preference > Settings > settings.json
+ "terminal.integrated.shell.osx": "/usr/local/bin/bash",
pyproject.tomlの記述
pyproject.toml
はプロジェクトの依存関係を統括する重要なファイルです。
ファイル作成
以下のコマンドでファイルを作成できます。
poetry init
依存関係を記載
本番で利用するものは[tool.poetry.dependencies]
に、テスト・開発時のみ利用するものは[tool.poetry.dev-dependencies]
に記載します。
[tool.poetry.dependencies]
python = "^3.7"
jinja2 = "^2.11.2"
pandas = "^0.25.3"
:
コマンドを使っても追加削除ができます。コマンドを使う場合、poetry.lock
にもそのタイミングで反映されます。
# dependenciesに追加
poetry add jinja2
# dev-dependenciesに追加
poetry add jinja2 --dev
# dependenciesから削除
poetry remove jinja2
# dev-dependenciesから削除
poetry remove jinja2 --dev
依存関係の解決
poetry install
poetry.lock
に記載されたパッケージをインストールするコマンドです。
ただし、poetry.lock
が存在しない場合だけ、pyproject.toml
を元に依存関係を解決してパッケージをインストールし、poetry.lock
を作成してくれます。
本番環境のデプロイ手順としては、このコマンドを利用することになります。また、開発時も他の人が更新したパッケージを反映する場合に利用します。
# 開発時
poetry install
# 本番
poetry install --no-dev
poetry update
pyproject.toml
を元に依存関係を解決・パッケージをインストールし、poetry.lock
を更新するコマンドです。
開発者が手でpyproject.toml
を変更した際には、commit前に忘れずにpoetry update
を実行しておく必要があります。
(普段からpoetry add
やpoetry remove
を使うようにしておけば、poetry.lock
も同時に更新されているので、あまり気にしなくて良さそう)
# 全部まとめて更新
poetry update
# 特定のパッケージだけ更新
poetry update jinja2 pandas
インストールされたパッケージの確認
現在インストールされているパッケージを確認するには、poetry show
を使います。
パッケージの概要やそのパッケージが依存するパッケージまで確認できるのがいいですね。
# リストアップ
poetry show
> boto 2.49.0 Amazon Web Services Library
> boto3 1.14.4 The AWS SDK for Python
> botocore 1.17.4 Low-level, data-driven core of boto 3.
> cachetools 4.1.0 Extensible memoizing collections and decorators
(省略)
# パッケージの詳細を確認
poetry show boto3
> name : boto3
> version : 1.14.4
> description : The AWS SDK for Python
>
> dependencies
> - botocore >=1.17.4,<1.18.0
> - jmespath >=0.7.1,<1.0.0
> - s3transfer >=0.3.0,<0.4.0
仮想環境の構築
poetryはすでに仮想環境がある場合はその仮想環境を使い、存在しなければ新たに仮想環境を作ります。
poetry installで依存関係解決のついでに構築
デフォルト(virtualenvs.create=true)では、poetry install
やpoetry add
した際に、仮想環境がすでに存在してなければ、自動的にpython
コマンドのバージョンを元に仮想環境が作成されます。
poetry install
> Creating virtualenv hoge-project-4FNRMnIZ-py3.8 in /Users/xxxxx/Library/Caches/pypoetry/virtualenvs
> Installing dependencies from lock file
>
> Package operations: 52 installs, 0 updates, 0 removals
>
> - Installing six (1.15.0)
> - Installing docutils (0.15.2)
> (省略)
poetry env use で明示的に構築
指定したpython
コマンドのバージョンを元に仮想環境を作成する方法もあります。こちらもすでに仮想環境があれば利用されません。
pyenv local 3.8.2
poetry env use python
> Creating virtualenv hoge-project-4FNRMnIZ-py3.8 in /Users/xxxxx/Library/Caches/pypoetry/virtualenvs
> Using virtualenv: /Users/xxxxx/Library/Caches/pypoetry/virtualenvs/hoge-project-4FNRMnIZ-py3.8
確認
アクティブな仮想環境の詳細を確認することもできます。
# リストアップ
poetry env list
> hoge-project-4FNRMnIZ-py3.7
> hoge-project-4FNRMnIZ-py3.8 (Activated)
# アクティブな仮想環境の詳細を確認
poetry env info
> Virtualenv
> Python: 3.8.2
> Implementation: CPython
> Path: /Users/xxxxx/Library/Caches/pypoetry/virtualenvs/hoge-project-4FNRMnIZ-py3.8
> Valid: True
>
> System
> Platform: darwin
> OS: posix
> Python: /Users/xxxxx/.pyenv/versions/3.8.2
削除
# コマンドで削除
poetry env remove hoge-project-4FNRMnIZ-py3.7
> Deleted virtualenv: /Users/xxxxx/Library/Caches/pypoetry/virtualenvs/hoge-project-4FNRMnIZ-py3.7
# フォルダを直接削除
rm -rf /Users/xxxxx/Library/Caches/pypoetry/virtualenvs/hoge-project-4FNRMnIZ-py3.7
venvと併用する意味はなさそう
たまにvenvで仮想環境を作ってから、poetry install
するような記事を見かけたので、意味はあるのだろうか?と疑問を持ちました。もしかしてvenvで作った仮想環境があったらそれを使い回すのか?と。ので、ちょっと試してみます。
pyenv local 3.8.2
python -m venv env # venv仮想環境を作成
source env/bin/activate
poetry env use python # このpythonは`path_to_project/env/bin/python`。`poetry install`でも同じ結果
> Creating virtualenv hoge-project-4FNRMnIZ-py3.8 in /Users/xxxxx/Library/Caches/pypoetry/virtualenvs
> Using virtualenv: /Users/xxxxx/Library/Caches/pypoetry/virtualenvs/hoge-project-4FNRMnIZ-py3.8
deactivate
rm -rf env # venv仮想環境を削除
poetry run python --version
> Python 3.8.2
結果としては、poetry仮想環境を構築する際にコピーされるpythonランタイムのコピー元としてvenv仮想環境のpythonランタイムが使われるようですが、コピー後はvenv仮想環境を削除してもpoetryは正常に動作するので、venv仮想環境は特に必要がありません。
pythonのバージョンを変更したいだけなら、venvを使って仮想環境を作るまでもなく、pyenvでバージョンを変えればいいだけなので、venvで仮想環境を作る意味はなさそうです。
プログラムの実行
以下のような超シンプルなscripts/hello.py
を実行するケースを考えます。
def main():
print('hello poetry!')
if __name__ == "__main__":
main()
直接pythonプログラムを実行
poetry run
の後にpythonプログラムを実行するコマンドを記載するだけです。引数つけても大丈夫です。
poetry run python scripts/hello.py
> hello poetry!
スクリプトとして実行する
pyproject.toml
にスクリプト定義とスクリプトを配置するフォルダを記載する必要があります。
[tool.poetry]
packages = [
{ include="scripts" },
]
[tool.poetry.scripts]
hello = "scripts.hello:main"
あとは、poetry run <スクリプト名>
で実行します。
poetry run hello
> hello poetry!
プロジェクト直下だと[ModuleOrPackageNotFound]
が発生してうまく動きません。
コマンドライン引数を渡して実行する
コマンドライン引数を扱うためにargparse
をインストールします。
poetry add argparse
scripts:/hello.py
を、message
というコマンドライン引数にとって、printするように変更します。
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('message')
args = parser.parse_args()
message: str = args.message
print(f'hello {message}!')
if __name__ == "__main__":
main()
pythonコマンドで直接実行してみると、普通に動きます。
python scripts/hello.py xxxxx
> hello xxxxx!
当然ながら、poetry runで直接起動することもできます。
poetry run python scripts/hello.py xxxxx
> hello xxxxx!
スクリプトとして実行することもできます。
poetry run hello xxxxx
> hello xxxxx!
ちなみに、スクリプトの設定時にhello = "scripts.hello:main"
と指定してるので、もしかしてmain()
関数に直接引数を渡したりできるのかな?とも思って試してみたのですが、それはできませんでした。
VS Code上でデバッグ実行する
Pythonプラグインを使ってデバッグすることもできます。とても便利です。
一点だけ注意点として、VS Codeがpoetryの仮想環境を認識できる必要があるため、virtualenvs.in-project
をtrue
にして、プロジェクト配下に仮想環境が作成されるようにする必要があります。
poetry config virtualenvs.in-project true --local
もしくは、.vscode/settings.json
のpython.pythonPath
に仮想環境のpathを書いても良いみたいです。
あとは、こちらに書いてある通り、launch.json
を作って、実行対象のpythonファイルを開いて、Runするだけです。
poetry自体の設定
poetryではconfigコマンドで設定を行うことができます。
設定はpoetry config --list
で確認できます。
poetry config --list
> cache-dir = "/Users/xxxxx/Library/Caches/pypoetry"
> virtualenvs.create = true
> virtualenvs.in-project = false
> virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/xxxxx/Library/Caches/pypoetry/virtualenvs
設定できるのは以下の5つのようです。個人的にはvirtualenvs.in-project: true
だけは設定した方が良いと感じています。
- cache-dir
- Poetryが使うキャッシュディレクトリのpathです。defaultは以下
- mac: ~/Library/Caches/pypoetry
- Linux: ~/.cache/pypoetry
- Poetryが使うキャッシュディレクトリのpathです。defaultは以下
- virtualenvs.create
- まだ仮想環境が存在していない場合、新しい仮想環境を作成するか。defaultはtrue
- virtualenvs.in-project
- プロジェクトのルートディレクトリに仮想環境を作成するか。defaultはfalse
- 仮想環境の消し忘れがありえると思うので、この設定はtrueにしておいた方が良よさそう
- virtualenvs.path
- 仮想環境が作成されるディレクトリ。defaultは{cache-dir}/virtualenvs
- repositories.{name}
- PyPI以外のプライベートリポジトリを利用したい場合などに設定。
プロジェクトローカルでも設定できます。
# グローバルの設定
poetry config {項目名} {値}
poetry config virtualenvs.in-project true
# プロジェクトローカルの設定
poetry config {項目名} {値} --local
poetry config virtualenvs.in-project true --local
トラブル対応
ほとんどトラブルらしいトラブルが起こったことはないですが、Linuxにinstallした際に少しハマったので載せておきす。
/home/xxx/.poetry/lib/poetry/_vendor/py2.7/subprocess32.py:149: RuntimeWarning: The _posixsubprocess module is not being used. Child process reliability may suffer if your program uses threads.
2020/06/16現在、obuntuにpoetryをインストールすると、このエラーが発生します。
このissueで対応されるまでは手でパッチを当てる必要があるので要注意です。
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py > get-poetry.py
vi get-poetry.py
- allowed_executables = ["python", "python3"]
+ allowed_executables = ["python3", "python"]
python3 get-poetry.py
[RecursionError] maximum recursion depth exceeded while calling a Python object
poetry add scrapy
をした際に、このエラーが発生しました。
同様のエラーがgithubのissueに報告されており、開発中の1.1.0b2
で対応されているとのことなので
poetry self update --preview
> Updating to 1.1.0b2
> - Downloading poetry-1.1.0b2-darwin.tar.gz 100%OS
> Poetry (1.1.0b2) is installed now. Great!
で、poetry自体をこのバージョンにあげてみたところ、問題なくscrapyをインストールできました。
[2020/10/20追記] リリースの#2342で修正済みのようで、現在はpoetryのバージョンUPをするだけで解決されます。
poetry self update
その他Tips
poetryではincompatibleなバージョンを無理やりインストールすることはできない
例えば、pyppeteerというスクレイピングライブラリが特定のケースでwebsocketの最新バージョンだと上手く動かないことが分かったので、過去バージョンをインストールしてみようとしたところ、以下のようにpyppeteer (0.2.2) depends on websockets (>=8.1,<9.0)
だから、依存関係を解決できないよ、というエラーが出ました。
poetry add websockets==6.0
Updating dependencies
Resolving dependencies... (0.0s)
SolverProblemError
Because pyppeteer (0.2.2) depends on websockets (>=8.1,<9.0)
and no versions of pyppeteer match >0.2.2,<0.3.0, pyppeteer (>=0.2.2,<0.3.0) requires websockets (>=8.1,<9.0).
So, because python-scraping-sample depends on both pyppeteer (^0.2.2) and websockets (6.0), version solving failed.
どうやら、強制的にインストールするオプションなども無いみたいですし、 stackoverflow でもできないよと書いてあります。
ここを見ると、poetry install
はresolverを含まないのでpipと同じようにできると書いてあるのですが、poetry.lock
を手で編集してpoetry install
を実行するのはちょっと厳しいので自分は断念しました。
なお、pyproject.toml
にwebsockets = 6.0
を追加して、poetry update
してみたのですが、こちらはpoetry add
と同様にSolverProblemErrorが出ました。
ちなみに、どうしても一時的にincompatibleな過去バージョンにして検証したくなった場合は、自分はpipを使うようにしてます。