PyPIは、Pythonで作成したパッケージを公開し、pip install パッケージを使用して簡単にダウンロードできるようにするツールです。簡潔に言えば、「AndroidのPlayストア」や「iPhoneのAppストア」のようなものです。
この記事では、 「Pythonで作ったパッケージをPyPIに登録するまでの手順」 を簡単に解説します。
具体的な手順は以下の通りです。まず、Pythonプロジェクトを用意した後、設定ファイルとしてpyproject.tomlを作成します。その後、「build」パッケージを使用して、設定どおりに.pyproject.tomlから.whlファイルと.tar.gzアーカイブを生成します。
生成された.whlファイルと.tar.gzアーカイブを、「twine」パッケージを利用して「TestPyPI」にアップロードし、動作を確認した後、本番環境である「PyPI」にアップロードする流れとなります。
環境はWindowsですが、同じ手順でMacやLinuxでも登録ができるはずです。
PyPIとは?
PyPIとは、「Python Package Index」 の略で、プログラミング言語Python用のソフトウェア・リポジトリ[1]のことです。
ターミナル上でPowershellなどを使用して、単に「pip install パッケージ」と入力するだけで、対応するパッケージを簡単にインストールできます。
例えば、marktreeを試しにインストールしてみましょう。marktreeは「pyperclip」というパッケージを特定の機能の実現に使用しているため、一緒にインストールされることがわかります。
(.venv) PS C:\Users\user\テスト> pip install marktree
Collecting marktree
  Using cached marktree-1.1.0-py3-none-any.whl.metadata (5.7 kB)
Collecting pyperclip>=1.8.2 (from marktree)
  Using cached pyperclip-1.8.2-py3-none-any.whl
Using cached marktree-1.1.0-py3-none-any.whl (6.1 kB)
Installing collected packages: pyperclip, marktree
Successfully installed marktree-1.1.0 pyperclip-1.8.2上記の手順では、Pythonにデフォルトで搭載されている仮想環境作成ツールであるvenvを使用してインストールしています。ただし、グローバル環境にインストールする場合は、管理者権限を持つターミナルでインストールしてください。
PyPIに登録する手順
【前準備】フォルダ構造の確認
GitHubでリポジトリを作成し、git cloneでコピーした場合、以下のようなフォルダ構造になると思います。
.
├── .gitignore
├── LICENSE
└── README.md
0 directories, 3 files
1.venv で仮想環境 の作成
作業を始める前に、まず仮想環境を作成します。
仮想環境は、パッケージをグローバル環境にインストールするのではなく、プロジェクトごとに特定のフォルダ以下に保存することで、グローバル環境に影響を与えずに作業できるようにするツールです。
Pythonには標準で搭載されている「venv」という仮想環境作成ツールが含まれていますので、これを使用します。以下のコマンドを実行することで、現在のディレクトリ以下に「.venv」というフォルダが作成されます。
「python -m venv .venv」とすれば、現在ディレクトリ以下に 「.venv」 というフォルダが作成されます。
PS C:\Users\ユーザー名\プロジェクト> python -m venv .venv現在、以下のようなフォルダ構造になっています。
.
├── .gitignore
├── .venv/
├── LICENSE
└── README.md
1 directory, 3 files
この状態で、Windowsの場合は「.\.venv\Scripts\activate」、Linux・Macの場合は「source .venv/bin/activate」とコマンドを実行すると、仮想環境に入ります。
仮想環境に入ると、プロンプトの左端に「(.venv)」と表示されます。
PS C:\Users\ユーザー名\プロジェクト> .\.venv\Scripts\activate
(.venv) PS C:\Users\ユーザー名\プロジェクト> 
「pip list」と実行すると、pip以外には何もインストールされていないことが確認できます。
(.venv) PS C:\Users\user\github\clipcount> pip list
Package Version
------- -------
pip     24.0
これにより、プロジェクトは独立した状態になりました。残りの手順では、プロジェクトに必要なツールをインストールしていきましょう。
2.Pythonプロジェクトの用意
ここにPythonプロジェクトを追加します。プロジェクトフォルダには通常、_init_.pyなどが格納されますが、これに関する詳細な説明は省略します。
.
├── .gitignore
├── .venv/
├── LICENSE
├── README.md
└── プロジェクト/
    └── __init__.py
