importlib.metadata -- パッケージメタデータへのアクセス

Added in version 3.8.

バージョン 3.10 で変更: importlib.metadata は暫定的なものではなくなりました。

ソースコード: Lib/importlib/metadata/__init__.py

importlib.metadata は、インストール済みの 配布パッケージ のメタデータにアクセスするライブラリです。 メタデータには、エントリポイントやトップレベルの名前 (インポートパッケージ や、存在する場合はモジュール) などがあります。 このライブラリは一部に Python のインポートシステムを利用しており、現在は削除された pkg_resources パッケージが提供していたエントリポイントとメタデータの API を提供します。 importlib.resources とともに pkg_resources を置き換えるものです。

importlib.metadata は、Pythonの site-packages ディレクトリに pip などのツールでインストールしたサードパーティの 配布パッケージ に対して動作します。具体的には、 dist-info や egg-info ディレクトリを持つ配布物や、 コアとなるメタデータの仕様 で定義されたメタデータを検出できるようにします。

重要

これらはPythonコード内でインポートできるトップレベルの パッケージ 名と 必ずしも 同等であったり、1:1 で対応するものではありません。1つの 配布パッケージ は複数の パッケージ (および単一のモジュール) を含むことができ、1つのトップレベルの パッケージ は、それが名前空間パッケージであれば複数の 配布パッケージ にマップすることができます。これらのマッピングを得るには packages_distributions() を使用します。

デフォルトでは、配布物のメタデータは sys.path 上のファイルシステムや zip アーカイブに置けます。 拡張機構を使えば、ほぼどこにでも置けます。

参考

https://importlib-metadata.readthedocs.io/

importlib.metadata のバックポートを提供する importlib_metadata のドキュメントです。 これには、このモジュールのクラスと関数の APIリファレンス と、 pkg_resources の既存のユーザーのための 移行ガイド があります。

概要

pip でインストールした 配布パッケージ のバージョン文字列を取得したいとします。 まず仮想環境を作り、パッケージをインストールします。

$ python -m venv example
$ source example/bin/activate
(example) $ python -m pip install wheel

以下のように実行することで、wheel のバージョン文字列を取得することができます:

(example) $ python
>>> from importlib.metadata import version
>>> version('wheel')
'0.32.3'

EntryPoint の属性 (通常は 'group' または 'name') によって選択できる、エントリポイントのコレクションも取得できます。 グループには console_scripts や distutils.commands などがあり、各グループは EntryPoint オブジェクトのコレクションを含みます。

ディストリビューションのメタデータ を取得することができます。:

