#11 pyproject.tomlとhatch-vcs: gitタグから自動バージョニング - Python OSS開発記録
リリースのたびに「pyproject.tomlのversionを書き換えて...」ってめちゃくちゃ面倒じゃないですか?
hatch-vcsを使えば、gitタグからバージョン番号を自動取得できます。今回はwagtail-reusable-blocksでの設定を振り返ります。
KEY TAKEAWAYS
この記事でわかること
- 手動バージョン管理の問題点と自動化のメリット
- hatch-vcsの仕組み(gitタグ → バージョン番号)
- pyproject.tomlのdynamic versioningの設定
用語の定義
TERM 01
hatch-vcs
Hatchling用のプラグイン。VCS (Version Control System) のタグからバージョン番号を自動取得。gitタグ「v0.1.0」からバージョン「0.1.0」を生成します。
TERM 02
pyproject.toml
Pythonプロジェクトのメタデータファイル (PEP 518/621)。パッケージ名・バージョン・依存関係・ビルド設定などを一元管理します。setup.pyの後継です。
TERM 03
uv
Rust製の超高速Pythonパッケージマネージャー。pip/pip-toolsの代替で、キャッシュ利用時は80〜115倍速いとされています。Astral社が開発しています。
TERM 04
Dynamic Versioning
バージョン番号をビルド時に動的に決定する仕組み。pyproject.tomlで「dynamic = ["version"]」と指定し、VCSタグから自動取得します。
参考: PEP 621 / hatch-vcs GitHub
手動バージョン管理の問題
従来は、リリースのたびにpyproject.tomlのversionを手動で書き換える必要がありました。これ、経験したことありませんか?
よくある手動バージョン管理の悲劇
従来の手動フロー
pyproject.tomlを開くversion = "0.0.9"→version = "0.1.0"に書き換え- コミット・プッシュ
- タグ作成
git tag v0.1.0 - タグプッシュ
git push origin v0.1.0 - リリース → あれ、pyproject.tomlとタグのバージョンが違う!
よくある問題
問題1: バージョン不一致
pyproject.tomlは「0.1.0」、gitタグは「v0.0.10」← どっちが正しい?
→ ユーザー混乱、パッケージ情報が信頼できない
問題2: 書き換え忘れ
タグは「v0.1.0」なのに、pyproject.tomlは「0.0.9」のまま
→ PyPIに「0.0.9」として公開される、タグと矛盾
問題3: コミット汚染
バージョン書き換えだけのコミットが量産される
→ 「chore: bump version to 0.1.0」← これ本当に必要?
問題4: 手順が多い
ファイル編集 → コミット → タグ作成 → プッシュ
→ ヒューマンエラーのリスク(トピック#9参照)
hatch-vcsを使えば全て解決
gitタグがバージョンの唯一の情報源 (Single Source of Truth) になります。 pyproject.tomlのバージョンは自動取得されるので、書き換え不要。 タグをpushするだけで、正しいバージョンが自動設定されます。
hatch-vcsの仕組み
hatch-vcsはビルド時にgitタグを読み取り、バージョン番号を自動生成します。内部ではsetuptools-scm(v8.2.0以上)を使用しています。
動作の流れ

バージョン番号の変換ルール
| gitタグ | 生成されるバージョン | 説明 |
|---|---|---|
v0.1.0 | 0.1.0 | 先頭の「v」を削除 |
v0.1.0-rc1 | 0.1.0rc1 | リリース候補版 |
タグなし | 0.0.0.dev0+g1234567 | 開発版(コミットハッシュ付き) |
タグから3コミット先 | 0.1.1.dev3+g7890abc | タグ後の開発版 |
Single Source of Truth: gitタグがバージョンの唯一の情報源。 pyproject.toml、__version__、パッケージメタデータ、全て同じバージョンが自動設定される。 不一致が起きない仕組みです。
実装: pyproject.toml設定
wagtail-reusable-blocksの実際の設定を見ていきましょう。PEP 621に準拠したモダンな構成です。
[project] - 基本情報
ポイント
PEP 621で標準化されたメタデータ形式です。dynamic = ["version"]を指定することで、バージョンを動的に取得することを宣言します。
dynamic: 動的に決定するフィールドを宣言version = "x.x.x"の記述は不要nameだけは動的にできない(PEP 621の制約)
[project]
name = "wagtail-reusable-blocks"
dynamic = ["version"]
description = "Reusable content blocks..."
readme = "README.md"
license = { text = "BSD-3-Clause" }
authors = [{ name = "kkm-horikawa" }]
requires-python = ">=3.10"
dependencies = [
"django>=4.2",
"wagtail>=5.0",
][build-system] - ビルド設定
ポイント
ビルドバックエンドとしてHatchlingを使用し、hatch-vcsプラグインを追加します。PEP 517に準拠した設定です。
requires: ビルドに必要なパッケージhatchling: モダンなビルドバックエンドhatch-vcs: VCSバージョニングプラグイン
[build-system] requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build"
[tool.hatch.version] - バージョン取得設定
ポイント
バージョンをVCS(Git)から取得することを指定します。version_schemeで開発版番号の生成ルールを制御できます。
source = "vcs": Gitからバージョン取得no-guess-dev: 次バージョンを推測しない- CI/CD環境では
fetch-depth: 0が必要
[tool.hatch.version] source = "vcs" [tool.hatch.version.raw-options] version_scheme = "no-guess-dev"
[tool.hatch.build.targets.wheel] - パッケージング設定
ポイント
wheelファイル生成時に含めるディレクトリを指定します。wagtail-reusable-blocksではsrc/レイアウトを採用しています。
packages: wheelに含めるパッケージパス- src/レイアウト: テスト時のimportミスを防止
[tool.hatch.build.targets.wheel] packages = ["src/wagtail_reusable_blocks"]
参考: Writing your pyproject.toml - Python Packaging User Guide
uvでのビルド
wagtail-reusable-blocksでは、uv(超高速Pythonパッケージマネージャー)を使用しています。Astral社(Ruffの開発元)製で、pipより8〜10倍、キャッシュ利用時は80〜115倍速いとされています。
なぜuvを使うのか
圧倒的な速度
Rust製で爆速。
キャッシュなしでも8〜10倍速い。
キャッシュありで80〜115倍速い。
シンプルなコマンド
uv buildだけ。
pip + build よりシンプル。
学習コスト低い。
オールインワン
pip, pip-tools, pipx,
poetry, pyenv, virtualenv
を1ツールで代替可能。
GitHub Actionsでのビルド
トピック#10で紹介したpublish.ymlでの設定例です。uvをインストールし、uv buildを実行するだけでパッケージが生成されます。
- uvをインストール(astral-sh/setup-uv@v4)
uv buildで.whlと.tar.gz生成- hatch-vcsが自動でバージョン埋め込み
# GitHub Actionsで実行 - name: Install uv uses: astral-sh/setup-uv@v4 - name: Build package run: uv build # dist/に生成される # wagtail_reusable_blocks-0.1.0-py3-none-any.whl # wagtail_reusable_blocks-0.1.0.tar.gz
参考: uv - Astral Docs / uv: Python packaging in Rust - Astral
実際の動作フロー
タグpushからパッケージ生成までの流れを見ていきましょう。開発者がやることはタグをpushするだけです。
完全自動バージョニング
- 01
開発者: タグをpush
git tag v0.1.0 && git push origin v0.1.0
→ pyproject.tomlの編集は不要 - 02
GitHub Actions: ビルド開始
publish.yml(トピック#10)がトリガーされます。
- 03
checkout: git履歴を全取得
fetch-depth: 0でタグ情報も取得します。 - 04
uv build: パッケージビルド
hatchlingがhatch-vcsを呼び出し、gitタグ「v0.1.0」を読み取り、
バージョン「0.1.0」を生成してパッケージに埋め込みます。 - 05
dist/にファイル生成
wagtail_reusable_blocks-0.1.0-py3-none-any.whlwagtail_reusable_blocks-0.1.0.tar.gz - 06
PyPI公開: バージョン0.1.0として公開
gitタグ、パッケージ、PyPI、全て同じバージョン。不一致なし。
開発者がやること
git tag v0.1.0 && git push origin v0.1.0 だけ。
pyproject.tomlの編集、バージョン番号のコピペ、コミット、全て不要。
タグが全ての情報源です。
OSS vs 企業プロジェクト: バージョン管理戦略
OSSでも企業プロジェクトでも、gitタグをバージョンの唯一の情報源にする考え方は共通です。
OSS: セマンティックバージョニング + hatch-vcs
wagtail-reusable-blocksでは、セマンティックバージョニング(major.minor.patch)を採用しています。
バージョン番号のルール
| 変更の種類 | 上げる部分 | 例 |
|---|---|---|
| 破壊的変更 | major | 0.1.0 → 1.0.0 |
| 新機能追加 | minor | 0.1.0 → 0.2.0 |
| バグ修正 | patch | 0.1.0 → 0.1.1 |
hatch-vcsとの連携
リリースフロー
- Release Drafter(トピック#9)がラベルからバージョン推定
- 開発者がRelease Draftを確認してタグ名決定(例: v0.2.0)
- タグpush → GitHub Actions実行
- hatch-vcsがタグからバージョン取得(0.2.0)
- PyPIに正しいバージョンで公開
トピック#9・#10・#11の連携: Release Drafter(#9)でバージョン推定 → Trusted Publishing(#10)で自動公開 → hatch-vcs(#11)で正しいバージョン埋め込み。 全て自動化、人間の手作業ゼロです。
企業: Dockerイメージタグ + アプリケーションバージョン
企業プロジェクトでも、gitタグをバージョンの唯一の情報源にする考え方は同じです。
Dockerイメージタグの例
CI/CDでのイメージタグ付け
- gitタグ
v0.1.0を push - CI/CDがタグを読み取り
- Dockerイメージを
myapp:0.1.0としてビルド - コンテナレジストリにpush
- k8sにデプロイ時に
image: myapp:0.1.0を指定
アプリ内のバージョン表示
Pythonアプリの場合、importlib.metadataを使ってパッケージバージョンを取得できます。ログや管理画面に表示すると、デプロイ時のトラブルシューティングが楽になります。
- hatch-vcsで
__version__自動生成 - ログ・管理画面・APIにバージョン表示
- gitタグと一致したバージョンが表示される
from importlib.metadata import version
__version__ = version("myapp")
# ログ出力
import logging
logger.info(f"App version: {__version__}")
# 管理画面フッター
# Version: 0.1.0 (matches git tag v0.1.0)企業プロジェクトでもgitタグが情報源
PyPIパッケージでもDockerイメージでも、gitタグをバージョンの唯一の情報源にする考え方は同じ。 手動でバージョンファイルを書き換える運用は、ヒューマンエラーの温床(トピック#9参照)です。
よくある質問
A: 開発版バージョンが自動生成されます。
hatch-vcsはタグがなくても動作します。
例: 0.0.0.dev0+g1234567(コミットハッシュ付き)
最後のタグから3コミット進んでいたら 0.1.1.dev3+g7890abc のように生成されます。
A: タグ情報を取得するためです。
GitHub Actionsのactions/checkout@v4はデフォルトで最新1コミットのみ取得します。
hatch-vcsはgitタグを読む必要があるため、fetch-depth: 0で全履歴を取得します。
これがないとタグが見えず、バージョン番号を正しく生成できません。
A: 機能はほぼ同じですが、ビルドバックエンドが違います。
setuptools-scmはsetuptools用、hatch-vcsはHatchling用です。 実際、hatch-vcsは内部でsetuptools-scm(v8.2.0以上)を使用しています。 wagtail-reusable-blocksはHatchling(モダンなビルドバックエンド)を採用しているため、hatch-vcsを使用しています。
A: uvはRust製で圧倒的に速いです。
pip/pip-toolsの代替として開発されたツールで、キャッシュなしで8〜10倍、キャッシュありで80〜115倍速いとされています。
コマンド体系もシンプル(uv build、uv pip install等)。
既存のpyproject.tomlをそのまま使えるため、移行コストもほぼゼロです。
A: 開発版番号の生成ルールです。
no-guess-devは、次のバージョンを推測せず、現在のタグベースで開発版番号を生成します。
例: v0.1.0タグから3コミット先 → 0.1.1.dev3(次のpatchバージョン + dev番号)
デフォルトのguess-next-devだと次のバージョンを推測しますが、誤推測のリスクがあるため明示的な設定を採用しています。
A: はい、使えます。
PyPIに公開しない社内アプリでも、pyproject.tomlでバージョン管理することは可能です。
アプリ内でimportlib.metadata.version("myapp")でバージョン取得し、ログ・管理画面に表示できます。
gitタグとアプリバージョンが一致するため、デプロイ時のトラブルシューティングが楽になります。


まだコメントはありません。最初のコメントを残しませんか?