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

Added in version 3.8.

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

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

importlib.metadata is a library that provides access to the metadata of an installed Distribution Package, such as its entry points or its top-level names (Import Packages, modules, if any). Built in part on Python's import system, this library provides the entry point and metadata APIs that were previously exposed by the now-removed pkg_resources package. Along with importlib.resources, it supersedes pkg_resources.

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

重要

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

By default, distribution metadata can live on the file system or in zip archives on sys.path. Through an extension mechanism, the metadata can live almost anywhere.

参考

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

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

概要

Let's say you wanted to get the version string for a Distribution Package you've installed using pip. We start by creating a virtual environment and installing something into it:

$ 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'

You can also get a collection of entry points selectable by properties of the EntryPoint (typically 'group' or 'name'), such as console_scripts, distutils.commands and others. Each group contains a collection of EntryPoint objects.

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

>>> 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 のサブクラスです。

関数 API

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

エントリポイント

importlib.metadata.entry_points(**select_params)

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

Note: it is not currently possible to query for entry points based on their EntryPoint.dist attribute (as different Distribution instances do not currently compare equal, even if they have the same attributes)

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>

The group and name are arbitrary values defined by the package author and usually a client will wish to resolve all entry points for a particular group. Read the setuptools docs for more information on entry points, their definition, and usage.

バージョン 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 を送出します。

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.10 で変更: ペイロードを通して提示されるとき、 Description がメタデータに含まれるようになりました。行の継続文字は削除されました。

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

配布物バージョン

importlib.metadata.version(distribution_name)

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

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

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

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

配布物ファイル

importlib.metadata.files(distribution_name)

Return the full set of files contained within the named distribution package.

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

Returns None if the distribution is found but the installation database records reporting the files associated with the distribuion package are missing.

class importlib.metadata.PackagePath

A pathlib.PurePath derived object with additional dist, size, and hash properties corresponding to the distribution package's installation metadata for that file.

The files() function takes a Distribution Package name and returns all of the files installed by this distribution. Each file is reported as a PackagePath instance. For example:

>>> 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.

配布物

importlib.metadata.distribution(distribution_name)

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

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

class importlib.metadata.Distribution

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

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

While the module level API described above is the most common and convenient usage, you can get all of that information from the Distribution class. Distribution is an abstract object that represents the metadata for a Python Distribution Package. You can get the concrete Distribution subclass instance for an installed distribution package by calling the distribution() function:

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

Thus, an alternative way to get the version number is through the Distribution instance:

>>> dist.version
'0.32.3'

There are all kinds of additional metadata available on Distribution instances:

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

For editable packages, an origin property may present PEP 610 metadata:

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

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

Added in version 3.13: The .origin property was added.

配布物の検出

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

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

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

Extending the search algorithm

Because Distribution Package metadata is not available through sys.path searches, or package loaders directly, the metadata for a distribution is found through import system finders. To find a distribution package's metadata, importlib.metadata queries the list of meta path finders on sys.meta_path.

By default importlib.metadata installs a finder for distribution packages found on the file system. This finder doesn't actually find any distributions, but it can find their metadata.

抽象クラス importlib.abc.MetaPathFinder はPythonの importシステムによってファインダーに期待されるインターフェイスを定義しています。 importlib.metadata はこのプロトコルを拡張し、 sys.meta_path からファインダーにオプションの find_distributions を呼び出すことができるようにし、この拡張インターフェースを DistributionFinder 抽象基底クラスとして提示し、この抽象メソッドを定義しています:

@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()):
    """Return an iterable of all Distribution instances capable of
    loading the metadata for packages for the indicated ``context``.
    """

The DistributionFinder.Context object provides .path and .name properties indicating the path to search and name to match and may supply other relevant context.

What this means in practice is that to support finding distribution package metadata in locations other than the file system, subclass Distribution and implement the abstract methods. Then from a custom finder, return instances of this derived Distribution in the find_distributions() method.

使用例

Consider for example a custom finder that loads Python modules from a database:

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(...)))

That importer now presumably provides importable modules from a database, but it provides no metadata or entry points. For this custom importer to provide metadata, it would also need to implement 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 が提供するパッケージのメタデータとエントリポイントを取得できるはずです。

The DatabaseDistribution may also provide other metadata files, like RECORD (required for Distribution.files) or override the implementation of Distribution.files. See the source for more inspiration.