"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" の実装をオ
ーバーライドしたりすることもできます。 さらに工夫する際はソースを参考
にしてください。