>>> from importlib.metadata import metadata
>>> list(metadata('wheel'))
['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Project-URL', 'Project-URL', 'Project-URL', 'Keywords', 'Platform', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Requires-Python', 'Provides-Extra', 'Requires-Dist', 'Requires-Dist']

また、 配布物のバージョン番号 を取得し、 構成ファイル をリストアップし、配布物の 配布物の要件 のリストを取得することができます。

exception importlib.metadata.PackageNotFoundError

現在の Python 環境にインストールされていない配布パッケージについて問い合わせた際に、このモジュールのいくつかの関数が送出する ModuleNotFoundError のサブクラスです。

exception importlib.metadata.MetadataNotFound

空の配布物フォルダや、その他の理由でメタデータファイルがない配布物フォルダからメタデータを読み込もうとした際に送出する、FileNotFoundError のサブクラスです。

機能 API

本パッケージは、公開APIを通じて以下の機能を提供します。

エントリポイント

importlib.metadata.entry_points(**select_params)

現在の環境のエントリポイントを表す EntryPoints インスタンスを返します。 指定されたキーワード引数はすべて select() メソッドに渡され、個々のエントリポイント定義の属性と比較されます。

注: EntryPoint.dist 属性に基づいてエントリポイントを問い合わせるには、代わりに Distribution.entry_points() を使ってください (現在、異なる Distribution インスタンスは、属性が同じでも等しいとは判定されません)。

class importlib.metadata.EntryPoints

インストール済みのエントリポイントのコレクションの詳細を表します。

見つかったすべてのエントリポイントのグループを返す .groups 属性と、すべてのエントリポイント名を返す .names 属性も提供します。

class importlib.metadata.EntryPoint

インストール済みのエントリポイントの詳細を表します。

各 EntryPoint インスタンスには、.name、.group、.value 属性と、値を解決する .load() メソッドがあります。 また、.value 属性の構成要素を取得する .module、.attr、.extras 属性と、エントリポイントを提供する配布パッケージの情報を取得する .dist 属性もあります。

すべてのエントリポイントに問い合わせる:

>>> eps = entry_points()

entry_points() 関数は、すべての EntryPoint オブジェクトのコレクションである EntryPoints オブジェクトを返します。 このオブジェクトには、便利な names 属性と groups 属性があります。

>>> sorted(eps.groups)
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']

EntryPoints には、特定の属性に合うエントリポイントを選ぶ select() メソッドがあります。 console_scripts グループのエントリポイントを選びます。

>>> scripts = eps.select(group='console_scripts')

entry_points() はキーワード引数を select に渡すので、次も等価です。

>>> scripts = entry_points(group='console_scripts')

"wheel" という名前の特定のスクリプトを選択します。(wheelプロジェクトにあります):

>>> 'wheel' in scripts.names
True
>>> wheel = scripts['wheel']

同様に、選択時にそのエントリポイントを問い合わせます:

>>> (wheel,) = entry_points(group='console_scripts', name='wheel')
>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')

解決したエントリポイントを検証する:

>>> wheel
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
>>> wheel.module
'wheel.cli'
>>> wheel.attr
'main'
>>> wheel.extras
[]
>>> main = wheel.load()
>>> main
<function main at 0x103528488>

group と name はパッケージ作者が定める任意の値です。 通常、利用する側では特定のグループに属するすべてのエントリポイントを解決したいと考えるでしょう。 エントリポイントとその定義、使い方の詳細は setuptools のドキュメント を読んでください。

バージョン 3.12 で変更: 「選択可能な」エントリポイントは importlib_metadata 3.6 と Python 3.10 で導入されました。 それ以前の entry_points は引数を受け付けず、常にグループをキーとするエントリポイントの辞書を返していました。 importlib_metadata 5.0 と Python 3.12 以降では、entry_points は常に EntryPoints オブジェクトを返します。 互換性を保つ方法については backports.entry_points_selectable を参照してください。

バージョン 3.13 で変更: EntryPoint オブジェクトはタプルのようなインターフェース (__getitem__()) を提供しなくなりました。

配布物メタデータ

importlib.metadata.metadata(distribution_name)

指定した名前の配布パッケージに対応するメタデータを、PackageMetadata インスタンスとして返します。

指定した名前の配布パッケージが現在の Python 環境にインストールされていなければ、PackageNotFoundError を送出します。

配布パッケージは存在しても METADATA ファイルが存在しない場合、MetadataNotFound を送出します。

class importlib.metadata.PackageMetadata

PackageMetadata プロトコル の具体的な実装です。

In addition to providing the defined protocol methods and attributes, subscripting the instance is equivalent to calling the get() method.

すべての 配布パッケージ はメタデータを含み、metadata() 関数で取り出せます。

>>> wheel_metadata = metadata('wheel')

返されるデータ構造のキーはメタデータのキーワード名で、値は配布物のメタデータから解析せずに返されます。

>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

PackageMetadata は json 属性も提供し、PEP 566 に従う JSON 互換の形式ですべてのメタデータを返します。

>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

利用できるメタデータのすべては、ここでは説明しません。 詳細は PyPA の コアメタデータ仕様 を参照してください。

バージョン 3.15 で変更: 従来は意図せず、配布物に METADATA ファイルがない場合に空の PackageMetadata が返され、空の METADATA ファイルと区別できませんでした。 現在は METADATA ファイルがない場合に MetadataNotFound 例外を送出します。

バージョン 3.10 で変更: ペイロードを通して提示されるとき、 Description がメタデータに含まれるようになりました。行の継続文字は削除されました。

json 属性が追加されました。

配布物バージョン

importlib.metadata.version(distribution_name)

指定した名前の配布パッケージの、インストール済みの バージョン を返します。

指定した名前の配布パッケージが現在の Python 環境にインストールされていなければ、PackageNotFoundError を送出します。

version() 関数は、配布パッケージ のバージョン番号を文字列として取得する最も手軽な方法です。

>>> version('wheel')
'0.32.3'

配布物ファイル

importlib.metadata.files(distribution_name)

指定した名前の配布パッケージに含まれるすべてのファイルを、PackagePath インスタンスとして返します。

指定した名前の配布パッケージが現在の Python 環境にインストールされていなければ、PackageNotFoundError を送出します。

配布物は見つかっても、関連するファイルを記録したインストールデータベースのレコードがない場合は、None を返します。

class importlib.metadata.PackagePath

pathlib.PurePath から派生したオブジェクトで、そのファイルの配布パッケージのインストールメタデータに対応する dist、size、hash プロパティが追加されています。 また、次のメソッドもあります。

locate()

可能であれば、データにアクセスするための具体的な SimplePath を返します。 それ以外の場合は NotImplementedError を送出します。

class importlib.metadata.SimplePath

pathlib.Path の最小限の機能を表すプロトコルです。 exists() で存在を確認し、joinpath() と parent でパスをたどり、read_text() と read_bytes() でデータを取得できます。

files() 関数は 配布パッケージ の名前を受け取り、その配布物がインストールしたすべてのファイルを返します。 例えば次のように使います。

>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]
>>> util
PackagePath('wheel/util.py')
>>> util.size
859
>>> util.dist
<importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
>>> util.hash
<FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>

