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のサブクラスです。
関数 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.distattribute (as differentDistributioninstances 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>
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を送出します。
- 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を送出します。配布物は見つかっても、関連するファイルを記録したインストールデータベースのレコードがない場合は、
Noneを返します。
- class importlib.metadata.PackagePath¶
A
pathlib.PurePathderived object with additionaldist,size, andhashproperties 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オブジェクトも、副次的に受け付けます。
独自のプロバイダの実装¶
importlib.metadata は、利用者 向けと プロバイダ 向けの 2 種類の API を扱います。
ほとんどのユーザは、パッケージが提供するメタデータを使う利用者です。
一方、独自のインポーターとともに提供する場合など、別の仕組みでメタデータを公開したい用途もあります。
そのような場合には 独自のプロバイダ が必要です。
配布パッケージ のメタデータは、sys.path の検索やパッケージのローダーから直接取得できないため、インポートシステムの ファインダー を通じて検出します。
配布パッケージのメタデータを探すために、importlib.metadata は sys.meta_path にある メタパスファインダー のリストに問い合わせます。
この実装では PathFinder にフックを組み込み、ファイルシステム上で見つかった配布パッケージのメタデータを提供します。
The abstract class importlib.abc.MetaPathFinder defines the
interface expected of finders by Python's import system.
importlib.metadata extends this protocol by looking for an optional
find_distributions callable on the finders from
sys.meta_path and presents this extended interface as the
DistributionFinder abstract base class, which defines this abstract
method:
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
"""指定された ``context`` に対応するパッケージのメタデータを
読み込めるすべての Distribution インスタンスのイテラブルを返す。
"""
The DistributionFinder.Context object provides .path and .name
properties indicating the path to search and name to match and may
supply other relevant context sought by the consumer.
In practice, 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.
使用例¶
データベースから 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(...)))
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.