0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PyPI(Python Package Index)ってなんだ? — Pythonの「巨大物流倉庫」を完全理解する

0
Posted at

この記事の対象読者

  • Pythonを使い始めたが、pip install の裏側で何が起きているか知らない方
  • パッケージ管理の全体像を掴みたい中級エンジニア
  • サプライチェーン攻撃のリスクを理解し、安全にパッケージを使いたい方
  • 自作ライブラリをPyPIに公開してみたい方

この記事で得られること

  • PyPIの仕組み・歴史・規模感を一通り理解できる
  • pip / uv / conda など主要インストーラとPyPIの関係を整理できる
  • サプライチェーン攻撃の実例と防御策を把握できる
  • 自作パッケージをPyPIに公開する手順を理解できる
  • PEP 740 デジタルアテステーション(署名検証)の最新動向を知れる

この記事で扱わないこと

  • Pythonそのもののインストール手順
  • condaやpoetryの詳細な使い方(別記事で扱います)
  • PyPIミラーサーバの構築方法

1. PyPIとは何か — Python界の「巨大物流倉庫」

pip install requests と打った経験があるだろう。あのコマンドの裏で、あなたのPCは PyPI(Python Package Index) というサーバにアクセスし、パッケージをダウンロードしている。

PyPIを一言で説明するなら、Pythonパッケージの巨大物流倉庫だ。

世界中のPython開発者が「荷物(パッケージ)」をこの倉庫に預け入れ、他の開発者が必要な荷物を取り出す。この物流倉庫は24時間365日稼働しており、年間数千億回もの「出荷(ダウンロード)」を処理している。

PyPIは Python Packaging Authority(PyPA) が管理し、Python Software Foundation(PSF) が支援している。URLは https://pypi.org/ だ。利用は完全に無料で、アカウントを作れば誰でもパッケージを公開できる。

読者がこのセクションで押さえるべきポイントは1つ。**PyPIは「倉庫」であり、pipは「配送トラック」**だということ。PyPIそのものがパッケージをインストールするわけではない。pipやuvといったインストーラ(配送トラック)がPyPIから荷物を取りに行く構造になっている。


2. 数字で見るPyPIの規模 — どれくらい「巨大」なのか

物流倉庫のサイズ感を掴んでもらおう。2026年3月時点のPyPIの統計データはこうなっている。

指標 数値 物流倉庫で例えると
登録パッケージ数 約736,000以上 倉庫の棚に並ぶ商品の種類数
登録バージョン数 約823万 商品の全モデル・全サイズの合計
登録ユーザー数 100万人以上 倉庫に荷物を預けている出荷元の数
年間ダウンロード数 数千億回 年間の出荷件数

2021年時点ではパッケージ数は約31万だった。わずか5年で2.4倍に膨れ上がっている。特に mcp(Model Context Protocol)関連パッケージが13,420件、ai 関連が3,914件と、AI時代の爆発的な増加が顕著だ。

最もダウンロードされているパッケージは boto3(AWS SDK)で、月間数億回のダウンロードを記録している。NumPyやPandasよりも多い、というのは意外に感じるかもしれないが、CI/CDパイプラインでの自動インストールが大量に発生するためだ。

ここまでで、PyPIが「個人の道具箱」ではなく産業インフラ級の物流システムであることが伝わったはずだ。次は、この倉庫がどうやって生まれたのかを見ていこう。


3. PyPIの歴史 — 「Cheese Shop」と呼ばれた時代

PyPIには知る人ぞ知る別名がある。「Cheese Shop(チーズショップ)」 だ。

これはモンティ・パイソンのコント「Cheese Shop」に由来する。客がチーズを注文するたびに「それは切らしてます」と言われ続けるコントだが、初期のPyPI(2003年頃)はまさにそんな状態だった。パッケージが少なすぎて、欲しいものがなかなか見つからない。

物流倉庫に例えれば、最初は田舎の小さな直売所だったものが、20年以上かけて世界最大級の自動化物流センターに成長したイメージだ。

転機は2018年。旧システムからWarehouseと呼ばれる新しい基盤に完全移行した。これは倉庫をまるごと建て替えたようなもので、検索性能、セキュリティ、API設計が大幅に改善された。