ファイルを取得したら、その内容を読むこともできます:

>>> print(util.read_text())
import base64
import sys
...
def as_bytes(s):
    if isinstance(s, text_type):
        return s.encode('utf-8')
    return s

locate() メソッドを使うと、ファイルの絶対パスを取得することもできます。

>>> util.locate()
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')

ファイル一覧を記録したメタデータファイル (RECORD または SOURCES.txt) がない場合、files() は None を返します。 対象の配布物にそのメタデータがあると分かっていない場合は、files() の呼び出しを always_iterable で包むなど、この状況に備えるとよいでしょう。

配布物の要件

importlib.metadata.requires(distribution_name)

指定した名前の配布パッケージについて、宣言された依存関係の指定を返します。

指定した名前の配布パッケージが現在の Python 環境にインストールされていなければ、PackageNotFoundError を送出します。

配布パッケージ の要件をすべて取得するには、requires() 関数を使います。

>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]

インポートパッケージから配布パッケージへの対応付け

importlib.metadata.packages_distributions()

sys.meta_path で見つかったトップレベルのモジュール名とインポートパッケージ名を、対応するファイルを提供する配布パッケージ名 (存在する場合) に対応付けるマッピングを返します。

名前空間パッケージ (複数の配布パッケージが構成要素を提供する場合があります) に対応するため、各トップレベルのインポート名は、単一の配布物名ではなく配布物名のリストに対応付けられます。

インポート可能なトップレベルのPythonモジュールまたは パッケージ を提供する 配布パッケージ 名(名前空間パッケージの場合はその名前)を解決する便利なメソッドです:

>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}

編集可能なインストールの中には トップレベルの名前を提供しないもの があるため、そのようなインストールではこの関数は信頼できません。

Added in version 3.10.

配布物

上記のモジュールレベルの API が最も一般的で便利な使い方ですが、これらの情報はすべて Distribution クラスからも取得できます。 Distribution は、Python の 配布パッケージ のメタデータを表す抽象オブジェクトです。 distribution() 関数を呼び出すと、インストール済みの配布パッケージに対応する Distribution の具体的なサブクラスのインスタンスを取得できます。

>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
importlib.metadata.distribution(distribution_name)

指定した名前の配布パッケージを表す Distribution インスタンスを返します。

指定した名前の配布パッケージが現在の Python 環境にインストールされていなければ、PackageNotFoundError を送出します。

そのため、例えばバージョン番号は Distribution.version 属性からも取得できます。

>>> dist.version
'0.32.3'

entry_points() と files() についても同様です。

class importlib.metadata.Distribution

インストール済みの配布パッケージの詳細を表します。

注: 現在、異なる Distribution インスタンスは、同じインストール済み配布物に対応し、その結果として属性が同じであっても、等しいとは判定されません。

