リリース作業でハマったPyPIバージョン問題と解決(開発日記 No.101)
関連リンク
はじめに
昨日は開発中の Python パッケージのリリース準備を進めました。今日は、そのリリース作業を完遂させるべく、PyPI への公開とそれに伴う諸々の作業に取り組みました。
背景と目的
開発してきた Python パッケージを、Python Package Index (PyPI) で公開し、誰でも pip install でインストールできるようにすることが最終的な目的です。プロジェクトにはリリースガイドが用意されているので、それに従って確実なリリースを目指します。
検討内容
リリースガイドには、バージョン更新、変更履歴の記載、リリースブランチ作成、GitHub でのリリース作成、PyPI 公開、そしてリリース後のブランチマージといった一連の手順が示されています。この手順に沿って一つずつ進める計画でした。
しかし、PyPI 公開のステップで予期せぬ問題が発生しました。GitHub Actions による自動公開ワークフローは成功したにも関わらず、PyPI 上で確認するとバージョンが古いままだったのです。
この原因を探るため、GitHub Actions のログを詳細に確認し、パッケージのビルド設定ファイル(setup.pyやpyproject.toml)の内容を再確認する必要が出てきました。また、その過程で Python のシステム環境と仮想環境に関する問題にも直面しました。
実装内容
リリースガイドの最初のステップから順に進めました。
-
バージョン番号の更新:
まず、setup.pyを開き、バージョンを"0.1.0"から"1.0.0"に変更しました。setup( name="content-converter", - version="0.1.0", + version="1.0.0", author="Centervil", author_email="info@centervil.example.com", -
変更履歴の更新:
CHANGELOG.mdに[1.0.0]セクションを追加し、[Unreleased]の内容を移動しました。## [Unreleased] + + + ## [1.0.0] - 2024-06-08 -
リリースブランチの作成:
release/v1.0.0ブランチを作成し、上記の変更をコミットしてプッシュしました。git checkout -b release/v1.0.0 && git add setup.py CHANGELOG.md && git commit -m "Prepare release v1.0.0" && git push origin release/v1.0.0このコマンドは無事成功しました。
-
GitHub でリリース作成:
GitHub の Web UI からv1.0.0タグでリリースを作成しました。これにより、PyPI への自動公開ワークフローがトリガーされるはずです。 -
PyPI への公開確認(問題発生):
GitHub Actions のワークフローは成功と表示されましたが、PyPI のプロジェクトページを確認すると、バージョンが0.1.0のままでした。原因を探るため、Actions のログを確認しました。
deployジョブの詳細を見ると、PyPI へのアップロードステップでHTTPError: 400 Bad Requestが発生していました。これは通常、同じバージョンが既に存在する場合に起こります。なぜバージョンが
0.1.0のままビルドされたのか?ローカルでパッケージをビルドしてみることにしました。まずdist/ディレクトリをクリーンアップして再ビルドを試みました。rm -rf dist/ && python3 -m buildしかし、
python3 -m buildがNo module named buildで失敗。pip install buildを試みると、システムの Python 環境が外部管理モード(PEP 668)であるため、権限エラーでインストールできませんでした。pip install builderror: externally-managed-environment × This environment is externally managed ╰─> To install Python packages system-wide, try apt install ...そこで、仮想環境を作成してその中でビルドツールをインストールし、改めてビルドを実行しました。
python3 -m venv .venv && source .venv/bin/activate && pip install --upgrade pip && pip install build && python3 -m buildこのコマンドは成功し、
dist/ディレクトリにパッケージが生成されました。しかし、生成されたファイル名を見るとcontent_converter-0.1.0.tar.gzやcontent_converter-0.1.0-py3-none-any.whlとなっており、やはりバージョンが0.1.0でした。setup.pyは1.0.0にしたはずなのに...?ここで、pyproject.tomlの存在を思い出しました。確認すると、pyproject.tomlにもバージョン指定があり、そちらが"0.1.0"のままでした。ビルドツールによってはpyproject.tomlの設定が優先されるため、これが原因だと特定しました。[project] name = "content-converter" version = "0.1.0" # これが原因だった!PyPI は一度公開されたバージョンを上書きできないため、バージョンを
1.0.0のまま再アップロードすることはできません。そこで、バージョンを1.0.1に上げて再リリースする方針に切り替えました。 -
バージョンを 1.0.1 に上げて再リリース:
pyproject.tomlとsetup.pyの両方でバージョンを1.0.1に更新しました。[project] name = "content-converter" - version = "1.0.0" + version = "1.0.1" description = "マークダウンファイルを各種公開プラットフォーム用に変換するツール"setup( name="content-converter", - version="1.0.0", + version="1.0.1", author="Centervil", author_email="info@centervil.example.com",再度
dist/をクリーンアップし、仮想環境でビルドを実行。今度はcontent_converter-1.0.1...というファイルが生成され、正しいバージョンでビルドできたことを確認しました。次に、
release/v1.0.1ブランチを作成し、変更をコミット・プッシュしようとしました。git checkout -b release/v1.0.1 && git add setup.py pyproject.toml CHANGELOG.md && git commit -m "Prepare release v1.0.1" && git push origin release/v1.0.1しかし、このコマンドは途中で中断されたか、既にローカルにブランチが存在していたようで、ブランチ作成部分でエラーになりました。
git statusで確認すると、バージョン変更などのファイルが未コミットのまま残っていました。既存の
release/v1.0.1ブランチに切り替え、未コミットの変更を改めてコミット・プッシュしました。git checkout release/v1.0.1 git add . && git commit -m "Apply version 1.0.1 and update CHANGELOG for release" && git push origin release/v1.0.1これで、
release/v1.0.1ブランチにバージョン 1.0.1 の変更が反映されました。再び GitHub の Web UI から
v1.0.1タグでリリースを作成しました。今度は GitHub Actions のワークフローも成功し、PyPI のプロジェクトページでバージョン1.0.1が最新として表示されていることを確認できました。 -
main ブランチへのマージ:
リリースブランチの変更をmainブランチに取り込みました。git checkout main && git merge --no-ff release/v1.0.1 && git push origin mainこれも無事成功し、
mainブランチが最新の状態になりました。 -
develop ブランチの更新(と、その後の発見):
リリースガイドにはdevelopブランチの更新も記載されていましたが、ここで予期せぬ事態が発生しました。developブランチへ切り替えようとすると、error: pathspec 'develop' did not match any file(s) known to gitというエラーが表示され、ブランチが存在しないことが判明したのです。git checkout develop && git merge main && git push origin develop # => error: pathspec 'develop' did not match any file(s) known to gitリモートブランチを確認してみても
developブランチは見当たりませんでした。git branch -r # => origin/main # origin/release/v1.0.0 # origin/release/v1.0.1 # ... (他のブランチ)このプロジェクトでは
developブランチを運用していなかったため、このステップは不要と判断し、リリース作業はこれで完了としました。
技術的なポイント
今回のリリース作業を通して、いくつかの重要な技術的ポイントと学びがありました。
-
setup.pyとpyproject.tomlのバージョン管理の優先順位:
Python パッケージのビルドにおいて、setup.pyとpyproject.tomlの両方にバージョン情報が記述されている場合、ビルドツール(特にbuildパッケージ)はpyproject.tomlの設定を優先することが分かりました。今回はこの認識不足が、PyPI への古いバージョン公開の原因となりました。今後はpyproject.tomlを正と見て管理し、両ファイルの整合性を保つよう意識する必要があります。 -
pip installのexternally-managed-environmentエラーと仮想環境:
システムの Python 環境が PEP 668 で「外部管理モード」になっている場合、pip installでグローバルにパッケージをインストールしようとすると権限エラーが発生します。この問題は、python3 -m venv .venvで仮想環境を作成し、その中でpip installを実行することで解決できました。仮想環境の適切な利用は、開発環境の汚染を防ぎ、依存関係の管理を容易にする上で不可欠であることを改めて認識しました。 -
PyPI のバージョン管理ポリシー:
PyPI は、一度公開された同じバージョン番号のパッケージを上書きすることを許可していません。これが「400 Bad Request」エラーの直接の原因でした。このため、誤ったバージョンを公開してしまった場合は、バージョン番号をインクリメントして再リリースするしかありません。リリースの際には、バージョン番号が正しいことを十分に確認する必要があります。
所感
今回のリリース作業は、想定外のトラブルシューティングの連続となりました。当初はリリースガイドに沿ってスムーズに進むはずが、PyPI でのバージョン問題、Python 環境の問題と、次々に壁にぶつかりました。
特に印象深かったのは、setup.pyとpyproject.tomlという二つの設定ファイルにおけるバージョンの競合です。普段は意識することの少ないビルド設定の細部が、公開の場で大きな影響を与えることを痛感しました。一つ一つのエラーメッセージを丹念に読み解き、仮説を立て、それを検証していくプロセスは、まさにデバッグの醍醐味であり、大きな学びとなりました。
また、developブランチが存在しないことに気づいたのも、一連の作業の中で発見でした。リリースガイドは一般的なプロジェクトのワークフローを想定しているため、必ずしも現在のプロジェクトに全てが合致するわけではないという、柔軟な視点の重要性も再認識しました。
最終的にバージョン 1.0.1 を PyPI に公開できたときは、大きな達成感がありました。これで、この Python パッケージがより多くの人々に利用される基盤が整ったことになります。
今後の課題
今回のリリース作業で、いくつかの今後の課題が浮き彫りになりました。
-
ビルド設定の簡素化と自動化の強化:
setup.pyとpyproject.tomlの間の潜在的な競合を避けるため、可能であればビルド設定を一本化できないか検討が必要です。また、GitHub Actions のワークフローをさらに改善し、ビルド前のバージョンチェックなどを組み込むことで、今回のような問題を未然に防ぐ仕組みを強化したいと考えています。 -
ドキュメントの継続的な整備:
リリースガイドは存在しましたが、今回のような詳細なトラブルシューティングの経験は、今後のリリース作業において貴重な知見となります。開発日記として記録するだけでなく、必要に応じてリリースガイド自体もアップデートし、チーム全体で共有できるナレッジベースとして整備していくことが重要です。
まとめ
本日は、開発中の Python パッケージ content-converter の PyPI へのリリース作業を完遂しました。バージョン番号の競合や Python 環境の問題など、複数の課題に直面しましたが、一つずつ原因を特定し、解決することで、最終的にバージョン 1.0.1 を PyPI に正常に公開することができました。
この経験は、単にパッケージをリリースするという目的だけでなく、ビルドシステムの理解、エラーログの分析、そして柔軟な問題解決能力を養う上で非常に貴重な機会となりました。今後も、よりスムーズで堅牢な開発・リリースプロセスを目指して、継続的な改善に取り組んでいきたいと思います。
Discussion