Pythonの古い記事で pypi.python.org というURLを見かけることがあるが、これは旧サイトだ。現在は pypi.org にリダイレクトされる。古い資料を参考にする際は注意しよう。

歴史を踏まえたところで、実際にこの倉庫からどうやって荷物を受け取るのか、具体的な手順に進もう。


4. パッケージのインストール — 「配送トラック」の選び方

PyPI(倉庫)から荷物を受け取るには「配送トラック」が必要だ。2026年現在、主要な選択肢は以下の通り。

インストーラ 役割 PyPIとの関係 特徴
pip Python標準の配送トラック 直接接続 Python同梱、最も基本的
uv Rust製の超高速トラック 直接接続 pip互換で10〜100倍速い
conda Anaconda社の独自配送網 独自倉庫(conda-forge)が主 データサイエンス向け
poetry プロジェクト管理統合型 直接接続 依存関係解決が賢い
pdm PEP準拠のモダンツール 直接接続 pyproject.toml中心

4.1 pip — 最も基本的な配送トラック

# パッケージのインストール(PyPIからダウンロード→展開→配置)
pip install requests

# バージョン指定(倉庫の棚から特定ロットを指定して取り寄せ)
pip install requests==2.31.0

# requirements.txt から一括インストール(注文書を渡して全部持ってきてもらう)
pip install -r requirements.txt

# インストール済みパッケージの一覧
pip list

# パッケージ情報の確認
pip show requests

4.2 uv — 次世代の高速トラック

2024年に登場したRust製パッケージマネージャ。pip互換のコマンド体系でありながら、依存関係の解決とダウンロードが桁違いに速い

# uvのインストール
pip install uv

# pip互換モードでインストール(体感で10〜100倍速い)
uv pip install requests

# プロジェクト管理モード
uv init my-project
cd my-project
uv add requests

uvは物流倉庫への「高速専用レーン」を使うイメージだ。荷物の受け取り口はPyPIと同じだが、並列ダウンロードとキャッシュ戦略が優れているため圧倒的に速い。2026年現在、新規プロジェクトではuvを第一選択にする開発者が増えている。

4.3 pip install の裏側で起きていること

pip install requests を実行したとき、裏では以下のフローが走っている。

ここで重要なのは、PyPI本体はメタデータ(どのバージョンがあるか、依存関係は何か)を返すだけで、実際のファイル配信はCDN(Fastly)が担当しているという点だ。物流倉庫の「在庫管理システム」と「実際の配送」が分離されている。

pip install はデフォルトでPyPIに接続するが、社内ネットワークやCI環境では独自のPyPIミラーやプライベートインデックスを使うことがある。--index-url オプションで接続先を変更可能だ。

配送の仕組みがわかったところで、次はこの倉庫に潜むリスクの話に踏み込む。ここからが本記事の核心だ。


5. サプライチェーン攻撃 — 倉庫に「毒入りの荷物」が紛れ込む

PyPIは誰でも荷物を預けられるオープンな倉庫だ。これは強みであると同時に、致命的な弱点でもある。

2025年〜2026年にかけて、PyPIを標的としたサプライチェーン攻撃が激化した。以下はその実例だ。

5.1 主要な攻撃手法

攻撃手法 概要 物流倉庫で例えると
タイポスクワッティング 人気パッケージに似た名前で悪意あるパッケージを登録 有名ブランドの偽物を紛れ込ませる
依存関係汚染 正規パッケージの依存先に悪意あるコードを仕込む 部品の仕入れ先を汚染する
アカウント乗っ取り メンテナのアカウントを奪い、正規パッケージに悪意ある更新を配信 正規出荷元のIDを盗んで偽物を送る
ビルドパイプライン攻撃 GitHub Actionsなどのビルド環境を侵害 工場の製造ラインに細工する

5.2 実際に起きた事件

2024年12月: Ultralytics(YOLOの開発元)パッケージ侵害事件

UltralyticsのPyPIパッケージが攻撃を受け、暗号通貨マイナーを含む悪意あるバージョンが配信された。攻撃者はGitHub Actionsのキャッシュを汚染してビルドパイプライン経由でマルウェアを注入した。Trusted Publishingの透明性ログのおかげで攻撃の経路を特定できたが、対策前に被害が拡大した。

2025年8月: termncolor / colorinal マルチステージ攻撃

