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 の コアメタデータ仕様 を参照してください。
- version: str¶
いくつかのメタデータフィールドには、手軽にアクセスするためのプロパティもあります。
Added in version 3.10:
nameを手軽に取得できる属性を追加しました。
- origin¶
For editable packages, an
originproperty may present PEP 610 metadata (for non-editable packages,originisNone):>>> 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¶
この配布パッケージが提供するエントリポイントです。
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の場合、すべての配布物に一致します。
- 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 の実装をオーバーライドしたりすることもできます。
さらに工夫する際はソースを参考にしてください。