はじめに
夜明けは目の前ぜよ、nikkieです1。
先月(2022年12月)に、コラボレータをしているSpeechRecognitionのv3.9.0をリリースしました🎉
リリースであった一幕をアウトプットします。
目次
- はじめに
- 目次
- リリース作業の前提
- buildしたファイルをuploadしようとしたらエラー
- setup関数のname引数に指定した文字列、変わってる〜!!
- エラーが送出された原因を理解する試み
- 終わりに
リリース作業の前提
メンテナです!
オーナーのUberiさんにnikkieをSpeechRecognitionのメンテナに設定していただきました。
PyPIでは共同編集者(collaborators)に2つのロールがあり、「メンテナ」ロールを付けてもらったということです。
ヘルプより:PyPI上のプロジェクトで使用可能な共同編集者の役割には、何がありますか?
メンテナ: パッケージのリリースのアップロードができる。共同編集者の追加、ファイル・リリース・プロジェクトの削除はできない。
PyPIの認証にAPIトークンを使います
ヘルプより:APIトークンを使ってPyPIで認証するにはどうすればよいですか?
We strongly recommend you authenticate with an API token where possible.
(nikkie訳) どこでもAPIトークンで認証するのを強くオススメします
上記のリンク先に日本語で手順が記載されています(ぜひやりましょう!)。
私は、PyPIに置いているライブラリごとにAPIトークンを作っています。
PyPIへのアップロードでは、以下のようにAPIトークンを指定しました。
twine upload -u __token__ -p pypi-接頭辞を含むトークン値 dist [dist ...]
buildしたファイルをuploadしようとしたらエラー
2022年1月のみんなのPython勉強会でaodagさんが発表された手順をベースにしています。
手短に言うと
python -m build .
twine upload
です(これだけで済むなんて、ホント便利になりましたよね)。
これを実行したところ、2でエラー2🙀
The name isn't allowed
ただ、これまでのコラボレータ活動からなんとなく思い当たるところがありました。
setup関数のname引数に指定した文字列、変わってる〜!!
ドキュメントとしては以下のあたりの話になります(setup.pyの他、setup.cfgやpyproject.tomlとの対応も記載されています)。
https://setuptools.pypa.io/en/latest/userguide/quickstart.html#basic-use
buildしてできるファイルの名前はspeech-recognition-3.9.0
で始まりました。
setup関数の<name引数の値>-<version引数の値>
と参照されています。
一方、PyPIのページのURLやpip install
ではSpeechRecognition
と指定します。
結論を言えば、name引数の指定を戻し、SpeechRecognition-3.9.0
で始まるファイルをbuildしたところ、uploadできました!
name引数の指定変更の経緯を(できる範囲で)確認
PR #434で修正されています。
Fixed IBM call to use api key. by chrisspen · Pull Request #434 · Uberi/speech_recognition · GitHub
その中の1コミット:Fixed package name. · Uberi/speech_recognition@3446b13 · GitHub
PR #434は機能追加というメリットがありつつも、それを打ち消すように感じられるほどコードベースに混乱を招いている3(デメリット)フシがあります4。
「Fixed package name.」というコミットですが、「これでパッケージがリリースできるかは当時は未確認で、結果的にバグが混入してしまったということなのかな」と私の中では整理しています。
(PyPIのURLをキャメルケースからケバブケースに変えようという意図でfixなのかな)
エラーが送出された原因を理解する試み
nameを変えたらなぜエラー?
リリース時はuploadを通すことを優先していて完全なエラーメッセージを控えられていません。
アウトプットにあたり、エラーの原因を調べたところ、PyPIのヘルプの以下ではないかなと考えています。
https://pypi.org/help/#project-name
The project name is too similar to an existing project and may be confusable.
(nikkie訳) プロジェクトの名前が既存のプロジェクトに類似しすぎており、混同させやすい(ため、その名前が使えない)
SpeechRecognition
とspeech-recognition
、編集距離は小さめなので、類似していると判定されたのではないかと考えます。
なお、SpeechRecognition
は5年ほどリリースが止まっていたため、その間にfork版がPyPIにリリースされています5。
- https://pypi.org/project/SpeechRecognition-ForkedVersion/
- https://pypi.org/project/speech-recognition-fork/
nameにforkと付けることで類似しているとは判定されなくなる面もあるのかもしれないですね。
PyPIの「プロジェクト」という概念
PyPIの用語集を引きます(ありがたいことに日本語で読めるんです!)。
https://packaging.python.org/ja/latest/glossary/#term-Project
Pythonにおけるプロジェクトは、 :term:`PyPI <Python Package Index (PyPI)>`に登録される一意の名前を持っていなければなりません。
今回の経験から、「setup.pyのname引数に指定したのは、プロジェクトのnameと言えるのではないか」と考え始めています。
パッケージの名前はspeech_recognition
6ですが、PyPIからはSpeechRecognition
という名前(=プロジェクト名)でインストールします。
(ここでは深く立ち入りませんが、)pip install SpeechRecognition
でspeech_recognition
パッケージがsys.path
7の下に配置される。
だからimport speech_recognition as sr
とインポートできる、ということではないかと考えます。
プロジェクト名(SpeechRecognition
)とパッケージ名(speech_recognition
)が完全一致していなくてもいいことの説明にもなっている記載も見つけました。
そのプロジェクトを稼働させるためにインポートされるパッケージの名前に因んでプロジェクトに名前をつけるのが普通であるという強い慣習があることを覚えておいてください。しかしながら、常にそうしなければならないわけではありません。
終わりに
SpeechRecognition v3.9.0リリースで経験したエラーから、プロジェクトという概念の理解が深まりました。
setup.pyで定義しているmetadataにはプロジェクトの設定をする項目もあるということなのかなととらえ始めています(誤解している可能性もあるので、build
やtwine
やpip install
でnameをどう扱っているか、どこかでガッツリ調べ尽くしたいですね)。
1日1エントリ、頻度を上げたことでちょっとしたネタでも書きやすくなっているメリットを感じていますが、他のことを劣後させざるを得ない状況にもなっています。
並行してコラボレータ活動などやっていきたい気持ちはあるので、今回は結び付けてアウトプットしてみました。
SpeechRecognitionについてはコラボレータとしてできそうなことはいくつか浮かんでいます。
もっとコードで価値を届けていく所存ですが、スターやスポンサーで支えていただけるととっても嬉しいです!
- ↩
- コミットメッセージに控えてありましたが、完全版ではなさそうです↩
- リファクタリングの名のもとに、既存のメソッドの返り値が複数となる(タプルを返す)ように変更され、それが原因でテストが落ち続けていました(修正済みです)。ユーザが使うメソッドの挙動を変えているので、ファウラーさん(『リファクタリング』)やフェザーズさん(『レガシーコード改善ガイド』)が言っている意味での"リファクタリング"ではないですね↩
- PR #434は巨大なプルリクエストになってしまっています。タイトル以上のことをガンガンやっている、単一責任違反のプルリクエストです↩
- いくつもあるfork版とどう折り合いをつけていくか、これも抱えている宿題の1つです↩
-
__init__.py
が置かれたディレクトリ名でもあります。パッケージ名はPythonにおける名前の制約を受けますね(ハイフンが使えない、など)↩ - 「モジュールを検索するパスを示す文字列のリスト」 https://docs.python.org/ja/3/library/sys.html#sys.path↩