termncolorという名前のパッケージが、依存先のcolorinalを通じてDLLサイドローディングによるリモートコード実行を仕掛けた。正規パッケージtermcolorのタイポスクワッティングだ。

2025年8月: SilentSync RAT 配布

sisawssecmeasureという2つの悪意あるパッケージが、Windowsシステムを標的にリモートアクセストロイの木馬(RAT)を配布。ブラウザの認証情報、Cookie、履歴を窃取する機能を持っていた。

5.3 身を守るための実践的対策

# === PyPI パッケージ安全性チェックスクリプト ===
# 使い方: python pypi_safety_check.py <パッケージ名>

import sys
import json
import urllib.request
from datetime import datetime, timezone


def check_package_safety(package_name: str) -> dict:
    """PyPI APIを使ってパッケージの基本的な安全性指標を確認する"""
    url = f"https://pypi.org/pypi/{package_name}/json"
    
    try:
        with urllib.request.urlopen(url) as response:
            data = json.loads(response.read().decode())
    except urllib.error.HTTPError as e:
        if e.code == 404:
            return {"error": f"パッケージ '{package_name}' が見つかりません"}
        raise
    
    info = data["info"]
    releases = data["releases"]
    
    # 最初のリリース日を取得
    all_dates = []
    for version_files in releases.values():
        for f in version_files:
            if f.get("upload_time_iso_8601"):
                all_dates.append(f["upload_time_iso_8601"])
    
    all_dates.sort()
    first_release = all_dates[0] if all_dates else "不明"
    latest_release = all_dates[-1] if all_dates else "不明"
    
    # 安全性の指標を収集
    result = {
        "パッケージ名": info["name"],
        "最新バージョン": info["version"],
        "作者": info.get("author", "不明"),
        "ホームページ": info.get("home_page") or info.get("project_url", "なし"),
        "リリース数": len(releases),
        "初回リリース": first_release[:10],
        "最新リリース": latest_release[:10],
        "ライセンス": info.get("license", "不明"),
        "Python要件": info.get("requires_python", "指定なし"),
    }
    
    # 警告フラグ
    warnings = []
    
    if len(releases) <= 1:
        warnings.append("⚠ リリースが1回のみ(作りたてのパッケージ)")
    
    if not info.get("home_page") and not info.get("project_urls"):
        warnings.append("⚠ ホームページやリポジトリのリンクがない")
    
    if not info.get("license"):
        warnings.append("⚠ ライセンスが未指定")
    
    if info.get("author") in ("", None, "UNKNOWN"):
        warnings.append("⚠ 作者情報が不明")
    
    result["警告"] = warnings if warnings else ["✅ 基本的な指標に問題なし"]
    
    return result


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("使い方: python pypi_safety_check.py <パッケージ名>")
        sys.exit(1)
    
    result = check_package_safety(sys.argv[1])
    
    if "error" in result:
        print(result["error"])
        sys.exit(1)
    
    print("=" * 50)
    print(f"  PyPI パッケージ安全性チェック結果")
    print("=" * 50)
    for key, value in result.items():
        if key == "警告":
            print(f"\n--- 警告 ---")
            for w in value:
                print(f"  {w}")
        else:
            print(f"  {key}: {value}")

実行例:

$ python pypi_safety_check.py requests
==================================================
  PyPI パッケージ安全性チェック結果
==================================================
  パッケージ名: requests
  最新バージョン: 2.32.3
  作者: Kenneth Reitz
  ホームページ: https://requests.readthedocs.io
  リリース数: 148
  初回リリース: 2011-02-14
  最新リリース: 2024-05-29
  ライセンス: Apache-2.0
  Python要件: >=3.8

--- 警告 ---
  ✅ 基本的な指標に問題なし

このスクリプトは「明らかに怪しいパッケージ」を見分ける補助ツールであり、完璧な安全性検証にはならない。プロダクション環境では pip-auditsafety、Socket.dev などの専用セキュリティツールの導入を強く推奨する。

サプライチェーン攻撃のリスクを理解したところで、次はPyPIが導入した最新のセキュリティ対策を見ていこう。


6. PEP 740とTrusted Publishing — 倉庫の「出荷証明書」制度

