Pythonパッケージのビルドについて学ぶ#

Pythonパッケージの作成、ビルド、そしてPyPIとconda-forgeへの公開の流れを示す画像です。あなたのコードをPyPIが受け付ける配布ファイル(sdistとwheel)に変換します。そして、両方のディストリビューションを公開しているPyPIリポジトリへの矢印があります。PyPIからconda-forgeのレシピを作成し、conda-forgeに公開することができます。

両方のパッケージ配布(ソース配布とホイール)をPyPIに公開したら、次にconda-forgeに公開します。conda-forgeでパッケージをビルドするには、PyPIでのソース配布が必要です。conda-forgeに公開するためにパッケージをリビルドする必要はありません。#

PythonパッケージをPyPI(またはcondaチャンネル)に公開するには、ビルドする必要があります。ビルドプロセスは、あなたのコードとメタデータをPyPIにアップロードできる配布フォーマットに整理し、その後ユーザーがダウンロードしてインストールできるようにします。 注意: conda-forgeが適切にパッケージを自動ビルドするためには、sdistをPyPIに公開する必要があります。

Pythonパッケージのビルドとは何ですか?#

Pythonパッケージを公開 し、誰でも簡単にインストールできるようにするには、まずそれをビルドする必要があります。

しかし、Pythonのパッケージを作るとは何ですか?

上の図のように, Pythonパッケージをビルドするとき、ソースファイルをディストリビューションパッケージと呼ばれるものに変換します。 配布パッケージには、あなたのソースコードとパッケージのメタデータが、 Python Package Index が要求する形式で含まれています。

注釈

Pythonや他の言語では、パッケージという言葉は様々な意味で使われています。 このページでは、 Python Packaging Authority の慣例に合わせ、ビルドステップの成果物を 配布パッケージ と呼ぶことにします。

あなたのコード、ドキュメント、テスト、メタデータをpipとPyPIの両方が使える形式に整理し、フォーマットするこのプロセスは、ビルドステップと呼ばれます。

プロジェクトのメタデータとPyPI#

