PipenvからPoetryへのお引越しツール作った

動機

1/13にPoetry 0.12.11 がリリースされました。

poetry.eustace.io

それに伴い、poetry init で会話形式でいろいろ設定していく際に依存パッケージを事前に検索して記述する機能のバグが修正されました。

$ poetry init

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

Package name [foo]:
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] yes

Search for package:

ここのことです。

前のバージョンだと検索するとLegacyRipositoryなんちゃらってエラーで落ちてたので使わずスキップするしかなかったんですが、直ったとのことなのでさっそく試してみると…

Search for package: django

Found 100 packages matching django

Enter package # to add, or the complete package name if it is not listed:
 [ 0] django-bagou
 [ 1] django-maro
 [ 2] django-ide
 [ 3] django-hooked
 [ 4] django-six
 [ 5] django-umanage

(中略)

 [95] django-bittersweet
 [96] django-perm
 [97] django-simplemde
 [98] django-rosetta
 [99] django-pjax
 >

ああ、うん…。そっか、そういうやつなんだ…。

引っ越しツール

これだとPipfile見ながらひとつひとつ poetry add django とかしていくのとそんな大差ない気がするのでPipfileパースしていい感じに poetry init を実行してくれるツール作りました。

github.com

たぶんMac以外だと全然動かなかったりしそうなのでPyPIには登録してません。名前もイマイチ思いつかなかくて長ったらしいしかえるかもしれない(変えた*1)ので、ひとまず個人的に使うだけのつもりですが、それでも試したい人いたらどうぞ。

PyPI に登録しました。Mac以外だとまともに動かない気がするのには変わりないのであくまでアルファ版ということで。

pypi.org

概要

poetry init コマンドは --dependency--dev-dependencyオプションにパッケージ名を与えることで、pyproject.toml[tool.poetry.dependencies] および [tool.poetry.dev-dependencies] セクションにいい感じに依存バージョン解決して書き込んでくれるという機能があります。

今回作ったツールはPipfileをパースしてこの--dependency/--dev-dependencyオプションの文字列を組み立ててくれるツールになってます。

インストール

READMEに二通りのインストール方法書いてますけど、今回はこっちの $HOME/.local/bin 以下にインストールする方法にしてみます。リポジトリsetup.py がないので pip install --user git+https://github.com/kk6/poetrify.git ではインストールできません。ごめんなさい。

PyPIに登録したのでpipでインストールできます。

$ pip install poetrify

引数無しで実行して以下のように出力されていればインストールできてます。

$ poetrify
Poetrify version 0.2.0

USAGE
  poetrify [-h] [-q] [-v [<...>]] [-V] [--ansi] [--no-ansi] [-n] <command> [<arg1>] ... [<argN>]

ARGUMENTS
  <command>              The command to execute
  <arg>                  The arguments of the command

GLOBAL OPTIONS
  -h (--help)            Display this help message
  -q (--quiet)           Do not output any message
  -v (--verbose)         Increase the verbosity of messages: "-v" for normal output, "-vv" for more verbose output and
                         "-vvv" for debug
  -V (--version)         Display this application version
  --ansi                 Force ANSI output
  --no-ansi              Disable ANSI output
  -n (--no-interaction)  Do not ask any interactive question

AVAILABLE COMMANDS
  completions            Generate completion scripts for your shell.
  generate               Generate pyproject.toml from Pipfile
  help                   Display the manual of a command

使い方

こんな感じのリポジトリがあるとします。実際は他にもソースコード等あると思いますけど。

$ tree
.
├── LICENSE
└── Pipfile

0 directories, 2 files

Pipfileの中身はこんな感じだとします。

[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[dev-packages]
pytest = "*"
pytest-cov = "*"
"pytest-flake8" = "*"
responses = "*"
pytest-runner = "*"

[packages]
rauth = "*"
requests = "*"
requests-cache = "*"
furl = "*"
arrow = "*"
pytest = "*"
responses = "*"

[requires]
python_version = "3.7"

ツールの generate コマンドを実行します。

$ poetrify generate
Generated init command:

poetry init --dependency=rauth --dependency=requests --dependency=requests-cache --dependency=furl --dependency=arrow --dependency=pytest --dependency=responses --dev-dependency=pytest --dev-dependency=pytest-cov --dev-dependency=pytest-flake8 --dev-dependency=responses --dev-dependency=pytest-runner --license=MIT

Execute the above command. Also, the following output is due to Poetry.

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

Package name [foo]:

LICENSEファイルもパースしてライセンスを自動判定させてるので、これもちゃんと判定できてれば会話モードで License [MIT]: みたいな感じでデフォルト値としてパースされたライセンス名が入ってるはずです。

initコマンドを組み立てたらあとは雑に subprocess.run() に投げてるだけです…。

あとは普通に poetry init の会話モードでの入力となります。

Package name [foo]:
Version [0.1.0]:
Description []:
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]

Using version ^0.7.3 for rauth
Using version ^2.21 for requests
Using version ^0.4.13 for requests-cache
Using version ^2.0 for furl
Using version ^0.13.0 for arrow
Using version ^4.1 for pytest
Using version ^0.10.5 for responses

Would you like to define your dev dependencies (require-dev) interactively (yes/no) [yes]

Using version ^4.1 for pytest
Using version ^2.6 for pytest-cov
Using version ^1.0 for pytest-flake8
Using version ^0.10.5 for responses
Using version ^4.2 for pytest-runner

Generated file

[tool.poetry]
name = "foo"
version = "0.1.0"
description = ""
authors = ["kk6 <hiro.ashiya@gmail.com>"]
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"
rauth = "^0.7.3"
requests = "^2.21"
requests-cache = "^0.4.13"
furl = "^2.0"
arrow = "^0.13.0"
pytest = "^4.1"
responses = "^0.10.5"

[tool.poetry.dev-dependencies]
pytest = "^4.1"
pytest-cov = "^2.6"
pytest-flake8 = "^1.0"
responses = "^0.10.5"
pytest-runner = "^4.2"

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

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

基本的にgenerateコマンド叩いた後はEnter押しっぱなしでいい感じにpyproject.tomlが生成されると思います。

既知の不具合

poetry init自体がエラーで落ちるようなパッケージ名を与えると当然このツールも落ちます。

$ poetry init --dependency black

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

Package name [foo]:
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]


[ValueError]
Could not find a matching version of package black

init [--name NAME] [--description DESCRIPTION] [--author AUTHOR] [--dependency DEPENDENCY] [--dev-dependency DEV-DEPENDENCY] [-l|--license LICENSE]

たぶんpipenvで allow_prereleases = true に設定してないとインストールできないようなパッケージはこうなります。

--dev-dependency=black:18.9b0 みたいにプレリリースなバージョン番号を指定できればちゃんとインストールできるんですけど、pipenvって pipenv install black --pre とインストールしてもPipfileには black = "*" って何でもかんでもアスタリスクにしてしまうのでバージョン番号がわからないんですよね…。Pipfile.lockのほうに書いてあるけど、こっちパースするの面倒くさそうなので気が向いたらやるかもって感じです。

なのでとりあえずPipfileから該当パッケージ行を削除してやり直して、うまくpyproject.tomlが生成できたら、あとから手動で poetry add --allow-prereleases black みたいな感じで --allow-prereleases オプション付きでインストールしてください。

*1:pipenv2poetry → poetrify