PyPIは2024年末に PEP 740(デジタルアテステーション) という仕組みを導入した。これは物流倉庫に例えれば、すべての荷物に「出荷証明書」を添付する制度だ。

6.1 Trusted Publishingとは

従来、PyPIへのパッケージ公開にはAPIトークン(長期有効なパスワードのようなもの)が必要だった。このトークンが漏洩すれば、誰でもパッケージを改ざんできてしまう。

Trusted Publishing は、GitHub ActionsなどのCI環境から一時的な認証情報(OIDC) でPyPIに直接公開する仕組みだ。長期トークンが不要になるため、漏洩リスクが大幅に低減される。

6.2 デジタルアテステーション(PEP 740)

Trusted Publishingの上に乗る形で導入されたのがPEP 740だ。パッケージのファイル(.whlや.tar.gz)に対して暗号署名を行い、「このファイルは確かにこのGitHubリポジトリからビルドされた」ことを証明する。

要素 説明 物流倉庫での例え
Attestation(証明) パッケージに付与される暗号署名 荷物に貼る封印シール
Sigstore 署名基盤(Linux Foundation傘下) 封印シールの発行機関
Trusted Publisher パッケージの出荷元ID 認定出荷元の登録証
Provenance(来歴) 「どのリポジトリのどのコミットから作られたか」の記録 原材料のトレーサビリティ

2024年10月以降、公式のGitHub Actions公開ワークフロー(pypa/gh-action-pypi-publish@v1.11.0以降)を使っているプロジェクトは、デフォルトでアテステーションが自動生成される。追加の設定は不要だ。

この仕組みの導入状況は Are we PEP 740 yet? というサイトでトラッキングされている。2026年3月時点で、上位360パッケージの中でアテステーション対応済みのプロジェクトは着実に増加中だ。

セキュリティの話を終えたところで、今度は「自分がパッケージを公開する側」に立ってみよう。


7. パッケージを公開する — 倉庫に「荷物を預ける」手順

自作ライブラリをPyPIに公開する流れを、最小限のステップで解説する。

7.1 プロジェクト構成

my-awesome-lib/
├── src/
│   └── my_awesome_lib/
│       ├── __init__.py
│       └── core.py
├── tests/
│   └── test_core.py
├── pyproject.toml      # ← プロジェクト設定の中心
├── README.md
└── LICENSE

7.2 pyproject.toml の記述

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-awesome-lib"
version = "0.1.0"
description = "An awesome library that does awesome things"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.10"
authors = [
    {name = "Your Name", email = "you@example.com"},
]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]
dependencies = [
    "requests>=2.28.0",
]

[project.urls]
Homepage = "https://github.com/yourname/my-awesome-lib"
Repository = "https://github.com/yourname/my-awesome-lib"
Issues = "https://github.com/yourname/my-awesome-lib/issues"

7.3 ビルドとアップロード

# 1. ビルドツールのインストール
pip install build twine

# 2. ディストリビューションの作成(sdist + wheel)
python -m build

