Содержание страницы
importlib.metadata – Доступ к метаданным пакета¶importlib.metadata – Accessing package metadata
Добавлено в версии 3.8.
Изменено в версии 3.10: importlib.metadata больше не является предварительным.
Исходный код: Lib/importlib/metadata/__init__.py
importlib.metadata – это библиотека, предоставляющая доступ к метаданным установленного дистрибутивного пакета,
таким как его точки входа
или имена верхнего уровня (импортируемые пакеты, модули, если таковые имеются).
Построенная отчасти на системе импорта Python, эта библиотека
предоставляет API точек входа и метаданных, которые ранее
были доступны через ныне удалённый пакет pkg_resources. Вместе с
importlib.resources она заменяет pkg_resources.
importlib.metadata работает со сторонними дистрибутивными пакетами,
установленными в каталог site-packages Python с помощью таких инструментов, как
pip.
В частности, он работает с дистрибутивами, имеющими обнаруживаемые
каталоги dist-info или egg-info,
и метаданными, определёнными спецификациями основных метаданных.
Важно
Они не обязательно эквивалентны или соответствуют 1:1 именам импортируемых пакетов верхнего уровня, которые можно импортировать в коде Python. Один дистрибутивный пакет может содержать несколько импортируемых пакетов (и отдельных модулей), а один импортируемый пакет верхнего уровня может соответствовать нескольким дистрибутивным пакетам, если он является пакетом пространства имён. Вы можете использовать packages_distributions(), чтобы получить отображение между ними.
По умолчанию метаданные дистрибутива могут находиться в файловой системе
или в zip-архивах в
sys.path. Благодаря механизму расширений метаданные могут находиться почти
где угодно.
См. также
- https://importlib-metadata.readthedocs.io/
Документация для
importlib_metadata, который предоставляет обратный портimportlib.metadata. Она включает справочник API для классов и функций этого модуля, а также руководство по миграции для существующих пользователейpkg_resources.
Обзор¶Overview
Предположим, вы хотите получить строку версии для
дистрибутивного пакета, установленного
с помощью 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.
Вы можете получить метаданные для дистрибутива:
>>> 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¶
Подкласс
ModuleNotFoundError, вызываемый несколькими функциями в этом модуле при запросе дистрибутивного пакета, который не установлен в текущем окружении Python.
Функциональное API¶Functional API
Этот пакет предоставляет следующие функции через свой публичный API.
Точки входа¶Entry points
- importlib.metadata.entry_points(**select_params)¶
Возвращает экземпляр
EntryPoints, описывающий точки входа для текущего окружения. Любые заданные именованные параметры передаются методуselect()для сравнения с атрибутами отдельных определений точек входа.Примечание: в настоящее время невозможно запрашивать точки входа на основе их атрибута
EntryPoint.dist(поскольку разные экземплярыDistributionв настоящее время не сравниваются как равные, даже если они имеют одинаковые атрибуты)
- class importlib.metadata.EntryPoints¶
Подробности о коллекции установленных точек входа.
Также предоставляет атрибут
.groups, который сообщает все идентифицированные группы точек входа, и атрибут.names, который сообщает все идентифицированные имена точек входа.
- class importlib.metadata.EntryPoint¶
Подробности об установленной точке входа.
Каждый экземпляр
EntryPointимеет атрибуты.name,.groupи.valueи метод.load()для разрешения значения. Также есть атрибуты.module,.attrи.extrasдля получения компонентов атрибута.valueи.distдля получения информации о дистрибутивном пакете, который предоставляет точку входа.
Запрос всех точек входа:
>>> eps = entry_points()
Функция entry_points() возвращает объект EntryPoints,
коллекцию всех объектов EntryPoint с атрибутами 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: «Выбираемые» («selectable») точки входа появились в 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__()).
Метаданные дистрибутива¶Distribution metadata
- importlib.metadata.metadata(distribution_name)¶
Возвращает метаданные дистрибутива для указанного пакета дистрибутива в виде экземпляра
PackageMetadata.Вызывает
PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.
- class importlib.metadata.PackageMetadata¶
Конкретная реализация протокола PackageMetadata.
В дополнение к предоставлению определённых методов и атрибутов протокола, обращение к экземпляру по индексу эквивалентно вызову метода
get().
Каждый пакет дистрибутива содержит метаданные, которые можно извлечь с помощью функции metadata():
>>> wheel_metadata = metadata('wheel')
Ключи возвращаемой структуры данных соответствуют ключевым словам метаданных, а значения возвращаются без разбора из метаданных дистрибутива:
>>> wheel_metadata['Requires-Python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
PackageMetadata также предоставляет атрибут json, который возвращает все метаданные в JSON-совместимой форме согласно PEP 566:
>>> wheel_metadata.json['requires_python']
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
Полный набор доступных метаданных здесь не описывается. За дополнительными сведениями обратитесь к спецификации основных метаданных PyPA.
Изменено в версии 3.10: Теперь Description включается в метаданные при представлении через полезную нагрузку (payload). Символы продолжения строки удалены.
Добавлен атрибут json.
Версии дистрибутивов¶Distribution versions
- importlib.metadata.version(distribution_name)¶
Возвращает версию установленного пакета дистрибутива для указанного пакета дистрибутива.
Вызывает
PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.
Функция version() – это самый быстрый способ получить номер версии пакета дистрибутива в виде строки:
>>> version('wheel')
'0.32.3'
Файлы дистрибутива¶Distribution files
- importlib.metadata.files(distribution_name)¶
Возвращает полный набор файлов, содержащихся в указанном пакете дистрибутива.
Вызывает
PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.Возвращает
None, если дистрибутив найден, но в базе данных установки отсутствуют записи о файлах, связанных с пакетом дистрибутива.
- class importlib.metadata.PackagePath¶
Объект, производный от
pathlib.PurePath, с дополнительными свойствамиdist,sizeиhash, соответствующими метаданным установки пакета дистрибутива для этого файла.
Функция files() принимает имя дистрибутивного пакета и возвращает все файлы, установленные этим дистрибутивом. Каждый файл представлен как экземпляр PackagePath. Например:
>>> 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 или иным образом защититься от такой ситуации, если неизвестно, что целевой дистрибутив содержит эти метаданные.
Зависимости дистрибутивов¶Distribution requirements
- importlib.metadata.requires(distribution_name)¶
Возвращает объявленные спецификаторы зависимостей для именованного дистрибутивного пакета.
Вызывает
PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.
Чтобы получить полный набор зависимостей для дистрибутивного пакета, используйте функцию requires():
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
Отображение импортов на дистрибутивы¶Mapping import to distribution packages
- importlib.metadata.packages_distributions()¶
Возвращает отображение имён модулей верхнего уровня и импортируемых пакетов, найденных с помощью
sys.meta_path, на имена дистрибутивных пакетов (если таковые есть), предоставляющих соответствующие файлы.Для поддержки пакетов пространств имён (которые могут содержать компоненты от нескольких дистрибутивов) каждое имя импорта верхнего уровня сопоставляется со списком имён дистрибутивов, а не напрямую с одним именем.
Удобный метод для разрешения имени дистрибутивного пакета (или имён, в случае пакета пространства имён), который предоставляет каждый импортируемый модуль или импортируемый пакет верхнего уровня:
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
Некоторые редактируемые установки не предоставляют имён верхнего уровня, поэтому эта функция не является надёжной для таких установок.
Добавлено в версии 3.10.
Дистрибутивы¶Distributions
- importlib.metadata.distribution(distribution_name)¶
Возвращает экземпляр
Distribution, описывающий именованный дистрибутивный пакет.Вызывает
PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.
- class importlib.metadata.Distribution¶
Сведения об установленном дистрибутивном пакете.
Примечание: разные экземпляры
Distributionв настоящее время не считаются равными, даже если они относятся к одному и тому же установленному дистрибутиву и, соответственно, имеют одинаковые атрибуты.
Хотя описанный выше API на уровне модуля является наиболее распространённым и удобным, всю эту информацию можно получить из класса Distribution. Distribution – это абстрактный объект, представляющий метаданные дистрибутивного пакета Python. Конкретный экземпляр подкласса Distribution для установленного дистрибутивного пакета можно получить, вызвав функцию distribution():
>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
Таким образом, альтернативный способ получить номер версии – через экземпляр Distribution:
>>> dist.version
'0.32.3'
На экземплярах Distribution доступны самые разные дополнительные метаданные:
>>> 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.
Добавлено в версии 3.13: Добавлено свойство .origin.
Обнаружение дистрибутивов¶Distribution Discovery
По умолчанию этот пакет предоставляет встроенную поддержку поиска метаданных для дистрибутивных пакетов файловой системы и zip-файлов. Поиск метаданных по умолчанию использует sys.path, но трактует эти значения немного иначе, чем другие механизмы импорта. В частности:
importlib.metadataне учитывает объектыbytesизsys.path.importlib.metadataслучайно будет учитывать объектыpathlib.Pathизsys.path, хотя такие значения игнорируются при импорте.
Реализация пользовательских провайдеров¶Implementing Custom Providers
importlib.metadata охватывает два API: один для потребителей, а другой для провайдеров. Большинство пользователей являются потребителями, потребляя метаданные, предоставляемые пакетами. Однако есть и другие сценарии, в которых пользователи хотят предоставлять метаданные через какой-то другой механизм, например, вместе с пользовательским импортёром. Такой сценарий требует пользовательского провайдера.
Поскольку метаданные дистрибутивного пакета недоступны напрямую через поиск sys.path или загрузчики пакетов, метаданные для дистрибутива находятся через искатели системы импорта. Чтобы найти метаданные дистрибутивного пакета, importlib.metadata опрашивает список искателей путей метаданных из sys.meta_path.
Реализация включает хуки, встроенные в PathFinder, которые предоставляют метаданные для дистрибутивных пакетов, найденных в файловой системе.
Абстрактный класс importlib.abc.MetaPathFinder определяет интерфейс, ожидаемый от искателей системой импорта Python.
importlib.metadata расширяет этот протокол, проверяя наличие опционального вызываемого объекта find_distributions у искателей из sys.meta_path, и предоставляет этот расширенный интерфейс как абстрактный базовый класс DistributionFinder, который определяет следующий абстрактный метод:
@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
"""Возвращает итерируемый объект со всеми экземплярами Distribution, способными
загружать метаданные для пакетов для указанного ``context``.
"""
Объект DistributionFinder.Context предоставляет свойства .path и .name, указывающие путь для поиска и имя для сопоставления, и может предоставлять другой релевантный контекст, запрашиваемый потребителем.
На практике, чтобы поддерживать поиск метаданных дистрибутивных пакетов в местах, отличных от файловой системы, создайте подкласс Distribution и реализуйте абстрактные методы. Затем из пользовательского искателя возвращайте экземпляры этого производного Distribution в методе find_distributions().
Пример¶Example
Представьте пользовательский искатель, который загружает модули 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 находится в базе данных, find_distributions вернёт DatabaseDistribution для Context(name='requests') или Context(name=None).
Ради простоты этот пример игнорирует context.path. Атрибут path по умолчанию равен sys.path и представляет собой набор путей импорта, которые следует учитывать при поиске. DatabaseImporter может потенциально функционировать без учёта пути поиска. Если предположить, что импортёр не выполняет разделение, «путь» не будет иметь значения. Чтобы проиллюстрировать назначение path, пример должен показать более сложный DatabaseImporter, поведение которого зависит от sys.path/PYTHONPATH. В таком случае find_distributions должен соблюдать context.path и возвращать только Distribution, относящиеся к этому пути.
Тогда DatabaseDistribution будет выглядеть примерно так:
class DatabaseDistribution(importlib.metadata.Distribution):
def __init__(self, record):
self.record = record
def read_text(self, filename):
"""
Читает файл, например "METADATA", для текущего Distribution.
"""
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")
Эта базовая реализация должна предоставлять метаданные и точки входа для пакетов, обслуживаемых DatabaseImporter, при условии, что record предоставляет подходящие атрибуты .name, .version и .entry_points.
DatabaseDistribution может также предоставлять другие файлы метаданных, например RECORD (требуется для Distribution.files), или переопределять реализацию Distribution.files. За дополнительными идеями обращайтесь к исходному коду.