static at(path)
classmethod from_name(name)

指定したパスまたは名前に対応する Distribution インスタンスを返します。

classmethod discover(*, context=None, **kwargs)

すべてのパッケージの Distribution インスタンスを返すイテラブルを返します (distribution-discovery を参照)。

オプションの context 引数は DistributionFinder.Context インスタンスで、配布物の検索方法を変更するために使います。 代わりに kwargs に、新たな DistributionFinder.Context を構築するキーワード引数を指定することもできます。

metadata: PackageMetadata

配布物に METADATA ファイルがなければ、MetadataNotFound を送出します。

Distribution インスタンスでは、さまざまな追加のメタデータを PackageMetadata インスタンスから取得できます。

>>> dist.metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
>>> dist.metadata['License']
'MIT'

利用できるメタデータのすべては、ここでは説明しません。 詳細は PyPA の コアメタデータ仕様 を参照してください。

name: str
requires: list[str]
version: str

いくつかのメタデータフィールドには、手軽にアクセスするためのプロパティもあります。

Added in version 3.10: name を手軽に取得できる属性を追加しました。

origin

For editable packages, an origin property may present PEP 610 metadata (for non-editable packages, origin is None):

>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'

origin オブジェクトは Direct URL Data Structure に従います。

Added in version 3.13.

entry_points: EntryPoints

この配布パッケージが提供するエントリポイントです。

files: list[PackagePath] | None

この配布パッケージに含まれるすべてのファイルです。 files() と同様、レコードがなければ None を返します。

implementing-custom-providers の際には、次の 2 つの抽象メソッドを実装する必要があります。

locate_file(path)

PackagePath.locate() と同様に、指定したパスの SimplePath を返します。 os.PathLike または str を受け取ります。

read_text(filename)

A shortcut for distribution.locate_file(filename).read_text().

配布物の検出

デフォルトでは、このパッケージは組み込みで、ファイルシステムおよび zip ファイル 配布パッケージ のメタデータを発見するためのサポートを提供します。このメタデータ検索のデフォルトは sys.path ですが、その値をどのように解釈するかは、他のインポート機構が行う方法とは若干異なります。具体的には:

  • importlib.metadata は sys.path の bytes オブジェクトを受け入れません。

  • importlib.metadata は、インポート時には無視される sys.path 上の pathlib.Path オブジェクトも、副次的に受け付けます。

class importlib.metadata.DistributionFinder

インストール済みの配布物を検出できる MetaPathFinder のサブクラスです。

独自のプロバイダでメタデータを提供するには、このインターフェースを実装する必要があります。

class Context(**kwargs)

Context は、配布物の検索時に .name と .path 以外の追加情報を、独自のプロバイダが呼び出し側に求めるための仕組みです。 呼び出し側は distributions() や Distribution.discover() などの配布物検出関数を使います。

例えば、プロバイダが "public" と "private" の realm のいずれかにパッケージ群を公開するとします。 配布物検出関数の呼び出し側が特定の領域の配布物だけを問い合わせたい場合、distributions(realm="private") と呼び出すことで、その領域の配布物だけを含めるよう独自のプロバイダに伝えられます。

各 DistributionFinder は任意の引数を想定し、適切な場合には以下に定義する標準の引数に従うようにしてください。

詳細は 独自のプロバイダの実装 を参照してください。

name

配布物のファインダーが照合する特定の名前です。

.name が None の場合、すべての配布物に一致します。

path

配布物のファインダーが検索するディレクトリパスの列を提供するプロパティです。

通常は "site-packages" など Python パッケージがインストールされているパスを指し、デフォルトは sys.path です。

importlib.metadata.distributions(**kwargs)

すべてのパッケージの Distribution インスタンスを返すイテラブルを返します。

kwargs 引数には、DistributionFinder.Context インスタンスを値とするキーワード引数 context、または新たな DistributionFinder.Context を構築するキーワード引数を指定できます。 DistributionFinder.Context は配布物の検索方法を変更するために使います。

独自のプロバイダの実装

importlib.metadata は、利用者 向けと プロバイダ 向けの 2 種類の API を扱います。 ほとんどのユーザは、パッケージが提供するメタデータを使う利用者です。 一方、独自のインポーターとともに提供する場合など、別の仕組みでメタデータを公開したい用途もあります。 そのような場合には 独自のプロバイダ が必要です。