# 3. TestPyPI で試験公開(本番前のリハーサル)
twine upload --repository testpypi dist/*

# 4. TestPyPI からインストールして動作確認
pip install --index-url https://test.pypi.org/simple/ my-awesome-lib

# 5. 本番PyPIにアップロード
twine upload dist/*

必ずTestPyPIで動作確認してから本番に公開すること。 一度公開したバージョン番号は、削除しても二度と再利用できない。物流倉庫の伝票番号が一度使われたら永久欠番になるようなものだ。

7.4 Trusted Publishingの設定(推奨)

前述のとおり、Trusted Publishingを使えばAPIトークン不要で安全に公開できる。GitHub Actionsのワークフロー例を示す。

# .github/workflows/publish.yml
name: Publish to PyPI

on:
  release:
    types: [published]

permissions:
  id-token: write  # OIDC認証に必要

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Install build tools
        run: pip install build
      
      - name: Build package
        run: python -m build
      
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        # APIトークン不要! Trusted Publishingで自動認証
        # v1.11.0以降はデジタルアテステーションも自動生成される

事前にPyPIの管理画面で「このGitHubリポジトリ+このワークフローからの公開を許可する」と登録しておけば、これだけで安全な自動公開が完成する。

パッケージ公開の手順を押さえたところで、次は日常的に役立つトラブルシューティングを見ていこう。


8. よくあるエラーと対処法

エラー/症状 原因 対処法
ERROR: Could not find a version that satisfies the requirement パッケージ名のtypo、Python/OSバージョン非対応 pip search は廃止済。PyPIのWebサイトで正確な名前を確認する
ERROR: No matching distribution found wheelが存在しない環境(古いPython、特殊なOS) --no-binary :all: でソースビルドを試す。ビルドツール(gcc等)が必要な場合あり
pip install が異常に遅い ネットワーク問題、依存関係解決の複雑さ uvへの移行を検討。--progress-bar off で表示負荷を軽減
Permission denied でインストール失敗 システムPythonへの書き込み権限なし --user フラグか仮想環境(python -m venv)を使う。sudo pip install は絶対にやめろ
Hash mismatch ダウンロード中の破損、ミラーの不整合 pip cache purge でキャッシュクリア後に再試行
externally-managed-environment PEP 668準拠のOS(Ubuntu 23.04+等)でシステムPythonに直接インストール 仮想環境を使うか --break-system-packages フラグ(非推奨)を付ける
twineでアップロード時に403 Forbidden APIトークンの権限不足、プロジェクト名が既に使用済み PyPI管理画面でトークンのスコープを確認。プロジェクト名は先着順

sudo pip install環境破壊の入口だ。システムPythonのパッケージを上書きしてOSの挙動がおかしくなった...なんて話は山ほどある。必ず仮想環境を使おう。...orz

エラー対処を押さえたところで、最後にPyPIの進化の方向性と、関連ツールの全体像を整理しよう。


9. PyPIエコシステムの全体像 — 倉庫を取り巻く「物流ネットワーク」

PyPIは単独で存在しているわけではない。周囲にはさまざまな「物流関連施設」が連携している。

9.1 PyPIと他のパッケージインデックスの比較

インデックス 言語 パッケージ数 特徴
PyPI Python ~736,000 オープン、誰でも公開可能
npm JavaScript ~2,500,000 最大規模、スコープパッケージあり
crates.io Rust ~160,000 ビルド前の検証が厳格
RubyGems Ruby ~180,000 歴史が長い
conda-forge 多言語 ~25,000 バイナリ配布、科学計算に強い

10. 学習ロードマップ — 次に何を学ぶべきか

PyPIの全体像を把握した読者が、次に進むべき道を段階別に示す。

レベル1: パッケージ利用者として(初心者)

学習項目 内容
仮想環境の使い方 python -m venv / uv venv で環境を分離する
requirements.txt の管理 pip freeze > requirements.txt で依存を固定する
Dockerとの組み合わせ 再現可能な環境をコンテナ化する

レベル2: パッケージ公開者として(中級者)

学習項目 内容
pyproject.toml の詳細設定 メタデータ、オプション依存、エントリポイント
Trusted Publishing GitHub Actions連携で安全な自動公開
セマンティックバージョニング MAJOR.MINOR.PATCH の意味と運用

レベル3: エコシステム貢献者として(上級者)

学習項目 内容
PEP 740 / Sigstore デジタルアテステーションの仕組みと検証
プライベートPyPIの構築 devpi / Artifactory で社内リポジトリを運用する
パッケージセキュリティ監査 pip-audit / Safety / Socket.dev を活用した脆弱性スキャン

まとめ

PyPIは、Pythonエコシステムの心臓部を担う巨大物流倉庫だ。73万以上のパッケージを抱え、年間数千億回のダウンロードをさばくこのインフラがなければ、現代のPython開発は成り立たない。

一方で、オープンな仕組みゆえにサプライチェーン攻撃のリスクは常に存在する。2025年には協調的かつ高度な攻撃が相次いだ。PEP 740やTrusted Publishingはその対策として大きな前進だが、開発者一人ひとりが「pip install を打つ前に一呼吸置く」意識を持つことが何より重要だと筆者は感じている。

pip install の裏側で動いている仕組みを知っているかどうかで、トラブルシューティングの速度もセキュリティ意識もまるで変わる。この記事がその第一歩になれば幸いだ。


参考文献


筆者X(Twitter): @geneLab_999

最新のAI/セキュリティ/インフラ記事を発信中。フォローお待ちしています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?