Python パッケージング #7 チームの規約と道具の選択 — uv、poetry、pip
1 人で使うときのパッケージングは好みの問題ですが、チームでは規約の問題になります。シリーズ最終回は、その規約を立てるのに必要な 2 つ、道具を選ぶ基準と、チーム・CI・Docker での標準パターンを整理して、全体を振り返ります。
道具の選択 — 3 つの候補を 1 つの表に #
2026 年現在、実質的な候補は 3 つです。
| 区分 | uv | poetry | pip + venv |
|---|---|---|---|
| ロック | uv.lock (標準装備) | poetry.lock (標準装備) | 別の道具が必要 (pip-tools) |
| Python バージョン管理 | 内蔵 (uv python) | なし (別の道具) | なし (別の道具) |
| 速度 | 基準点 (最速) | 数倍遅い | ロックのコンパイルまで含めると大幅に遅い |
| 標準への準拠 | pyproject.toml + PEP 735/751 | 2.x から標準に収束 | 標準の基準点 |
| 強み | 統合ワークフロー、速度 | 成熟したエコシステム、長い実績 | どこにでもある、依存ゼロ |
判断は状況ごとに分かれます。
- 新規プロジェクトは uv です。 #4 で見たとおり、環境・ロック・Python バージョンまで 1 つの道具で終わり、エコシステムの重心もすでにこちらです。
- poetry でうまく回っているチームは急ぐ理由がありません。 poetry は 2.x で標準形式に収束し、活発に維持されています。マイグレーションは道具の交換そのものより、メンバーの習慣と CI スクリプトの転換がコストなので、「次の新規プロジェクトから uv」が現実的な移行の道筋です。
- pip + venv が正解の場面も残ります。 外部ツールをインストールできない制約環境、標準ライブラリだけで回さなければならない最小構成がそうです。この場合、ロックは pip-tools か、#5 で見た標準 pylock.toml で補います。
避けるべきは道具そのものではなく混用です。1 つのリポジトリで、ある人は pip install、ある人は uv add を使うと、宣言とロックがずれ始めます。リポジトリごとに道具を 1 つ決めて README の最初の行に明記するのが、規約の始まりです。
CI — キャッシュと frozen 検証 #
テスト #7 の CI にパッケージングの規約を載せると、この形になります。
# .github/workflows/ci.yml (核心部)
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true # uv のキャッシュをランナー間で再利用
- run: uv sync --frozen # ロックのとおり再現、ずれたら失敗
- run: uv run pytestポイントは 2 つです。キャッシュは uv のグローバルキャッシュを CI ランナー間で再利用し、インストール時間を秒単位に縮めます。--frozen は #5 で立てた原則の執行装置で、ロックの更新を忘れた PR を CI が捕まえます。ここに月次の依存関係アップグレード PR(Renovate・Dependabot)と pip-audit のスキャンまで掛ければ、#5 の運用ルーチンがすべて自動化されます。
Docker — イメージの中でも同じ規約で #
プロダクションのイメージでも原則は同じです。ロックのとおりに、開発依存なしで、キャッシュを生かしてです。
FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
# 依存関係のレイヤー: ロックが変わらなければキャッシュヒット
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
# アプリのレイヤー: コードだけ変わればここから再ビルド
COPY . .
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "python", "main.py"]依存関係のインストールとコードのコピーをレイヤーとして分けるのが核心です。コードだけ直したビルドでは依存関係のレイヤーがキャッシュで素通りし、ビルド時間が大きく縮みます。--no-dev は pytest や ruff のような dev グループをプロダクションのイメージから外してくれます。#3 で extras と dependency-groups を分けておいたことが、ここで効いてきます。
チーム規約のチェックリスト #
新しいメンバーが加わっても崩れない状態を基準に整理すると、こうなります。
- 道具はリポジトリごとに 1 つ: README に明記し、他の道具のコマンドをドキュメントから消します。
- コミットの対象: pyproject.toml と uv.lock はコミット、
.venvは.gitignore。ロックのない PR は CI が拒否します。 - 宣言は範囲、ロックは正確に: 直接依存だけを pyproject.toml に、推移的依存はロックに任せます。
- Python バージョンは
.python-versionで固定: 「私は 3.12 なんですが」が起きないようにします。 - アップグレードは独立 PR + 定期ルーチン: 機能 PR にロックの変更が混ざっていたら、レビューで分離を求めます。
- CI は frozen、プロダクションは no-dev: 再現は機械的に、開発ツールはイメージの外へ。
シリーズを終えて #
7 本の旅を振り返ります。
- #1 仮想環境: 環境が壊れる構造的な原因と venv、activate の正体を見ました。
- #2 pip と requirements.txt: 伝統的ワークフローを身につけ、意図とスナップショットが 1 ファイルに混ざる限界を確認しました。
- #3 pyproject.toml: 意図が住む標準の住まいと、extras・dependency-groups の区別を覚えました。
- #4 uv: 環境・インストール・ロック・Python バージョンが 1 つの道具に畳み込まれる現代のワークフローを見ました。
- #5 依存関係のロック: uv.lock を直接読み、frozen の原則とアップグレードのルーチン、標準 pylock.toml を扱いました。
- #6 公開: 受け取る側から作る側に立って、wheel のビルドと PyPI、Trusted Publishing を身につけました。
- #7 チームの規約: そのすべてを、道具選択の基準と CI・Docker・チームのチェックリストにまとめました。
パッケージングは華やかなテーマではありません。しかし「クローンして sync すれば、誰のマシンでも同じ環境が立つ」という状態は、その上に載るすべてのコード・テスト・デプロイの床です。このシリーズがその床を固めるのに役立つことを願っています。この上に何を建てるかは、すでに用意されています。テストでコードを守り、自動化で繰り返しを消し、データ分析で質問に答えるシリーズは、どれもこの床の上で動きます。