2 directories, 3 files
上記のようなフォルダ構造ができたら、次のステップに進みます。
3.pyproject.toml を作成する
次に、PyPIにアップロードするための設定ファイルである「pyproject.toml」を作成します。
.
├── .gitignore
├── .venv/
├── LICENSE
├── README.md
├── pyproject.toml
└── プロジェクト/
2 directories, 4 files
ここに記述した内容がパッケージやPyPIに反映されるため、非常に重要なファイルです。
「pyproject.toml」は、「build-system」「project」「tool」の3つのセクションから構成されています。
[build-system]
どのビルドバックエンドを使うかを書き込む
[project]
依存関係や作者の名前など、そのプロジェクトの基本的なメタデータを指定するために書き込む
[tool]
ツールに特化したサブテーブル。省略してもいいこの解説は、下記のサイトを参考にしています。
[build-system]
[build-system] テーブルは、使用するビルドバックエンドを指定します。また、プロジェクトをビルドするために必要な依存関係を requires で指定します。
requires は、例えば requires = ["setuptools >= 61.0"] のようにしてバージョンを制限することもできます。
一般的に使用されるビルドバックエンドには、「hatching」「setuptools」「Flit」「PDM」などがあります。いずれか一つを選んで、[build-system] テーブルに記述してください。
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"[build-system]
requires = ["flit_core >= 3.4"]
build-backend = "flit_core.buildapi"[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"このテーブルは絶対に必要なので、迷った場合は「setuptools」を選んでください。
[project]
残りのほぼ全ての情報は、「project」テーブルに書き込みます。以下の内容をコピーして、必要に応じて値を設定してください。
[project]
name = "プロジェクトの名前"
version = "バージョン"
dependencies = [
    "依存しているパッケージ"
]
requires-python = "Pythonのバージョン要件"
authors = [
    {name="作成者の名前"},
    {email="作成者のメールアドレス"}
]
description = "プロジェクトの説明"
readme = "PyPIに掲載するREADMEファイル"
license = {file = "ライセンスファイル"}
keywords = [
    "キーワードの指定"
]
classifiers = [
  "Development Status :: 開発状況",
  "Intended Audience :: 誰向けのパッケージなのか",
  "Topic :: どんなテーマのパッケージか",
  "License :: OSI Approved :: ライセンス",
  "Programming Language :: 使用プログラミング言語"
]classifiersはPyPIで使用される分類子を指定できます。分類子についての詳細は、PyPIのドキュメントをご参照ください。

もしclassifiers一覧を素早く見たい方は、以下のページが便利です。
また、projectにはいくつかのサブテーブルが存在し、「project.urls」テーブルはPyPIの左欄にURLを追加するためのものです。
[project.urls]
Homepage = "ホームページを指定"
Repository = "GitHubのリポジトリを指定"
「project.scripts」テーブルでは、ファイルを実行できるコマンドを登録できます。例えば、「コマンド = “プロジェクト:関数名”」とすることで、ターミナル上でそのコマンドを実行すると、指定した関数が実行されるようになります。
通常は、「コマンド = “プロジェクト名:main”」として、main関数が実行されるようにします。コマンド名とプロジェクト名を一致させることが一般的です。
[project.scripts]
コマンド = "プロジェクト名:main"「project.optional-dependencies」は、開発用の依存パッケージを指定するテーブルです。例えば、「pip install パッケージ[dev]」とすれば、ここに記述したパッケージも一緒にインストールできるようになります。
[project.optional-dependencies]
dev = ["開発で使うパッケージ"][tool]
「tool」テーブルは、例えば [tool.hatch] や [tool.black]、[tool.mypy] のような、特定のツールに特化したサブテーブルを持っています[2]。
正直なところ、私もそれらのツールを使用していないため、具体的な内容はよく分かりません。また、「pyproject.toml」の作成においては、Python Packaging User Guideでも割愛されていることから、必須ではないと思われます。必要最小限の内容で進めても良いでしょう。
【参考例】marktreeの場合
以下は、参考として自分が作成した「marktree」の pyproject.toml の内容です。
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "marktree"
version = "1.1.0"
dependencies = [
    "pyperclip >= 1.8.2"
]
requires-python = ">=3.8"
authors = [
    {name="yusu79"},
    {email="oss@yusu79.com"}
]
description = "convert headings in a Markdown file (.md) into a tree-like structure and output."
readme = "README_en.md"
license = {file = "LICENSE"}
keywords = [
    "markdown", 
    "tree", 
    "pyperclip"
]
classifiers = [
  "Development Status :: 5 - Production/Stable",
  "Intended Audience :: Other Audience",
  "Topic :: Terminals",
  "License :: OSI Approved :: MIT License",
  "Programming Language :: Python"
]
[project.urls]
Homepage = "https://yusu79.com"
Repository = "https://github.com/yusu79/marktree"
[project.scripts]
marktree = "marktree:main"
[project.optional-dependencies]
dev = [
    "build",
    "twine",
]ちなみに、marktreeはMarkdownファイル(.md)を読み込んで「#」の数に応じてtreeを出力するパッケージです。
4..whl と .tar.gz を作成する
pyproject.toml が完成したら、プロジェクトから .whl ファイルと .tar.gz ファイルを生成します。これらをPyPIにアップロードします。
これらを生成するには、「build」 パッケージを使用します。まず最初に、build パッケージをインストールします。
(.venv) PS C:\Users\ユーザー名\プロジェクト> pip install build # 「.whl」 と 「.tar.gz」 を作成するためのツールあとは、「pyproject.toml」があるフォルダで 「python -m build」 を実行するだけで、.whl と.tar.gzが生成されます。
(.venv) PS C:\Users\ユーザー名\プロジェクト> python -m build
* Creating venv isolated environment...
* Installing packages in isolated environment... (setuptools)
* Getting build dependencies for sdist...
(中略)
Successfully built プロジェクト-バージョン.tar.gz and プロジェクト-バージョン-py3-none-any.whl生成されたファイルは、dist フォルダ以下に「プロジェクト-バージョン-py3-none-any.whl」と「プロジェクト-バージョン.tar.gz」が作成されます。また、「プロジェクト.egg-info」フォルダも作成されています。
.
├── .gitignore
├── .venv/
├── LICENSE
├── README.md
├── プロジェクト.egg-info/
├── dist/
│   ├── プロジェクト-バージョン-py3-none-any.whl
│   └── プロジェクト-バージョン.tar.gz
├── pyproject.toml
└── プロジェクト/
4 directories, 4 files
次のステップでは、生成された「プロジェクト-バージョン-py3-none-any.whl」と「プロジェクト-バージョン.tar.gz」をPyPIにアップロードします。
今は、wheelよりもbuild!?
以前はこの作業を 「wheel」 パッケージで行っていました。
wheelをインストールすると、「python setup.py sdist」で.tar.gzを、「python setup.py bdist_wheel」で .whl を生成できるようになります。
pip install wheel # wheelのインストール
python setup.py sdist # .tar.gzの生成
python setup.py bdist_wheel # .whlの生成しかし、現在wheelは非推奨となっています。その理由は、python setup.py および setup.py をコマンドラインツールとして使うことは非推奨[3]となったからです。つまり、以下のコマンドは全て非推奨となりました。
- python setup.py install
- python setup.py develop
- python setup.py sdist
- python setup.py bdist_wheel
よって、wheelの代わりに公式で推奨されている方法として、「python -m build」を使用することがお勧めされています。
詳細は下記を御覧ください。
5.twineでアップロード
ここまでできたら、後はアップロードするだけです。アップロードには、「twine」を使用します。
(.venv) PS C:\Users\ユーザー名\プロジェクト> pip install twine # PyPIへのアップロードに使用するツール後は、「twine upload --repository testpypi dist/*  」とすればTestPyPIに、そして「twine upload --repository pypi dist/*  」と入力すれば、PyPIに .whl と .tar.gz がアップロードされます。
ただし、それぞれのWEBサイトでアカウントを作成する必要があります。また、それぞれのアカウント先でAPIキーを作成し、その鍵IDを「C:\Users\ユーザー名\.pypirc」というファイルに記述しておく必要があります。
TestPyPIにアップロード
まずは、下記のサイトにアクセスし、アカウントを作成しましょう。右上の「登録」をクリックしてください。

アカウントが作成できたら、APIトークンを作成します。まず「アカウント設定」をクリックします。

次に、画面下部にある「APIトークンの追加」をクリックします。

APIトークンの名前とスコープを設定して、「Create Token」をクリックします。

生成されたAPIトークンは一度だけ表示されますので、コピーしてください。

「C:\Users\ユーザー名」以下に 「.pypirc」 ファイルを作成します。以下の内容をコピーして貼り付けます。
[distutils]
index-servers =
  testpypi
[testpypi]
repository = https://test.pypi.org/legacy/
username = ここにコピーしたAPIトークンを貼り付ける!上記で、[testpypi] の username にはAPIトークンを貼り付けます。このAPIトークンがパスワードの代わりとなります。
そして、「twine upload --repository testpypi dist/*」 を実行することで、TestPyPIに dist フォルダ以下の「プロジェクト-バージョン-py3-none-any.whl」と「プロジェクト-バージョン.tar.gz」がアップロードされます。
(.venv) PS C:\Users\ユーザー名\プロジェクト> twine upload --repository testpypi dist/*TestPyPIにアップロードしたパッケージのページ(https://test.pypi.org/project/プロジェクト/バージョン/)にアクセスすると、黄色で囲まれた箇所にインストールコマンドが表示されます。これをクリックして貼り付けることで、インストールが可能です。

実際にインストールして動作確認ができたら、本番環境の「PyPI」にアップロードします。
TestPyPIの注意点
実はTestPyPIにはひとつ欠点があります。それは依存関係を正しく処理できないことです。
pyproject.tomlの「project」テーブルの「dependencies」に記載したパッケージを正しくインストールできるとは限りません。そのため、TestPyPIにアップロードしたパッケージをインストールすると、エラーになることがあります。
しかし、これはTestPyPIが依存関係を正しく処理できないために起こる問題であり、本番環境のPyPIにアップロードすると、通常はうまく処理されます。
PyPIにアップロード
PyPIも同様に、まずはアカウントを作成します。下記のサイトにアクセスして、右上の「登録」からアカウントを作成してください。

その後、同様の流れでAPIトークンを作成します。APIトークンを生成したら、コピーしておきます。
[distutils]
index-servers =
  testpypi
  pypi
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = TestPyPIのAPIトークン
[pypi]
repository = https://upload.pypi.org/legacy/
username = __token__
password = ここにコピーしたAPIトークンを貼り付ける!
最後に、「twine upload –repository pypi dist/*」を実行すれば、PyPIに dist フォルダ以下の「プロジェクト-バージョン-py3-none-any.whl」と「プロジェクト-バージョン.tar.gz」がアップロードされます。
(.venv) PS C:\Users\ユーザー名\プロジェクト> twine upload --repository pypi dist/*6.PyPI Stats で確認
アップロードは完了しましたが、PyPIでは「どれくらいインストールされているのか」や「インストールの推移」などの情報は確認できません。
そういった情報を確認したい場合は、下記のサイトにアクセスしてください。「Package:」という欄に自身のパッケージ名を入力すると、関連情報が表示されます。
ただし、アップロードしたばかりだと反映までに時間がかかることがあるので、その点は注意してください。
まとめ
この記事では、「Pythonで作ったパッケージをPyPIに登録するまでの手順」を簡単に解説しました。
手順は以下の通りです:
- まずは「venv」を使用して仮想環境を作成し、その中に入ります。
- 次に、プロジェクトを完成させて、pyproject.tomlに必要な設定を書き込みます。
- 書き込みが完了したら、「build」を使用して、dist/以下に「プロジェクト-バージョン-py3-none-any.whl」と「プロジェクト-バージョン.tar.gz」を作成します。
- 最後に、「twine」を使用して、「プロジェクト-バージョン-py3-none-any.whl」と「プロジェクト-バージョン.tar.gz」をPyPIにアップロードして手順を完了します。
Pythonの環境や手順が頻繁に変わるため、最新の情報を確認することが重要です。この記事では、直近の経験を元にPyPIにアップロードする手順をまとめています。