ビルドツールとPyPIの両方があなたのパッケージを説明し理解するために使うメタデータは、一般的に pyproject.tomlファイル に格納されます。 このメタデータはいくつかの目的で使用されます:

  1. パッケージのビルドに使用するツール(pip、 pypa's Build 、またはpoetry、PDM、Hatchのようなエンドツーエンドツール)があなたのパッケージのビルド方法を理解するのに役立ちます。ビルドツールに提供される情報には以下のものがあります:

  • pyproject.tomlファイルの [build-system] テーブルは、sdistとwheelディストリビューションの作成に使用したい ビルドバックエンドツール をpipに伝えます。

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
  • また、プロジェクトテーブルの dependencies セクションは、ビルドツールと PyPI にプロジェクトが必要とする依存関係を伝えます。

dependencies = [
    "numpy",
    "geopandas",
]
  1. ビルドツールがパッケージ配布ファイル(PyPIで公開するファイル)を作成するとき、PyPIが読み込んでユーザがあなたのパッケージを見つけるのに役立つMETADATAファイルも作成します。 例えば:

  • pyproject.tomlファイルの [project] テーブルの classifiers = セクションは、PyPIのユーザが特定のライセンスを含むパッケージや特定のバージョンのpythonをサポートするパッケージをフィルタリングするための情報を提供します。

classifiers = [
    # How mature is this project? Common values are
    "Development Status :: 4 - Beta",

    # Indicate who your project is intended for
    "Intended Audience :: Developers",
    "Topic :: Software Development :: Build Tools",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
]

メタデータのsetup.pyとsetup.cfgはどうなったのですか?

プロジェクトのメタデータは、以前は setup.py ファイルか setup.cfg ファイルに保存されていました。パッケージのメタデータを保存するために現在推奨されている方法は、pyproject.tomlファイルを使用することです。 pyproject.tomlファイルについての詳細はこちらです。

例 - xclim#

PyPIに公開すると、各パッケージにメタデータがリストされていることに気づくでしょう。pyOpenSciパッケージ の一つである xclim を見てみましょう。PyPIのランディングページには、pythonやメンテナ情報など、パッケージに関するメタデータが表示されていることに注目してください。PyPIは、Xclimのメンテナ pyproject.toml file によって正しい構文と分類子を用いて定義されたメタデータを入力することができます。xclimパッケージがビルドされるとき、このメタデータは配布ファイルに変換され、PyPIがメタデータを読み込んでウェブサイトに出力できるようになります。

xclimパッケージのPyPI左サイドバーの画像です。一番上のセクションにはClassifierとあります。その下に、開発状況、対象読者、ライセンス、自然言語、オペレーティングシステム、プログラミング言語、トピックなどの項目があります。 これらの各セクションの下には、さまざまな分類オプションがあります。 " width="300px">

pyproject.tomlにclassifierセクションを追加してパッケージがビルドされると、ビルドツールはメタデータをPyPIが理解できる形式に整理し、PyPIのランディングページに表示します。 これらの分類子により、ユーザはサポートするpythonのバージョンやカテゴリなどでパッケージをソートすることもできます。#

ハイレベルなパッケージングのワークフローを示すグラフィックです。左側には、コード、メタデータ、テストが入ったグラフィックがあります。ドキュメンテーションやデータは、通常、パッケージホイールの配布物には掲載されないため、そのボックスの下にあります。右の矢印は、ビルド配布ファイルのボックスに移動します。このボックスは、TestPyPIか本物のPyPIのどちらかに公開するように導きます。PyPIからconda-forgeに接続し、ディストリビューションをPyPIからconda-forgeに送る自動ビルドを行うことができます。

PythonパッケージをPyPI(またはConda)に公開するには、ビルドする必要があります。ビルドプロセスは、あなたのコードとメタデータをPyPIにアップロードできる配布フォーマットに整理し、その後ユーザーがダウンロードしてインストールできるようにします。 注意: conda-forgeが適切にパッケージを自動ビルドするためには、sdistをPyPIに公開する必要があります。#

このスクリーンショットは、PyPI上のxclimパッケージのメタデータを示しています。そこには、ライセンスの名前、そのパッケージに関連する作者とメンテナの名前のキーワード、そしてそのパッケージが必要とするpythonの基本バージョン3.8が表示されています。

xclimパッケージのメタデータを示すPyPIのスクリーンショット。#


ここでは、PyPIに表示されているメインテナメタデータを見ることができます。xclimの場合、3人のメンテナがリストアップされており、右側にプロフィール写真とgithubユーザー名が表示されています。

PyPI に表示されている xclim パッケージのメンテナ名と GitHub ユーザ名。 この情報はpyproject.tomlに記録され、ビルドツールで処理され、パッケージのsdistとwheelディストリビューションに保存されます。#

PyPIやPipが期待する配布フォーマットを作るには?#

理論的には、PyPIが望むようにコードを整理する独自のスクリプトを作成することができます。 しかし、データフレーム用のPandasや配列用のNumpyのような既知の構造を扱うパッケージがあるように、パッケージのビルド配布ファイルの作成を支援するパッケージやツールがあります。

注釈

パッケージングツールには、パッケージングプロセス全体を支援するものもあれば、プロセスの1ステップだけを支援するものもあります。 例えばsetuptoolsはよく使われるビルドバックエンドで、sdistやwheelの作成に使うことができます。 一方、Hatch、PDM、Poetry、flitのようなツールは、パッケージングプロセスの他の部分を支援します。

これは、パッケージングエコシステムに混乱と複雑さをもたらす可能性がありますが、ほとんどの場合、各ツールは同じ配布出力を提供します(ほとんどのユーザーが気にしないような小さな違いはあります)。 これらのツールの詳細については、このページを参照してください。

以下では、PyPIが公開することを期待している2つの配布ファイル、sdistとwheelについて学びます。 sdistとwheelです。それぞれの構造と、どのようなファイルがあるのかについて学びます。

PythonパッケージをPyPIに公開するために作成する必要がある配布ファイルは、ソース配布(sdist)とwheelの2つです。 sdistにはパッケージの生のソースコードが含まれています。 wheel(.whl)には、ビルド/コンパイルされたファイルが含まれており、誰のコンピュータにも直接インストールすることができます。

両ディストリビューションの詳細は以下の通りです。

注釈

もし、あなたのパッケージが追加のビルド/コンパイルステップのない純粋なpythonパッケージであれば、sdistとwheelのディストリビューションは似たような内容になるでしょう。 しかし、もしあなたのパッケージが他の言語での拡張機能を持っていたり、ビルドがより複雑であったりする場合、2つのディストリビューションは大きく異なるでしょう。

また、このセクションではcondaのビルドワークフローについては触れないことに注意してください。 condaのビルドについてはこちらで詳しく説明しています

ソースディストリビューションとは (sdist)#

ソースファイル は、パッケージをビルドするために必要な、ビルドされていないファイルです。 これらは、GitHubやあなたがコードを管理するために使っているプラットフォームに保存する "raw / as-is" ファイルです。

Source Distributions(S + Dist)はsdistと呼ばれます。その名の通り、SDISTにはソースコードが含まれています; ビルドもコンパイルもされていません。 したがって、ユーザーがpipを使ってソースディストリビューションをインストールする場合、pipは最初にビルドステップを実行する必要があります。 このため、ソース・ディストリビューションを、(プロジェクトの依存関係を除いた)ホイールのビルドに必要なすべてを含む圧縮アーカイブとして、ネットワークアクセスなしで定義することができます。

Sdist は通常、 .tar.gz アーカイブ (しばしば "tarball" と呼ばれます) として保存されます。 そのため、ユーザーが pip を使用してソースディストリビューションをインストールする場合、pip は最初にビルドステップを実行する必要があります。

以下はstravalib Pythonパッケージのsdistの例です:

stravalib-1.1.0.post2-SDist.tar.gz file contents

├─ 📂 stravalib
│  ├─ tests
│  │  ├─ integration
│  │  │  ├─ __init__.py
│  │  │  ├─ conftest.py
│  │  │  ├─ strava_api_stub.py
│  │  │  └─ test_client.py
│  │  ├─ unit
│  │  │  ├─ __init__.py
│  │  │  ├─ test_attributes.py
│  │  │  ├─ ...
│  │  ├─ __init__.py
│  │  ├─ auth_responder.py
│  │  └─ test.ini-example
│  ├─ util
│  │  ├─ __init__.py
│  │  └─ limiter.py
│  ├─ __init__.py
│  ├─ _version.py
│  ├─ _version_generated.py
│  ├─ attributes.py
│  ├─ ...
├─ stravalib.egg-info
│  ├─ PKG-INFO
│  ├─ SOURCES.txt
│  ├─ dependency_links.txt
│  ├─ requires.txt
│  └─ top_level.txt
├─ CODE_OF_CONDUCT.md
├─ CONTRIBUTING.md
├─ LICENSE.txt
├─ MANIFEST.in
├─ Makefile
├─ PKG-INFO
├─ README.md
├─ CHANGELOG.md
├─ environment.yml
├─ pyproject.toml
├─ requirements-build.txt
├─ requirements.txt
└─ setup.cfg

GitHubアーカイブとsdistの比較

GitHub でリリースを作成すると、GitHub リポジトリ内のすべてのファイルを含む git archive が作成されます。これらのファイルはsdistと似ているが、これら2つのアーカイブは同じではない。sdistには、メタデータ・ディレクトリーや、 setuptools_scmhatch_vcs を使う場合はバージョンを保存するファイルなど、他にもいくつかの項目が含まれています。

Python wheel (whl)とは何ですか?#

ホイールファイルはZIP形式のアーカイブで、ファイル名は特定のフォーマット(下記)に従い、拡張子は .whl です。 .whl アーカイブには、プロジェクトのpyproject.tomlファイルから生成されたメタデータを含む特定のファイル群が含まれています。 pyproject.tomlや、ソース配布物に含まれる可能性のあるその他のファイルは、ビルドされた配布物であるため、wheelには含まれません。

wheel(.whl)は、ビルドされたバイナリーディストリビューションです。 バイナリファイル は、ビルド/コンパイルされたソースファイルです。 これらのファイルはすぐにインストールできます。 wheel (.whl) は、パッケージを直接インストールするために必要な すべてのファイルを含む zip ファイルです。 wheelに含まれるファイルはすべてバイナリで、これはコードがすでにコンパイル/ビルドされていることを意味します。 wheelの取り付けがより迅速に - 特にビルド・ステップが必要なパッケージの場合はそうなります。

ホイールには、setup.cfgpyproject.toml のようなパッケージの設定ファイルは含まれていません。 このディストリビューションはすでにビルドされていますので、すぐにインストールできます。

ビルドされているため、純粋な Python プロジェクトでは wheel ファイルのインストールが速くなり、マシン間で一貫したインストールができるようになります。

Tip

ウィールは、パッケージがより複雑なビルドをサポートするために setup.py ファイルを必要とする場合にも便利です。 この場合、wheelバンドルのファイルはあらかじめビルドされているので、インストールするユーザはインストール時に悪意のあるコードインジェクションを心配する必要がありません。

wheelのファイル名には、パッケージに関する重要なメタデータが含まれています。

例: stravalib-1.1.0.post2-py3-none.whl

  • 名前: stravalib

  • version: 1.1.0

  • ビルド番号: 2 (post2) (read more about post here)

  • py3: Python 3.x をサポート

  • none: オペレーティングシステムに依存しない (Windows、Mac、Linuxで動作)

  • any: あらゆるコンピュータ・プロセッサ/アーキテクチャで動作

ホイールファイルを解凍 (unzipped) するとどのように見えるか:

stravalib-1.1.0.post2-py3-none.whl file contents:

├─ 📂 stravalib
│  ├─ tests
│  │  ├─ functional
│  │  │  ├─ __init__.py
│  │  │  ├─ test_client.py
│  │  ├─ unit
│  │  │  ├─ __init__.py
│  │  │  ├─ test_attributes.py
│  │  ├─ __init__.py
│  │  ├─ auth_responder.py
│  │  └─ test.ini-example
│  ├─ util
│  │  ├─ __init__.py
│  │  └─ limiter.py
│  ├─ __init__.py
│  ├─ _version.py
│  ├─ _version_generated.py
│  ├─ attributes.py
│  ├─ client.py
└─ stravalib-1.1.0.post2.dist-info  # Package metadata are stored here
   ├─ LICENSE.txt
   ├─ METADATA
   ├─ RECORD
   ├─ WHEEL
   └─ top_level.txt