Документация Python неофициальный перевод
Содержание страницы

Использование importlib.metadataUsing importlib.metadata

Новое в версии 3.8.

Изменено в версии 3.10: importlib.metadata больше не является предварительным.

Исходный код: Lib/importlib/metadata/__init__.py

importlib.metadata – это библиотека, обеспечивающая доступ к метаданным установленных пакетов. Частично основанная на системе импорта Python, эта библиотека призвана заменить аналогичную функциональность в точки входа API и API метаданных из pkg_resources. Вместе с importlib.resources (начиная с Python 3.7 и новее, с обратным портированием как importlib_resources для более старых версий Python), это позволяет отказаться от использования устаревшего и менее эффективного пакета pkg_resources.

Под «установленным пакетом» обычно понимается сторонний пакет, установленный в каталог site-packages Python с помощью таких инструментов, как pip. В частности, это пакет, имеющий либо обнаруживаемый каталог dist-info или egg-info и метаданные, определённые PEP 566 или более старыми спецификациями. По умолчанию метаданные пакета могут находиться в файловой системе или в zip-архивах на sys.path. Благодаря механизму расширения метаданные могут находиться практически где угодно.

ОбзорOverview

Предположим, вы хотите получить строку версии для пакета, установленного с помощью pip. Начнём с создания виртуального окружения и установки чего-нибудь в него:

$ python -m venv example
$ source example/bin/activate
(example) $ pip install wheel

Вы можете получить строку версии для wheel, выполнив следующее:

(example) $ python
>>> from importlib.metadata import version  
>>> version('wheel')  
'0.32.3'

Также можно получить набор точек входа, разбитых по группам, таких как 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']

Вы также можете получить номер версии дистрибутива, перечислить его составные файлы и получить список зависимостей дистрибутива.

Функциональное APIFunctional API

Этот пакет предоставляет следующие функции через свой публичный API.

Точки входаEntry points

Функция entry_points() возвращает коллекцию точек входа. Точки входа представлены экземплярами EntryPoint; каждый EntryPoint имеет атрибуты .name, .group и .value, а также метод .load() для получения значения. Также есть атрибуты .module, .attr и .extras для получения компонентов атрибута .value.

Запрос всех точек входа:

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

Примечание о совместимости

«Выбираемые» точки входа были введены в importlib_metadata 3.6 и Python 3.10. До этих изменений entry_points не принимал параметров и всегда возвращал словарь точек входа, сгруппированных по группе. Для совместимости, если в entry_points не переданы параметры, возвращается объект SelectableGroups, реализующий интерфейс словаря. В будущем вызов entry_points без параметров будет возвращать объект EntryPoints. Пользователям следует полагаться на интерфейс выбора для получения точек входа по группе.

Метаданные дистрибутиваDistribution metadata

Каждый дистрибутив содержит некоторые метаданные, которые можно извлечь с помощью функции metadata():

>>> wheel_metadata = metadata('wheel')  

Ключи возвращаемой структуры данных, PackageMetadata, определяют ключевые слова метаданных, а значения возвращаются неразобранными из метаданных дистрибутива:

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

Изменено в версии 3.10: Теперь Description включается в метаданные при представлении через полезную нагрузку (payload). Символы продолжения строки удалены.

Новое в версии 3.10: Был добавлен атрибут json.

Версии дистрибутивовDistribution versions

Функция version() – это самый быстрый способ получить номер версии дистрибутива в виде строки:

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

Файлы дистрибутиваDistribution files

Также можно получить полный набор файлов, содержащихся в дистрибутиве. Функция files() принимает имя пакета дистрибутива и возвращает все файлы, установленные этим дистрибутивом. Каждый возвращаемый файловый объект – это PackagePath, производный объект pathlib.PurePath с дополнительными свойствами dist, size и hash, как указано в метаданных. Например:

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

Чтобы получить полный набор зависимостей для дистрибутива, используйте функцию requires():

>>> requires('wheel')  
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]

Дистрибутивы пакетовPackage distributions

Удобный метод для определения дистрибутива или дистрибутивов (в случае пространства имён пакета) для корневых пакетов или модулей Python:

>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}

Новое в версии 3.10.

ДистрибутивыDistributions

Хотя приведённый выше API является наиболее распространённым и удобным способом использования, всю эту информацию можно получить из класса Distribution. Distribution – это абстрактный объект, представляющий метаданные для пакета Python. Можно получить экземпляр Distribution:

>>> from importlib.metadata import distribution  
>>> dist = distribution('wheel')  

Таким образом, альтернативный способ получить номер версии – через экземпляр 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'

Полный набор доступных метаданных здесь не описан. Подробнее см. PEP 566 .

Расширение алгоритма поискаExtending the search algorithm

Поскольку метаданные пакета недоступны через поиск sys.path или напрямую через загрузчики пакетов, метаданные пакета находятся через систему импорта искатели. Чтобы найти метаданные пакета дистрибутива, importlib.metadata запрашивает список искателей метапути в sys.meta_path.

Стандартный PathFinder для Python включает хук, который вызывает importlib.metadata.MetadataPathFinder для поиска дистрибутивов, загруженных из типичных путей файловой системы.

Абстрактный класс importlib.abc.MetaPathFinder определяет интерфейс, ожидаемый от искателей системой импорта Python. importlib.metadata расширяет этот протокол, проверяя наличие опционального вызываемого объекта find_distributions у искателей из sys.meta_path, и предоставляет этот расширенный интерфейс как абстрактный базовый класс DistributionFinder, который определяет следующий абстрактный метод:

@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()):
    """Возвращает итерируемый объект со всеми экземплярами Distribution, способными
    загружать метаданные для пакетов для указанного ``context``.
    """

Объект DistributionFinder.Context предоставляет свойства .path и .name, указывающие путь для поиска и имя для сопоставления, а также может предоставлять другой релевантный контекст.

На практике это означает, что для поддержки поиска метаданных дистрибутивного пакета в местах, отличных от файловой системы, нужно создать подкласс Distribution и реализовать абстрактные методы. Затем из пользовательского находильщика возвращать экземпляры этого производного Distribution в методе find_distributions().