配布パッケージ のメタデータは、sys.path の検索やパッケージのローダーから直接取得できないため、インポートシステムの ファインダー を通じて検出します。 配布パッケージのメタデータを探すために、importlib.metadata は sys.meta_path にある メタパスファインダー のリストに問い合わせます。

この実装では PathFinder にフックを組み込み、ファイルシステム上で見つかった配布パッケージのメタデータを提供します。

抽象クラス importlib.abc.MetaPathFinder は、Python のインポートシステムがファインダーに求めるインターフェースを定義します。 importlib.metadata は sys.meta_path のファインダーにオプションの find_distributions 呼び出し可能オブジェクトを探すことで、このプロトコルを拡張します。 拡張したインターフェースは、次の抽象メソッドを定義する DistributionFinder 抽象基底クラスとして提供されています。

@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
    """指定された ``context`` に対応するパッケージのメタデータを
    読み込めるすべての Distribution インスタンスのイテラブルを返す。
    """

DistributionFinder.Context オブジェクトは、検索するパスを示す path と、照合する名前を示す name を提供します。 利用者が求める他の関連情報も提供できます。

実際にファイルシステム以外の場所から配布パッケージのメタデータを検出するには、Distribution を継承して抽象メソッドを実装します。 そして、独自のファインダーの find_distributions() メソッドから、派生した Distribution のインスタンスを返します。

使用例

データベースから Python モジュールを読み込む独自のファインダーを考えます。

class DatabaseImporter(importlib.abc.MetaPathFinder):
    def __init__(self, db):
        self.db = db

    def find_spec(self, fullname, target=None) -> ModuleSpec:
        return self.db.spec_from_name(fullname)

sys.meta_path.append(DatabaseImporter(connect_db(...)))

このインポーターはデータベースからインポート可能なモジュールを提供するはずですが、メタデータやエントリポイントは提供しません。 メタデータも提供するには、DistributionFinder を実装する必要があります。

from importlib.metadata import DistributionFinder

class DatabaseImporter(DistributionFinder):
    ...

    def find_distributions(self, context=DistributionFinder.Context()):
        query = dict(name=context.name) if context.name else {}
        for dist_record in self.db.query_distributions(query):
            yield DatabaseDistribution(dist_record)

このようにすると、query_distributions は、クエリに一致するデータベース内の各配布物のレコードを返すことになります。 例えば、データベースに requests-1.0 があれば、Context(name='requests') または Context(name=None) に対して find_distributions は DatabaseDistribution を yield します。

説明を簡単にするため、この例では context.path を無視しています。 path 属性のデフォルトは sys.path で、検索対象とするインポートパスの集合です。 DatabaseImporter は検索パスを意識せずに動作することもできます。 インポーターがデータを分割して扱わないなら、"path" は関係ありません。 path の目的を説明するには、sys.path/PYTHONPATH に応じて動作を変える、より複雑な DatabaseImporter の例が必要になります。 その場合、find_distributions は context.path に従い、そのパスに関係する Distribution だけを yield する必要があります。

その場合、DatabaseDistribution は次のようになります。

class DatabaseDistribution(importlib.metadata.Distribution):
    def __init__(self, record):
        self.record = record

    def read_text(self, filename):
        """
        現在の配布物の "METADATA" などのファイルを読む。
        """
        if filename == "METADATA":
            return f"""Name: {self.record.name}
Version: {self.record.version}
"""
        if filename == "entry_points.txt":
            return "\n".join(
              f"""[{ep.group}]\n{ep.name}={ep.value}"""
              for ep in self.record.entry_points)

    def locate_file(self, path):
        raise RuntimeError("This distribution has no file system")

record が適切な .name、.version、.entry_points 属性を提供すれば、この基本的な実装で DatabaseImporter が提供するパッケージのメタデータとエントリポイントを取得できるはずです。

DatabaseDistribution は、RECORD (Distribution.files に必要) などの他のメタデータファイルを提供したり、Distribution.files の実装をオーバーライドしたりすることもできます。 さらに工夫する際はソースを参考にしてください。