バイナリ配布物のフォーマット¶
このページでは、Python パッケージのバイナリ配布物フォーマット、これは wheel フォーマットとも呼ばれているものですが、これについて仕様を記述します。
wheel は、特別にフォーマットされたファイル名と .whl 拡張子を持った ZIP フォーマットのアーカイブです。 PEP 376 にほぼ従ってインストールされる単一の配布物を含んでいます。特別なインストーラを使うことが推奨されていますが、 wheel ファイルは、いつでも後でその最終的なパス名の場所に内容物を展開するに足りる情報を保存しつつ、サイトパッケージを置くべき場所に標準の 'unxip' ツールで単純にアンパックすればインストールできます。
詳細¶
wheel の 'distribution-1.0-py32-none-any.whl' をインストールする¶
Wheel によるインストールは、概念上、ふたつの段階から構成されています:
アンパックする。
distribution-1.0.dist-info/WHEELをパースします。インストーラが Wheel のバージョンと互換であることを確認します。マイナーバージョンが大きければ警告し、メジャーバージョンが大きければ処理を中断します。
もし、 Root-Is-Purelib == 'true' であれば、アーカイブを purelib (site-packages) へアンパックします。
そうでなければ、アーカイブを platlib (site-packages) へアンパックします。
広げる。
アンパックされたアーカイブは、
distribution-1.0.dist-info/と (データ部分があれば)distribution-1.0.data/を含んでいます。distribution-1.0.data/の下の全てのサブツリーを、その目的地となるディレクトリパスに移動しましょう。distribution-1.0.data/(purelib|platlib|headers|scripts|data)のようなdistribution-1.0.data/の下のサブディレクトリは、それぞれ、目的地となるディレクトリの辞書のキーになっています。このようなサブディレクトリ群は、 sysconfig によって定義されるインストールパス群 です。もし該当するならば、
#!pythonから始まるスクリプト群が適切なインタープリタを指し示すように更新します。distribution-1.0.dist-info/RECORDをインストール先のディレクトリパスに更新します。空の
distribution-1.0.dataディレクトリを削除します。インストールされた .py のファイルを全て .pyc にコンパイルします。 (アンインストーラは、 RECORD で言及されていなくても .pyc ファイルを削除できるほどに賢くあるべきです。)
推奨されるインストーラの機能¶
#!pythonを書き換えます。wheel では、スクリプトは
{distribution}-{version}.data/scripts/にパッケージされます。scripts/ディレクトリにあるファイルの先頭行が正確にb'#!python'で始まっている場合には、正確なインタープリタを指し示すように書き換えられます。アーカイブが Windows で作成されていれば、 Unix でのインストーラがこのようなファイルに +x ビットを追加設定する必要があるでしょう。b'#!pythonw'と書く慣習も許容されています。b'#!pythonw'というのはコンソール版ではなくて GUI 版のスクリプトであることを示します。- スクリプトラッパを生成します。
wheel では、 Unix システム上でパッケージされたスクリプトには .exe ラッパーが随伴していないことが普通でしょう。 Windows インストーラは、インストールする際にそれらを追加したくなるかもしれません。
推奨されるアーカイバの機能¶
- アーカイブの末尾に
.dist-infoを置くこと。 アーカイバには、
.dist-infoファイルをアーカイブの物理的な末尾に置くことが推奨されています。こうすることで、アーカイブ全体を書き換えなくてもメタデータを修正することができる点を含む、 ZIP のいくつかの潜在的に興味深いトリックを使うことができるようになります。
ファイルフォーマット¶
ファイル名の慣習¶
wheel のファイル名は(訳注、慣習として) {配布物}-{バージョン}(-{ビルドタグ})?-{python タグ}-{abi タグ}-{プラットフォームタグ}.whl <{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl> です。
- ディストリビューション
配布物の名前、例えば 'django' や 'pyramid' 。
- version
配布物のバージョン、例えば 1.0 。
- ビルドタグ
省略可能なビルドナンバー。数字で始まらなければなりません。ふたつの wheel ファイルのファイル名がこの部分を除いて (すなわち名称やバージョンやその他のタグが) 同一であった場合のタイブレーカ (訳注、いずれかに決めるための方法) として働きます。指定されていれば先頭の数字を
intとして解釈して第1要素とし残りをstrとして解釈して第2要素とするような要素数が2個のタプルとして解釈され、指定されていない場合には空欄のタプルとして解釈されます。ビルド番号に係るよくあるユースケースは、CPython のリリース前のバージョンを使って manylinux で配布物をビルドしている場合のように、ビルド環境に変化があったためにバイナリ配布物を再ビルドする時です。
警告
ビルド番号は、配布物のバージョンの一部ではなく、従って外部からの、特に Python エコシステムのツール類や標準類の外側からの参照は困難です。配布物が外部からの参照を必要とするようなケースとしては、セキュリティ上の脆弱性を解決しようとする場合がよくあるケースです。
この制約のゆえに、外部から参照される必要がある新たな配布物は、新しい配布物をビルドする際にビルド番号を使う べきではありません 。そのような場合には、代わりに 新しい配布物バージョン が作成されるべきです。
- 言語の実装とバージョンタグ
例えば、 'py27' ・ 'py2' ・ 'py3' 。
- abi タグ
例えば、 'cp33m' ・ 'abi3' ・ 'none' 。
- プラットフォームタグ
例えば、 'linux_x86_64' ・ 'any' 。
例えば、 distribution-1.0.1-py27-none-any.whl とは、 'distribution' と呼ばれるパッケージの最初のビルドであって、 Python 2.7 (任意の Python 2.7 実装) で動作し、 ABI を持たず (即ち純 Python) 、任意の CPU アーキテクチャで動作するということです。
ファイル名の拡張子を除いて最後の3個の構成要素は、"互換性タグ" と呼ばれます。互換性タグは、インタープリタに対するそのパッケージの基本的な要求事項を表現しており、 PEP 425 に詳しく書かれています。
エスケープとユニコード¶
ファイル名の構成要素がダッシュ (- つまり HYPHEN-MINUS) で分離されているので、この文字は構成要素の中に出現してはなりません。これは次のように取り扱われます:
配布物の名前の部分では、
-_.(HYPHEN-MINUS ・ LOW LINE ・ FULL STOP) の文字は、すべて_(LOW LINE) で置き換えられるべきで、かつ、大文字はすべて対応する小文字に置き換えられるべきです。これは、通常の 名前の正規化 の後に-を_で置き換えるのと同じです。 wheel ファイルを入力とするツールは、.(FULL STOP) や大文字を受け入れる準備ができていなければなりませんが、しかし、それはこの仕様の初期のバージョンで許されていたからです。バージョン番号は、 バージョン指定子仕様 に従って正規化されなければなりません。正規化済みのバージョン番号は
-を含んでいてはなりません。残りの構成要素は、
-文字を含んでいてはいけないので、エスケープ処理を行う必要がありません。
wheel ファイルを生成するツールはファイル名部分の構成要素が - を含んでいないことを検証しなければなりませんが、これは、もし含んでいれば結果として生成されたファイルが正しく処理されないかもしれないからです。
アーカイブのファイル名は Unicode です。いくつかのツールでは非 ASCII 文字のファイル名をサポートするように更新されるまでに幾らかの時間がかかるかもしれませんが、しかし、この仕様ではサポートされているのです。
アーカイブの 中にある ファイルのファイル名は、 UTF-8 でエンコードされています。 ZIP クライアントのいくつかは共通して UTF-8 のファイル名を正常に表示しませんが、このエンコーディングは ZIP の仕様でも Python の zipfile の仕様でも、共にサポートされています。
ファイルの内容¶
{distribution} の部分を例えば beaglevote のようなパッケージの normalized name で置き換え、 {version} を例えば 1.0.0 のような normalized version で置き換えた時 (両方のフィールドでダッシュ文字/- をアンダースコア文字/_ で置換) 、 wheel ファイルの内容は、以下の要素から構成されます:
アーカイブのルートディレクトリに当たる
/は、WHEELで指定されている通りpurelibまたはplatlibにインストールされるファイルをすべて含んでいます。通常は、purelibやplatlibはいずれもsite-packagesです。{distribution}-{version}.dist-info/はメタデータを含んでいます。distribution-version.dist-info/licenses/はライセンスファイル群を含んでいます。{distribution}-{version}.data/は、すでにカバーされているものは別として、サブディレクトリ名がインストールパスの辞書への (例えば、data,scripts,headers,purelib,platlibのような) 指示子になっているような、空ではないそれぞれのインストールスキームに対応したディレクトリを含みます。Python スクリプトは
scriptsディレクトリに置かなければならず、また、インストール時のスクリプトラッパの生成や#!python書き換えといった利点を活用するために、正確にb'#!python'で始まっていなければなりません。scriptsディレクトリには、通常ファイルしか置けません。{distribution}-{version}.dist-info/METADATAは、バージョン 1.1 またはそれ以上のフォーマットのメタデータです。{distribution}-{version}.dist-info/WHEELは、同様のキー:バリュー形式で表現されたアーカイブそのものに関するメタデータです:Wheel-Version: 1.0 Generator: bdist_wheel 1.0 Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any Build: 1
Wheel-Versionは、Wheel の仕様のバージョン番号です。Generatorは、そのアーカイブを作成したソフトウェアの名前で、バージョン番号を付加しても構いません。Root-Is-Purelibは、アーカイブの最上位のディレクトリが pure lib へインストールされるべきものであれば true で、そうでなければ platlib へインストールされます。Tagは、 wheel の拡張互換性タグで、例の中ではファイル名のpy2.py3-none-anyの部分です。Buildは、ビルドナンバーで、もしビルドナンバーがなければ省略されます。wheel インストーラは、もし自身がサポートしているものより Wheel-Version が大きければ警告するべきですし、 Wheel-Version のメジャーバージョンがサポートしているものより大きい場合にはフェイルするべきです。
Wheel は複数のバージョンの Python を跨いでも動作するように意図されたインストール用のフォーマットですが、通常は .pyc ファイルを含みません。
Wheel は、 setup.py ないし setup.cfg を含みません。
このバージョンの wheel 仕様は、distutils のインストール方法論に基づいていて、ファイルを他の場所にインストールする方法については定義していません。既存の wininst や egg バイナリフォーマットが提供する機能の上位互換のレイアウトを提案します。
.dist-info ディレクトリ¶
Wheel の .dist-info ディレクトリは、最低限でも METADATA ・ WHEEL ・RECORD を含みます。
METADATA はパッケージのメタデータで、 sdists のルートディレクトリにある PKG-INFO と同じフォーマットで記述されます。
WHEEL は、パッケージをビルドする部分に特化した wheel のメタデータです。
RECORD は、 wheel に含まれる (ほとんど) 全てのファイルとそのセキュアなハッシュ値のリストです。 PEP 376 とは異なり、自分自身のハッシュ値を内包することは不可能な RECORD を除く個々のファイルのハッシュ値が含まれていなければなりません。ハッシュ計算のアルゴリズムは sha256 以上でなければならず、特に md5 とsha1 は許されません。
PEP 376 の INSTALLER と REQUESTED はアーカイブに含まれません。
RECORD.jws および RECORD.p7s は非推奨になりました。これらがまだ使われている場所では、 RECORD.jws と RECORD.p7s のいずれも RECORD の中で言及してはなりません。ビルドバックエンドとその他のツール類はもはやこれらを wheel ファイルに追加してはならず、 インストーラはこれらのファイルがまだその一部をなすような wheel ファイルも存在するということを認識しておくべきです。
展開中に wheel インストーラは RECORD 内のハッシュ値と実際のファイルの内容 (のハッシュ値) をすべて検証します。 RECORD ・ RECORD.jws ・ RECORD.p7s による検証とは別に、アーカイブの中のいずれかのファイルが RECORD にリストされていないか、または、ハッシュ値が正しくない時には、インストールがフェイルするでしょう。
.dist-info/ 内のサブディレクトリ群¶
.dist-info/ の下のサブディレクトリは、将来の使用のために予約されています。 .dist-info/ の下のサブディレクトリ名で以下のものが特定の用途のために予約されています:
サブディレクトリ名 |
PEP / Standard |
|---|---|
|
|
|
|
|
|
|
.dist-info/licenses/ ディレクトリ¶
メタデータバージョンが 2.4 かそれ以上で、一つないしそれ以上の License-File フィールドが指定された場合は、 .dist-info/ ディレクトリには licenses/ サブディレクトリがなければならず、そこには METADATA ファイル内の License-File フィールドに licenses/ ディレクトリからの相対パスの形で列挙されたファイル群を含んでいなければなりません。
The .dist-info/sboms/ ディレクトリ¶
.dist-info/sboms/ ディレクトリにあるすべてのファイルは、配布物アーカイブ内に含まれるソフトウェアを記述するソフトウェア部品表 <Software Bill-of-Materials> (SBOM) のファイルでなければなりません。
.data ディレクトリ¶
site-packages 内に通常通りにインストールされなかったファイルはどれでも、.dist-info ディレクトリと同様に命名されるが .data/ 拡張子をつけられて .data ディレクトリに行きます:
distribution-1.0.dist-info/
distribution-1.0.data/
.data ディレクトリでは、配布物からのスクリプト・ヘッダー・説明文書などをサブディレクトリに収めています。インストール中に、これらのサブディレクトリの内容を行き先となるパスへ動かします。
FAQ¶
Wheel は .data ディレクトリを定義します。すべてのデータをそこに入れるべきでしょうか?¶
この仕様では、あなたがあなたのソースコードをどのように組織立てて置くべきかについては特に意見を表明していません。 .data ディレクトリは、
site-packages内や PYTHONPATH 内に通常ならインストールされない全てのファイルを置く場所というだけのことです。換言すれば、 そのような ファイル群が通常なら wheel の.dataディレクトリに置く形で配布されない時でさえも、pkgutil.get_data(package, resource)を使い続けても構わないのです。
"pure lib" と "plat lib" って、どう扱えばいいの?¶
Wheel は "purelib" と "platlib" を区別して扱いますが、これらはプラットフォームによっては大きな違いがあります。例えば、 Fedora では純 Python のパッケージを '/usr/lib/pythonX.Y/site-packages' にインストールし、プラットフォームに依存しないパッケージを '/usr/lib64/pythonX.Y/site-packages' にインストールします。
{name}-{version}.data/purelib内の全てのファイルについて "Root-Is-Purelib: false" という設定になっている wheel ファイルは、同じファイルがルートディレクトリ内にあって "Root-Is-Purelib: true" になっている wheel ファイルと相同であり、 "purelib" と "platlib" の両カテゴリにファイル群が存在することには問題がありません。実際のところ、 wheel は、それが純 Python なのかそうでないのかによって "purelib" または "platlib" のいずれか一方しか持たず、 "Root-Is-purelib" を適切に設定しつつファイル群をルートディレクトリに置くべきです。
Python のソースコードを wheel ファイルから直接にインポートすることはできますか?¶
技術的な話としては、単純に展開するだけでインストールできる機能と
zipimportと互換性のあるアーカイブフォーマットの両方をサポートしているので、一部の wheel ファイルはsys.pathに直接に置くことを サポートしています 。しかし、このような動作はフォーマット設計の自然な結果とはいうものの、実際にはこれに依存することは一般的には推奨されていません。第一に、wheel は一義的に配布物のフォーマットとして 設計されている ので、インストールの段階を省略するということは、完全なインストールがなされているものと仮定している機能 (例えば、正当性監査やセキュリティアップデートのために正しく追跡することができるひとつの方法であるところの
pipやvirtualenvのような標準ツールを使った依存関係の捕捉や管理の機能、あるいは、ヘッダファイルを適切な場所に公開することによって C 言語拡張をビルドする標準的な機構を完全に統合する機能) への信頼を故意に避けることになります。第二に、 Python のソフトウェアの中には zip アーカイブから直接に動作させることをサポートするように書かれているものもありますが、やはり完全にインストールされることを前提にして書かれたソースコードが今も普通です。 zip アーカイブからソフトウェアを走らせようと試みてこの仮定を崩すと、 (とりわけ失敗動作がサードパーティのライブラリで起きる場合には) しばしば失敗動作がわかりにくく原因究明が困難になるでしょう。この問題の最もありがちなふたつの根源は、 C 言語拡張を zip アーカイブからインポートすることが CPython では サポートされていない こと (というのは、どんなプラットフォームの動的ローディング機構でもこのような動作が直接にはサポートされていないから) 、および、 zip アーカイブから動作する際には
__file__属性がもはや普通のファイルシステム上のファイルパスを参照しておらず、 zip アーカイブのファイルシステム上の置き場所のパスとアーカイブ内のモジュールへの相対パスの両方を含んだ連結パスになることです。ソフトウェア内では抽象化された資源への API 群を正しく扱えたとしても、外部の部品とのインタフェイスは依然として実際にディスク上に存在するファイルがないと動作できないかもしれません。メタクラスと同様に、モンキーパッチングとメタパスからのインポートは、この機能を使う利点をあなたが本当に必要としていると確信しているのでなければ、おそらくあなたはこの機能を使う必要がないと思われます。兎にも角にもこれを使う 意思を固めた のであれば、 (訳注、この機能を使った時に生じた) ある動作不良を真性のバグであると認めてもらう前に、多くのプロジェクトではそれを完全インストールの状態で再現するように要求されるであろうということを認識しておいてください。
歴史¶
2013年2月: PEP 427 を通じてこの仕様書が承認されました。
2021年2月: 他の普及しているツールが実際にやるのと同じやり方に合わせて、 wheel のファイル名におけるエスケーピングの規則が修正されました。
2024年12月:
scriptsフォルダには、通常ファイルだけが含まれているべきであると明確化された (このフォルダ内でシンボリックリンクやサブディレクトリに遭遇した場合に消費する側のツール類がどのように振る舞うことを期待されているかについての公式には定義されていないので、ツールが異なれば動作が異なるかもしれません) 。2022年12月: PEP 639 を通じて、
.dist-info/licenses/ディレクトリが仕様化された。2025年1月: 名称とバージョン番号が
.dist-infoと.dataのディレクトリのために標準化される必要があると明確化された。2026年1月: PEP 815 で RECORD.jws および RECORD.p7s が非推奨になりました。
補遺¶
urlsafe-base64-nopad の実装の例:
# urlsafe-base64-nopad for Python 3
import base64
def urlsafe_b64encode_nopad(data):
return base64.urlsafe_b64encode(data).rstrip(b'=')
def urlsafe_b64decode_nopad(data):
pad = b'=' * (4 - (len(data) & 3))
return base64.urlsafe_b64decode(data + pad)