Python パッケージング #7 チームの規約と道具の選択 — uv、poetry、pip

読了 5分

1 人で使うときのパッケージングは好みの問題ですが、チームでは規約の問題になります。シリーズ最終回は、その規約を立てるのに必要な 2 つ、道具を選ぶ基準と、チーム・CI・Docker での標準パターンを整理して、全体を振り返ります。

道具の選択 — 3 つの候補を 1 つの表に #

2026 年現在、実質的な候補は 3 つです。

区分uvpoetrypip + venv
ロックuv.lock (標準装備)poetry.lock (標準装備)別の道具が必要 (pip-tools)
Python バージョン管理内蔵 (uv python)なし (別の道具)なし (別の道具)
速度基準点 (最速)数倍遅いロックのコンパイルまで含めると大幅に遅い
標準への準拠pyproject.toml + PEP 735/7512.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
# .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 — イメージの中でも同じ規約で #

プロダクションのイメージでも原則は同じです。ロックのとおりに、開発依存なしで、キャッシュを生かしてです。

Dockerfile
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. 道具はリポジトリごとに 1 つ: README に明記し、他の道具のコマンドをドキュメントから消します。
  2. コミットの対象: pyproject.toml と uv.lock はコミット、.venv.gitignore。ロックのない PR は CI が拒否します。
  3. 宣言は範囲、ロックは正確に: 直接依存だけを pyproject.toml に、推移的依存はロックに任せます。
  4. Python バージョンは .python-version で固定: 「私は 3.12 なんですが」が起きないようにします。
  5. アップグレードは独立 PR + 定期ルーチン: 機能 PR にロックの変更が混ざっていたら、レビューで分離を求めます。
  6. 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 すれば、誰のマシンでも同じ環境が立つ」という状態は、その上に載るすべてのコード・テスト・デプロイの床です。このシリーズがその床を固めるのに役立つことを願っています。この上に何を建てるかは、すでに用意されています。テストでコードを守り、自動化で繰り返しを消し、データ分析で質問に答えるシリーズは、どれもこの床の上で動きます。

X