Poetryを使ったPythonパッケージ開発からPyPI公開まで
この記事は BeProud Advent Calender 2018 の20日目の記事です。そのためいつもよりボリュームたっぷり、文体も丁寧にお送りします。
本記事ではPoetryを使ってパッケージ開発→PyPIへ登録するまでの流れを紹介します。
- プロジェクト作成からPyPI登録までわずか30秒
- Poetry について
- Poetryの各種設定
- プロジェクトを用意する
- 必要なライブラリのインストール
- 公開予定のプログラムを書く
- パッケージのビルド
- PyPIへの公開
- まとめ
- 参考文献
プロジェクト作成からPyPI登録までわずか30秒
$ poetry new kk6-demo Created package kk6-demo in kk6-demo $ cd kk6-demo/ $ poetry build Creating virtualenv kk6-demo-py3.7 in /Users/kk6/sandbox/kk6-demo/.venv Building kk6-demo (0.1.0) - Building sdist - Built kk6-demo-0.1.0.tar.gz - Building wheel - Built kk6_demo-0.1.0-py3-none-any.whl $ poetry publish Publishing kk6-demo (0.1.0) to PyPI - Uploading kk6-demo-0.1.0.tar.gz 100% - Uploading kk6_demo-0.1.0-py3-none-any.whl 100%
new
→build
→publish
の3コマンドでプロジェクト作成からPyPIに登録まで、約25秒ほどでした。簡単ですね。(やったあとでTestPyPIの存在思い出したんですが、こういうテスト用途にはそちらを使いましょう…)
実際にはパッケージのコードを書いたりテストコード書いたりが必要ですが、それ以外の諸々はPoetryがやってくれるので、本質的なコードを書くことに集中できます。
Poetry について
基本的な使い方
Poetryの基本的な使い方については公式ドキュメントの他にも日本語で有用な記事がすでに2つありますのでそちらを紹介しておきます。
Poetry と Pipenv
Pipenvは確かに便利だけど
Pythonのパッケージ管理ツールとしては最近ではPipenvが使われるようになってきていると思いますが、あちらはあくまでも依存関係の管理までです。Poetryはそれに加えてこうしたパッケージのビルドや公開までもが守備範囲となっています。
また、先程紹介した記事によるとPipenvはちょっと雲行きが怪しい感じみたいです。
Pipenv は当初大きな期待を持って迎えられましたが(私も期待しましたが)、さまざまな問題が出ており最近はどうも先行きが怪しい感じです。私自身 Pipenv の導入によって便利になったところがある一方で Pipenv を使っていなかったら抱えていなかった新たなストレスも増えた感じがします。 Poetry は私のような「脱 Pipenv 」を検討している方にとって有力な移行先候補のひとつです。
Pipenv の問題点とそれにまつわる議論については次のページ等が参考になります。興味のある方は読んでみてください。
PoetryのREADMEでもPipenvについて言及しています。それによるとPipenvで依存関係の解決に失敗するようなケースでもPoetryなら大丈夫とのこと。
What about Pipenv? | sdispater/poetry
またライブラリのインストールもPipenvは結構遅いですが、Poetryはだいぶましです。Pipほど速くはないですが…。
Pipenvも良いツールだと思います。ただ、「これからの時代は何も考えずにとりあえずPipenvいれとけばいい」という状況ではないと思います。
Pipenv から Poetry に乗り換える
先日書いた記事を参考にしてください。
Pipenv から Poetry への乗り換え - PYTHONIC BOOM BOOM HEAD
今をときめくソースコード自動整形ツール black もPipenvからPoetryへの移行が進行中のようです(現在のところは両方サポート)。こちらも参考になるでしょう。
Poetry と Pyenv
Poetryの作者自身、Pyenvと組み合わせて利用しているようです。Pyenvを使う場合はこんな感じでインストールすると良いでしょう。以下Issueのオーナーコメントより引用。
Basically:
Pyenvといえば日本人しか使ってないニッチなツールというイメージはもはや過去のものとなり、PipenvもPoetryもPyenvとの連携が想定されていますし、CircleCIも内部でPyenvを使っています。
以前は僕もPyenvのハマりどころの多さに嫌気が差して使うのをやめていましたが、あれから徐々に海外でも知名度を上げコミットする人間も増えたみたいで、今では使っていても問題は特に起きないので日常的に使っています。
ただし、こういうパッケージ開発なんかでは細かいバージョン違いまで管理する必要がありますが、「これからPythonはじめたいのでPyenvいれてみるかー」みたいなのは避けたほうがいいです。Python2と3の使い分け程度ならhomebrewで十分ですし、Python3.6と3.7の使い分けとかも公式からバイナリ落としてきてそれぞれインストールすればいいだけです。「よくわからないけどとりあえずPyenvいれてみる」というケースが一番ハマるパターンだと思っています。
PEP517 と PEP518
Poetryにも使われているpyproject.toml
はPipfile
のようなものですが、あちらが独自定義のファイルなのに対し、こちらはPEPで仕様が決められています。
PEP518によると、pyproject.toml
でビルドツールを指定、もしそのツールが無ければsetuptools
によるビルドが行われます。Poetryだとこんな感じですが、例えば flit に乗り換えたければここだけ直せば後の設定は一切書き換えること無くビルドツールを乗り換えることができる、というのが本来の目的だと思います。
[build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"
ただ、現状では [tool.poetry]
のようなPoetry独自設定を書き込まなければならないため、「一見PEPに従ってるように見えるが蓋を開けてみればまだまだPipfileと同じような独自定義の設定ファイル」となっています。これは同じく pyproject.toml
に対応しているflitも同様のようです。
また、ビルドツール用の標準APIの仕様策定は、PEP517で行われています。
PoetryもすでにPEP517に準拠しています。
ちなみにpipもPEP517対応済みで、pip build
コマンドの追加へ向けてすでに動いているようです。
将来的にすべてがpipに収束するのかはわかりませんが、少なくともユースケースに応じて適切なツールを自由に選択できるようになっているんじゃないかと思います。
Poetryの各種設定
個人的に仮想環境(venv)をプロジェクト内に作ってほしかったりするのでそれらの設定を先に行います。このあたりは必要に応じて行ってください。
venvの作成先をプロジェクト内にしたい
デフォルトだと仮想環境は、macだと$HOME/Library/Caches/pypoetry/virtualenvs
以下に作られます。これは $ poetry config --list
で確認できます。
$ poetry config --list settings.virtualenvs.create = true settings.virtualenvs.in-project = false settings.virtualenvs.path = "/Users/kk6/Library/Caches/pypoetry/virtualenvs"
これらの設定は公式ドキュメントに記載されている通り、macなら~/Library/Application Support/pypoetry
以下のconfig.toml
というファイルに記載されています。もしまだconfig.toml
が無ければ、一度 $ poetry config --list
を実行すると自動で作成されます。
現状だとsettings.virtualenvs.in-project
がfalse
なので、settings.virtualenvs.path
に設定されている"~/Library/Caches/pypoetry/virtualenvs"
へ仮想環境が作られるようになっています。
仮想環境をプロジェクト配下に作りたい場合は、以下のようにsettings.virtualenvs.in-project
にtrue
を指定します。
$ poetry config settings.virtualenvs.in-project true
これによりvirtualenvs.path
の設定は無視して各プロジェクト直下に仮想環境が作られるようになります。
TestPyPIへアップロードできるようにする
パッケージを公開する publish
コマンドはデフォルトではPyPIへのアップロードになっています。練習なのでPyPIではなくTestPyPIのほうにアップロードできるように設定しましょう。まずリポジトリの設定から。
$ poetry config repositories.testpypi https://test.pypi.org/legacy/
このような感じで、repositories.XXX
のXXXの部分に好きな名前を指定して、URLを紐づけます。
余談ですが、TestPyPIへのアップロードは https://test.pypi.org/legacy のほうじゃないとダメです。https://test.pypi.org/simple だと失敗します。(実は記事執筆時点の最新版のPoetryだと成功してる風な出力になりますが実際にはアップロードできてなくて、試しにtwineでアップロードしてみると405エラーになります。)
また、TestPyPIに限り、pyproject.toml
にhomepageの設定がないとアップロードできません。先ほどと同様Poetryだと成功しているように見えるんですが、twineでやってみるとHome-PageにNoneは設定できないよという感じのエラーが返ってきます。
$ poetry run twine upload --repository-url https://test.pypi.org/legacy/ dist/* ... HTTPError: 400 Client Error: 'None' is an invalid value for Home-Page. Error: Invalid URI See https://packaging.python.org/specifications/core-metadata for url: https://test.pypi.org/legacy/
とりあえずIssueは出しておきました。オーナーからバグなので対処すると返事も来てました。
TestPyPIのユーザー名とパスワードを設定する
publish
コマンドを実行すると通常は対話形式でログインのためにユーザー名とパスワードを要求されます。対話形式での入力以外の方法としては以下の2通りがあります。
publish
コマンドに--username
と--password
のオプションとしてユーザー名とパスワードを渡す方法config
コマンドでユーザー名とパスワードを恒久的に設定する方法
今回は後者を設定してみます。
$ poetry config http-basic.testpypi username password
以上です。なお、PyPIの場合はこう。
poetry config http-basic.pypi username password
ちなみにここで入力した値は ~/Library/Application\ Support/pypoetry/auth.toml
に保存されています。twine
の.pypirc
のようなものですね。そちら同様パスワードは平文で保存されてるので扱いには十分注意してください。
あ、そもそもTestPyPIにアカウント登録していない場合はまずそちらを済ませておいてください。
ここまでの設定
ここまでで設定はこんな感じになりました。
$ poetry config --list settings.virtualenvs.create = true settings.virtualenvs.in-project = true settings.virtualenvs.path = "/Users/kk6/Library/Caches/pypoetry/virtualenvs" repositories.testpypi.url = "https://test.pypi.org/legacy/"
プロジェクトを用意する
新規作成
標準的なレイアウト
それではまずプロジェクトを作ってみます。プロジェクトを作るには new
コマンドを実行します。
$ poetry new howoldru
Created package howoldru in howoldru
作成されたプロジェクトの構成はこうなっています。
$ tree howoldru/ howoldru/ ├── README.rst ├── howoldru │ └── __init__.py ├── pyproject.toml └── tests ├── __init__.py └── test_howoldru.py 2 directories, 5 files
新規作成時点でのpyproject.toml
です。
[tool.poetry] name = "howoldru" version = "0.1.0" description = "" authors = ["kk6 <hiro.ashiya@gmail.com>"] [tool.poetry.dependencies] python = "^3.7" [tool.poetry.dev-dependencies] pytest = "^3.0" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"
srcレイアウト
パッケージのルートディレクトリを howoldru
のような名前ではなく src
という名前を使う、いわゆる src レイアウトでプロジェクトを作成したい場合は --src
オプションを指定します。
$ poetry new --src foo $ tree foo foo ├── README.rst ├── pyproject.toml ├── src │ └── foo │ └── __init__.py └── tests ├── __init__.py └── test_foo.py 3 directories, 5 files
srcレイアウトの有効性についてはPytestの公式ドキュメントの翻訳記事があったのでこちらを参考にしてください。
しかし、これだとちょっとした問題を引き起こす。それは、testsディレクトリからモジュールをロードするために、pytestがsys.pathの先頭にレポジトリのルートを追加するが、これがmypkgもインポート可能となるということを引き起こすからである。
もし仮想環境においてパッケージをテストするためにtoxのようなツールを利用しているならば、これは問題となる。なぜなら、テストしたいのはインストールされたバージョンのパッケージであり、レポジトリのローカルコードではないからだ。
このようなシチュエーションではsrcレイアウトを使用することが強く推奨される。
本家公式ドキュメントの同じ記述の部分はこちら
Good Integration Practices — pytest documentation
おまけ:既存のプロジェクトに適用する
もし既存のプロジェクトをPoetryで管理したい場合は init
コマンドを使用します。パッケージ名やバージョンなどを対話形式で入力すると、その内容を元にpyproject.toml
を生成してくれます。
$ poetry init This command will guide you through creating your pyproject.toml config. Package name [howoldru]: Version [0.1.0]: Description []: Author [kk6 <hiro.ashiya@gmail.com>, n to skip]: License []: Compatible Python versions [^3.7]: Would you like to define your dependencies (require) interactively? (yes/no) [yes] Search for package: Would you like to define your dev dependencies (require-dev) interactively (yes/no) [yes] Search for package: Generated file [tool.poetry] name = "howoldru" version = "0.1.0" description = "" authors = ["kk6 <hiro.ashiya@gmail.com>"] [tool.poetry.dependencies] python = "^3.7" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" Do you confirm generation? (yes/no) [yes]
new
の場合はtool.poetry.dev-dependencies
に自動的にpytestが記載されていましたがこちらの場合はそれがないです。
必要なライブラリのインストール
今回は作ろうと思っているパッケージは、コマンドラインツールを簡単に実装できるfire
と日時操作を簡単に行えるpendulum
を使いますのでそれらをインストールします。
インストールにはadd
コマンドを使用します。
$ cd howoldru $ poetry add fire pendulum Creating virtualenv howoldru-py3.7 in /Users/kk6/sandbox/python/bp_advent_calendar_2018/howoldru/.venv Using version ^0.1.3 for fire Using version ^2.0 for pendulum Updating dependencies Resolving dependencies... (0.7s) Package operations: 11 installs, 0 updates, 0 removals Writing lock file - Installing six (1.12.0) - Installing atomicwrites (1.2.1) - Installing attrs (18.2.0) - Installing more-itertools (4.3.0) - Installing pluggy (0.8.0) - Installing py (1.7.0) - Installing python-dateutil (2.7.5) - Installing pytzdata (2018.7) - Installing fire (0.1.3) - Installing pendulum (2.0.4) - Installing pytest (3.10.1)
新たに依存パッケージをインストールする add
コマンドや、pyproject.toml
に記載された依存パッケージをインストールする install
コマンドを実行すると初回は自動的に仮想環境が作られます。続いてその環境に先程指定したパッケージがインストールされます。
なお、add
で自ら追加したパッケージはpyproject.toml
に追記されています。
[tool.poetry.dependencies] python = "^3.7" fire = "^0.1.3" pendulum = "^2.0"
依存関係は poetry.lock
のほうに記載されています。結構な行数なのでここでは割愛します。
公開予定のプログラムを書く
今回は1ファイルのみのコマンドラインツールを作ろうと思うので、howoldru
パッケージは削除してhowoldru.py
を用意します。
$ ls README.rst howoldru poetry.lock pyproject.toml tests $ rm -rf howoldru/ $ touch howoldru.py $ nvim howoldru.py
howoldru.py
はこんな感じで実装しました。
import fire import pendulum def show_age(birthday): age = pendulum.parse(birthday).age print(f"あなたは現在{age}歳です。") def main(): fire.Fire(show_age) if __name__ == "__main__": main()
生年月日を入力すると現在の年齢を教えてくれるだけのコマンドラインツールです。
動作確認してみましょう。poetry run python
で仮想環境のほうのPythonが実行されます。
$ poetry run python howoldru.py 2001-08-25 あなたは現在17歳です。
ちゃんと動いてますね。
スクリプトの設定
python howolru.py
とするのではなく、howoldru
コマンドで実行できるようにしてみましょう。
pyproject.toml
に以下の設定を追加します。
[tool.poetry.scripts] howoldru = "howoldru:main"
これで poetry run howoldru
コマンドが実行できるようになりました。
$ poetry run howoldru 2001-08-25 あなたは現在17歳です。
poetry run howoldru
だとまだ長い?では howoldru
コマンドだけで済むようにしてみましょう。
仮想環境をactivateする
poetry
コマンドを経由しないで python
コマンドを実行しても当然 pyproject.toml
の内容は読み込んでくれません。なのでまず shell
コマンドで仮想環境をactivateします。
$ poetry shell Spawning shell within /Users/kk6/sandbox/python/bp_advent_calendar_2018/howoldru/.venv Welcome to fish, the friendly interactive shell
これで source .venv/bin/activate.fish
したのと同じ状態になりました。
※ 筆者はfish shellを利用しています。bashやzshだと表示が若干違うと思いますが適宜読み替えてください。
開発途中のパッケージをインストールする
develop
コマンドで開発途中のパッケージを仮想環境にインストールします。 pip install -e .
のことですね。
$ poetry develop develop is deprecated use install instead. Installing dependencies from lock file Nothing to install or update - Installing howoldru (0.1.0)
これで howoldru
コマンドだけで実行できるようになりました。
$ howoldru Fire trace: 1. Initial component 2. ('The function received no value for the required argument:', 'birthday') Type: function String form: <function show_age at 0x108f81048> File: /Users/kk6/sandbox/python/bp_advent_calendar_2018/howoldru/howoldru.py Line: 5 Usage: howoldru BIRTHDAY howoldru --birthday BIRTHDAY
パッケージのビルド
ビルドする
build
コマンドを実行すると pyproject.toml
の [tool.poetry]
セクションの name
に設定されている名前と同じPythonファイルまたはパッケージ、あるいは src
ディレクトリを探し、ビルドを行います。
build
コマンドでパッケージをビルドする際にpyproject.toml
の内容に基づいてsetup.py
を自動生成してくれます。では実際に確認してみましょう。
まずビルドします。(事情によりhowoldruのバージョンがちょっと上がってますが気にしないでください…)
$ poetry build Building howoldru (0.1.2) - Building sdist - Built howoldru-0.1.2.tar.gz - Building wheel - Built howoldru-0.1.2-py3-none-any.whl
dist/
以下にtar.gz
とwhl
ファイルが作成されました。
setup.py/setup.cfg/MANIFEST.inはもう書かなくて良い
生成されたsetup.py
を確認してみます。
$ cd dist/ $ tar zxvf howoldru-0.1.2.tar.gz x howoldru-0.1.2/howoldru.py x howoldru-0.1.2/pyproject.toml x howoldru-0.1.2/setup.py x howoldru-0.1.2/PKG-INFO $ nvim howoldru-0.1.2/setup.py
内容がこちら。
# -*- coding: utf-8 -*- from distutils.core import setup modules = \ ['howoldru'] install_requires = \ ['fire>=0.1.3,<0.2.0', 'pendulum>=2.0,<3.0'] entry_points = \ {'console_scripts': ['howoldru = howoldru:main']} setup_kwargs = { 'name': 'howoldru', 'version': '0.1.2', 'description': '', 'long_description': None, 'author': 'kk6', 'author_email': 'hiro.ashiya@gmail.com', 'url': 'https://github.com/kk6', 'py_modules': modules, 'install_requires': install_requires, 'entry_points': entry_points, 'python_requires': '>=3.7,<4.0', } setup(**setup_kwargs)
install_requires
やconsole_scripts
なんかもpyproject.toml
に基づいて自動的に設定してくれているのがわかると思います。
また、setup.cfg
やMANIFEST.in
に書いていたような内容もpyproject.toml
がその役目を負ってくれているのでもう書く必要はありません。
PyPIへの公開
TestPyPIに公開する
PyPI(TestPyPI)への公開はpublish
コマンドで行います。今回はTestPyPIのほうにアップロードしたいので poetry publish -r testpypi
のようにアップロード先を指定します。オプションを忘れるとPyPIのほうへアップロードされてしまうので注意。
config
コマンドでPyPIの認証情報を設定していない場合は、実行時にユーザー名とパスワードの入力を求められますので入力します。認証に成功すると先程ビルドしたものがPyPIへアップロードされます。
$ poetry publish -r testpypi Publishing howoldru (0.1.2) to testpypi - Uploading howoldru-0.1.2-py3-none-any.whl 100% - Uploading howoldru-0.1.2.tar.gz 100%
PyPIの自身のプロジェクト一覧を見るとアップロードされているのが確認できると思います。
https://pypi.org/manage/projects/
パッケージのページもできてますね。
付加情報の追加
パッケージの公開ページにdescriptionがなかったりと寂しいので追加してみましょう。
パッケージ公開ページのdescriptionはREADMEとして書く
setup.py
を書く場合のテクニックとして、README.rst
等をパースしてlong_description
に設定するという方法がありますが、Poetryの場合 pyproject.toml
に1行追加すればそれを勝手にやってくれます。
[tool.poetry] ... readme = "README.rst" ...
README.rst自体はpoetry new
した時に作られてますが、空のファイルのためdescriptnも空になっています。なのでREADME.rstを編集しましょう。以下は例です。
howoldru ======== It's demo package.
ライセンスファイルを用意する
プロジェクト内にLICENSE
ファイルがあればPoetryが自動的に検出してビルド成果物に含めてくれます。
pyproject.toml
に以下を書くのが慣習ぽいのですが、無くても勝手に含めてくれてました。
[tool.poetry] ... license = "MIT" ...
CHANGELOG 等も含めたい場合
ライセンスは自動で検出してくれるようですが、自分で含めたいファイルを指定する場合は tool.poetry
セクションに include
で指定します。
[tool.poetry] # ... include = ["CHANGELOG.md"]
逆に除外したいファイルがあれば exclude
を指定します。
exclude = ["my_package/excluded.py"]
その他諸々
その他の項目も pyproject.toml
に追記してこういう感じになりました。
[tool.poetry] name = "howoldru" version = "0.1.3" description = "How old are you?" readme = "README.rst" authors = ["kk6 <hiro.ashiya@gmail.com>"] homepage = "https://github.com/kk6" repository = "https://github.com/kk6/howoldru" documentation = "https://github.com/kk6/howoldru/wiki" license = "MIT" classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: MIT License", 'Programming Language :: Python :: 3', "Programming Language :: Python :: 3.7", "Topic :: Utilities", ]
homepageとは別途、リポジトリのURLを設定したり、ドキュメントのURLを設定したり、classifiersのようなメタデータを記載しました。これらは公開されたパッケージのページ上で表示されます。
classifiersについては以下のページからそれらしいものを選んで記載しましょう。
https://pypi.org/pypi?%3Aaction=list_classifiers
より詳しい pyproject.toml の書き方はドキュメントを参考に。
なお、PyPIの仕様上、再アップロード時にバージョン番号がアップロード済みのものだと受け付けてくれないのでバージョンを version = "0.1.3"
みたいな感じで上げておきましょう。
公開パッケージをアップデートする
パッケージを更新したのでアップロードし直します。今回はpublish
コマンドに--build
オプションを指定してビルドと公開を同時に行ってみましょう。
$ poetry publish -r testpypi --build Building howoldru (0.1.5) - Building sdist - Built howoldru-0.1.5.tar.gz - Building wheel - Built howoldru-0.1.5-py3-none-any.whl Publishing howoldru (0.1.5) to testpypi - Uploading howoldru-0.1.5-py3-none-any.whl 100% - Uploading howoldru-0.1.5.tar.gz 100%
パッケージのページを見てみるとメタ情報等が表示されているのが確認できます。
また、手元でビルドした成果物を確認してみましょう。
$ cd dist/ $ tar zxvf howoldru-0.1.5.tar.gz x howoldru-0.1.5/LICENSE x howoldru-0.1.5/README.rst x howoldru-0.1.5/howoldru.py x howoldru-0.1.5/pyproject.toml x howoldru-0.1.5/setup.py x howoldru-0.1.5/PKG-INFO
最初のビルドとの違いはまず、 LICENSE
と README.rst
が追加されていることです。それからsetup.pyの内容も変わっています。long_description
がREADME.rst
をもとに設定されていますね。
# -*- coding: utf-8 -*- from distutils.core import setup modules = \ ['howoldru'] install_requires = \ ['fire>=0.1.3,<0.2.0', 'pendulum>=2.0,<3.0'] entry_points = \ {'console_scripts': ['howoldru = howoldru:main']} setup_kwargs = { 'name': 'howoldru', 'version': '0.1.5', 'description': '', 'long_description': "howoldru\n========\n\nIt's demo package.\n\n", 'author': 'kk6', 'author_email': 'hiro.ashiya@gmail.com', 'url': 'https://github.com/kk6', 'py_modules': modules, 'install_requires': install_requires, 'entry_points': entry_points, 'python_requires': '>=3.7,<4.0', } setup(**setup_kwargs)
また、PKG-INFO
を見ると先程pyproject.tomlに記載したClassifier等が記述されています。
Metadata-Version: 2.1 Name: howoldru Version: 0.1.5 Summary: Home-page: https://github.com/kk6 Author: kk6 Author-email: hiro.ashiya@gmail.com Requires-Python: >=3.7,<4.0 Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Utilities Requires-Dist: fire (>=0.1.3,<0.2.0) Requires-Dist: pendulum (>=2.0,<3.0) Project-URL: Documentation, https://github.com/kk6/howoldru/wiki Project-URL: Repository, https://github.com/kk6/howoldru
インストールして使ってみる
こちらは素のvenv環境にpipでインストールするというごく普通のユースケースでやってみます。
$ mkdir foo $ cd foo $ python3 -m "venv" .venv $ source .venv/bin/activate.fish
ではTestPyPIからインストールしてみましょう。PyPIではなくTestPyPIからインストールしたいので --index-url
でURLを指定します。
(.venv) $ pip install howoldru --index-url https://test.pypi.org/simple Looking in indexes: https://test.pypi.org/simple Collecting howoldru Downloading https://test-files.pythonhosted.org/packages/35/28/db1a4755d30fed9135cb9136e2151a0c2cf84421f6fed8c3a9f56919d534/howoldru-0.1.5-py3-none-any.whl Collecting fire<0.2.0,>=0.1.3 (from howoldru) Could not find a version that satisfies the requirement fire<0.2.0,>=0.1.3 (from howoldru) (from versions: ) No matching distribution found for fire<0.2.0,>=0.1.3 (from howoldru)
おっと、失敗しました。どうやら依存ライブラリであるfireがTestPyPIに存在しないようです。
複数のサーバーから検索してくるには --extra-index-url
を使うのが正解です。これはまず指定したURL(今回はTestPyPI)からインストールを試み、無ければPyPIのほうから探してきてくれます。
(.venv) $ pip install howoldru --extra-index-url https://test.pypi.org/simple Looking in indexes: https://pypi.org/simple, https://test.pypi.org/simple Collecting howoldru Using cached https://test-files.pythonhosted.org/packages/35/28/db1a4755d30fed9135cb9136e2151a0c2cf84421f6fed8c3a9f56919d534/howoldru-0.1.5-py3-none-any.whl Collecting fire<0.2.0,>=0.1.3 (from howoldru) Using cached https://files.pythonhosted.org/packages/5a/b7/205702f348aab198baecd1d8344a90748cb68f53bdcd1cc30cbc08e47d3e/fire-0.1.3.tar.gz Collecting pendulum<3.0,>=2.0 (from howoldru) Using cached https://files.pythonhosted.org/packages/1c/03/7b84db3d21741dc93ac0e89545010a143fc71437b24a7abdf9eff8a7e483/pendulum-2.0.4-cp37-cp37m-macosx_10_14_x86_64.whl Collecting six (from fire<0.2.0,>=0.1.3->howoldru) Using cached https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl Collecting pytzdata>=2018.3 (from pendulum<3.0,>=2.0->howoldru) Using cached https://files.pythonhosted.org/packages/46/8b/d7fe0c5b24870438d5ed59ceab72d537c5374d7db3cc57107e4a922b3a23/pytzdata-2018.7-py2.py3-none-any.whl Collecting python-dateutil<3.0,>=2.6 (from pendulum<3.0,>=2.0->howoldru) Using cached https://files.pythonhosted.org/packages/74/68/d87d9b36af36f44254a8d512cbfc48369103a3b9e474be9bdfe536abfc45/python_dateutil-2.7.5-py2.py3-none-any.whl Installing collected packages: six, fire, pytzdata, python-dateutil, pendulum, howoldru Running setup.py install for fire ... done Successfully installed fire-0.1.3 howoldru-0.1.5 pendulum-2.0.4 python-dateutil-2.7.5 pytzdata-2018.7 six-1.12.0
無事インストールできました。ログを見ると Looking in indexes: https://pypi.org/simple, https://test.pypi.org/simple
というようにTestPyPIとPyPIの両方を参照してくれているのがわかります。
それではインストールした howoldru を使ってみましょう。
(.venv) $ howoldru 2001-08-25 あなたは現在17歳です。
ちゃんと howoldru
コマンドとして動作していますね。
まとめ
このようにPoetryはパッケージの依存関係の管理から公開まで一括で管理してくれます。パッケージ開発者にとってはとても便利なツールだと思います。
ただ、非常に強力で便利なツールではありますが、「これからはPoetryの時代だ!Pipenvなんか捨ててしまえ!」などと言うつもりはありません。Poetryがいかなるユースケースにおいても最適とは言い切れません。build
もpublish
も不要ならPipenvでも十分*1ですし、それ以上に素のpip+venvだけで十分なことも多いでしょう。公式なツールで事足りるなら、余計なものを入れるのはトラブルの素になりかねません。
というわけで目的に合わせて最適なツールを使い分けるのが良いと思います。
参考文献
- Poetry - Python dependency management and packaging made easy.
- Poetry: Python の依存関係管理とパッケージングを支援するツール | org-技術
- ライブラリ: Poetry - Life with Python
- Pythonとパッケージングと私
- Pythonのパッケージングのベストプラクティスについて考える2018 - 朝日ネット 開発者ブログ
- Python's New Package Landscape
*1:依存関係の解決すらPipenvよりPoetryのほうが賢いという問題はありますが
Pipenv から Poetry への乗り換え
乗り換えるリポジトリ
GitHub - kk6/aeroplast: Transparent PNG conversion (Mainly for Twitter)
twitterにアップロードしたPNGがJPGに強制変換されて見栄えが悪いという問題がある。それを回避するために画像の四隅どこかに1pxの透過ドットを打ち込むという方法がある。いちいちペイントソフトでそれを毎回やるのが面倒なのでコマンドラインで画像のPath渡したら変換してくれるコマンドラインツールを以前作った。こいつをPipenvで管理しているので、Poetryへの乗り換えを試してみる。
Poetry については数日中にもうちょっと突っ込んだ記事書きます。
pyproject.toml の生成
init
コマンドで対話形式で情報を入力していくだけ。
$ poetry init This command will guide you through creating your pyproject.toml config. Package name [aeroplast]: Version [0.1.0]: Description []: Transparent PNG conversion. Author [kk6 <hiro.ashiya@gmail.com>, n to skip]: License []: MIT Compatible Python versions [^3.7]: Would you like to define your dependencies (require) interactively? (yes/no) [yes] no Would you like to define your dev dependencies (require-dev) interactively (yes/no) [yes] no Generated file [tool.poetry] name = "aeroplast" version = "0.1.0" description = "Transparent PNG conversion." authors = ["kk6 <hiro.ashiya@gmail.com>"] license = "MIT" [tool.poetry.dependencies] python = "^3.7" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" Do you confirm generation? (yes/no) [yes] yes
これで pyproject.toml が生成される。
依存パッケージのインストール
続いて Pipfile を見ながらパッケージを poetry add
で追加する。
(追記) だるいのでツール作った
(追記おわり)
$ poetry add pillow fire logzero Using version ^5.3 for pillow Using version ^0.1.3 for fire Using version ^1.5 for logzero Updating dependencies Resolving dependencies... (3.2s) Package operations: 0 installs, 1 update, 0 removals Writing lock file - Updating six (1.11.0 -> 1.12.0)
インストールしたパッケージは tool.poetry.dependencies
セクションに追記されている。
[tool.poetry] name = "aeroplast" version = "0.1.0" description = "Transparent PNG conversion." authors = ["kk6 <hiro.ashiya@gmail.com>"] license = "MIT" [tool.poetry.dependencies] python = "^3.7" pillow = "^5.3" fire = "^0.1.3" logzero = "^1.5" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"
と同時に poetry.lock
ファイルが生成されている。依存関係はこちらに記述されている。
pytestのインストールを忘れていた。
$ poetry add -D pytest Using version ^4.0 for pytest Updating dependencies Resolving dependencies... (0.5s) Package operations: 0 installs, 1 update, 0 removals Writing lock file - Updating pytest (4.0.1 -> 4.0.2)
今回は -D
オプションを付けてインストールしたので、pyproject.toml の tool.poetry.dev-dependencies
セクションの方に追記されている。
[tool.poetry.dependencies] python = "^3.7" pillow = "^5.3" fire = "^0.1.3" logzero = "^1.5" [tool.poetry.dev-dependencies] pytest = "^4.0"
コマンドラインツールとして実行できるようにする
PipenvのときはPipfileに以下のように記述して使っていた。
[scripts] cli = "pipenv run python -m aeroplast"
こんな感じで使っていた
$ pipenv run cli convert ...
Potryの場合は pyproject.toml
の tool.poetry.scripts
セクションに記述する。
[tool.poetry.scripts] aeroplast = "aeroplast.cli:main"
Poetryの便利なところは、パッケージをビルドした際にこの設定がそのまま setup.py
の console_scripts
として記述される。ビルドする時にコマンドとして実行できるようにあらためてsetup.pyを書く必要がない。
... entry_points = \ {'console_scripts': ['aeroplast = aeroplast.cli:main']} ...
とまあそれはさておき poetry run aeroplast
で実行できるようになった。
$ poetry run aeroplast Type: CLI String form: <aeroplast.cli.CLI object at 0x100f6e978> Docstring: Transparent PNG conversion Usage: aeroplast aeroplast convert aeroplast resize
また、poetry develop
で pip install -e .
するのと同様に開発途中のパッケージをvenvにインストールできる。インストールした上で poetry shell
でvenvをactivateすると aeroplast
コマンドのみで実行可能になる。
$ poetry shell Spawning shell within /Users/kk6/PycharmProjects/aeroplast/.venv Welcome to fish, the friendly interactive shell $ poetry develop develop is deprecated use install instead. Installing dependencies from lock file Nothing to install or update - Installing aeroplast (0.1.0) $ aeroplast Type: CLI String form: <aeroplast.cli.CLI object at 0x107dfa908> Docstring: Transparent PNG conversion Usage: aeroplast aeroplast convert aeroplast resize <aeroplast.cli.CLI object at 0x107dfa908>
以上で移行完了。わりと簡単。しかも今すぐにでも以下の2行を実行するだけでPyPIに公開可能な状態となっている。
$ poetry build $ poetry publish
Poetry便利。
またPyenv環境に戻してみた
- 動機
- 事前確認
- pyenvのインストール
- python3.6をインストールしてみるも失敗する
- 最近Mojaveにアップグレードしたのが原因だった
- それぞれのPythonを呼び出せるようにする
- brewで入れたpython3.7をどうするか問題
- brewのpython消してpyenvで入れ直した
動機
ライブラリ開発とかしてるとどうしてもhomebrewだけではつらくなる(python3.6が入ってる状態でbrew upgradeしてpython3.7に上がると、brew cleanup叩いた時点でpython3.6が消される)
公式からバージョンごとにインストーラー落としてきて入れるというのも考えたが、pipenvにはpyenvと連携するオプションが用意されてるし、CircleCIも内部でpyenv使ってるしpoetryの作者もとあるissueに対する返信で「pyenvを使うことを強く推奨する」なんて言ってたのでもう海外でもpyenvが市民権得たのかなーとか思った次第。じゃあもうpyenvでいいやーと。
事前確認
- 2.7.10
- 3.7.0
pyenvのインストール
~ $ brew install pyenv ==> Installing dependencies for pyenv: autoconf ==> Installing pyenv dependency: autoconf ==> Downloading https://homebrew.bintray.com/bottles/autoconf-2.69.mojave.bottle.4.tar.gz ######################################################################## 100.0% ==> Pouring autoconf-2.69.mojave.bottle.4.tar.gz ==> Caveats Emacs Lisp files have been installed to: /usr/local/share/emacs/site-lisp/autoconf ==> Summary 🍺 /usr/local/Cellar/autoconf/2.69: 71 files, 3.0MB ==> Installing pyenv ==> Downloading https://homebrew.bintray.com/bottles/pyenv-1.2.8.mojave.bottle.tar.gz ######################################################################## 100.0% ==> Pouring pyenv-1.2.8.mojave.bottle.tar.gz 🍺 /usr/local/Cellar/pyenv/1.2.8: 612 files, 2.4MB ==> Caveats ==> autoconf Emacs Lisp files have been installed to: /usr/local/share/emacs/site-lisp/autoconf
pyenvを使えるようにするために、bash等の場合は.bash_profile等にパスの追記等が必要だが、自分はfish shell & fisherman を使っているので、このへんはプラグインを入れるだけでOK.
~ $ fisher pyenv Installing 1 plugin/s OK Fetch pyenv github.com/fisherman/pyenv Done in 2s 26ms
python3.6をインストールしてみるも失敗する
~ $ pyenv install 3.6.7 python-build: use openssl from homebrew python-build: use readline from homebrew Downloading Python-3.6.7.tar.xz... -> https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz Installing Python-3.6.7... python-build: use readline from homebrew BUILD FAILED (OS X 10.14 using python-build 20180424) Inspect or clean up the working tree at /var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189 Results logged to /var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189.log Last 10 log lines: File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__main__.py", line 5, in <module> sys.exit(ensurepip._main()) File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__init__.py", line 204, in _main default_pip=args.default_pip, File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__init__.py", line 117, in _bootstrap return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths) File "/private/var/folders/kb/knxvk74s36s20fd1n2sh7g200000gq/T/python-build.20181107053928.7189/Python-3.6.7/Lib/ensurepip/__init__.py", line 27, in _run_pip import pip._internal zipimport.ZipImportError: can't decompress data; zlib not available make: *** [install] Error 1
最近Mojaveにアップグレードしたのが原因だった
mac os Mojave にアップグレードしたら bundle install が失敗する事象を解決した - Qiita
~ $ open /Library/Developer/CommandLineTools/Packages/ The file /Library/Developer/CommandLineTools/Packages does not exist.
上記リンクのQiita記事を参考にopen
してみるも、無いって言われる。記事の一番下の参照元リンク先みるとxcode-select
が必要っぽい。
~ $ xcode-select --install xcode-select: note: install requested for command line developer tools
インストール完了後、再びopen
コマンド実行すると今度はちゃんとフォルダが開くので、macOS_SDK_headers_for_macOS_10.14.pkg
をダブルクリックでインストールする。
再びpyenvでpython3.6をインストールすると、今度は無事成功。
それぞれのPythonを呼び出せるようにする
simlink作成(非推奨)
tox等で使えるよう、python3.6
コマンドで呼び出せるようにしたい。何も考えずに最初はsimlinkで対応した。
$ cd /usr/local/bin $ ln -s ~/.pyenv/versions/3.6.7/bin/python3.6 .
でも後で気づいたが公式のやりかたがあった
pyenv global コマンド
~ $ pyenv versions * system (set by /Users/kk6/.pyenv/version) 2.7.15 3.4.9 3.5.6 3.6.7
上記のような状態で、とりあえず3系全部動くようにしたいとする。pyenv global
に使いたいバージョンを列挙すればいいだけ。
~ $ pyenv global 3.4.9 3.5.6 3.6.7 ~ $ pyenv versions system 2.7.15 * 3.4.9 (set by /Users/kk6/.pyenv/version) * 3.5.6 (set by /Users/kk6/.pyenv/version) * 3.6.7 (set by /Users/kk6/.pyenv/version) ~ $ python3.5 Python 3.5.6 (default, Nov 7 2018, 06:07:53) [GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>>
これだけ。いちいちsimlink張るより楽。ただ、simlinkのほうが起動は速い。
【追記】
pyenv global
するとき、バージョンが新しいものから列挙してやらないとpython3
コマンドが3.4
を見に行ったりするので以下のようにする。
$ pyenv global 3.7.1 3.6.7 3.5.6 3.4.9 2.7.15
brewで入れたpython3.7をどうするか問題
brewのpythonとpyenvのpython混在とか地獄を見そうなのでbrewのpythonを消そうと思ったが…
~ $ brew uninstall python3 Error: Refusing to uninstall /usr/local/Cellar/python/3.7.0 because it is required by thefuck and vim, which are currently installed. You can override this and force removal with: brew uninstall --ignore-dependencies python3
python3に依存してるやつらがいるから消せないとのこと。
thefuck
に関してはわりとどうでもいい。vim
もいい機会だしneovim
にするのもありかもしれない。pyenvのpython3.7インストールしたら問題無さそうな気もするし、問題があればpythonインストール後にこいつらをbrewでインストールし直すのでもいけそうではある。
brewのpython消してpyenvで入れ直した
やっぱり3.7だけbrewで他のバージョンがpyenvなの気持ち悪すぎるのでpyenvに統一する。
$ brew uninstall thefuck vim python3
したあと$ pyenv install 3.7.1
した。
で、既存のvenvが壊れるのは想定内で、pipenvが壊れるのも想定内(brewのpython3.7で入れていた)。ただ、pipも動かないとは思ってなかった。
$ pipenv Failed to execute process '/Users/kk6/.local/bin/pipenv'. Reason: The file '/Users/kk6/.local/bin/pipenv' specified the interpreter '/usr/local/opt/python/bin/python3.7', which is not an executable command. $ pip Failed to execute process '/usr/local/bin/pip'. Reason: The file '/usr/local/bin/pip' specified the interpreter '/usr/local/opt/python/bin/python3.7', which is not an executable command. $ which pip /usr/local/bin/pip
エラーメッセージ見れば分かる通り、/usr/local/bin/
にゴミが残っている。ついでにpip3も消しておく。
$ which pip /Users/kk6/.pyenv/shims/pip $ pip Usage: pip <command> [options] Commands: install Install packages. download Download packages. ...
これで動くようになった。
あとはpipenvを入れ直す。
$ pip install --user pipenv
以上でひとまずpyenvへの移行完了です。