Применение importlib.metadata

Примечание

Эта функциональность является предварительной и может отличаться от обычной семантики версии стандартной библиотеки.

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

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

Обзор

Допустим, вы хотели получить строку версии пакета, который вы установили с помощью pip. Мы начинаем с создания виртуальной среды и установки чего-то в нее:

$ python3 -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']

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

Функциональный API

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

Точки входа

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

>>> eps = entry_points()  # doctest: +SKIP
>>> list(eps)  # doctest: +SKIP
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
>>> scripts = eps['console_scripts']  # doctest: +SKIP
>>> wheel = [ep for ep in scripts if ep.name == 'wheel'][0]  # doctest: +SKIP
>>> wheel  # doctest: +SKIP
EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
>>> main = wheel.load()  # doctest: +SKIP
>>> main  # doctest: +SKIP
<function main at 0x103528488>

group и name являются произвольными значениями, определяемыми автором пакета, и обычно клиент желает разрешить все точки входа для конкретной группы. Дополнительные сведения о точках входа, их определении и использовании см. в разделе документы по setuptools.

Метаданные дистрибуции

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

>>> wheel_metadata = metadata('wheel')  

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

>>> wheel_metadata['Requires-Python']  
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'

Версии дистрибуции

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

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

Файлы дистрибуции

Вы также можете получить полный набор файлов, содержащихся в дистрибутиве. Функция files() принимает имя пакета распространения и возвращает все файлы, установленные этим дистрибутивом. Каждый возвращаемый файловый объект является PackagePath, производным объектом pathlib.Path с дополнительными свойствами 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

В случае, где файл метаданных, перечисляющий файлы (RECORD или SOURCES.txt), отсутствует, files() будет возвращает None. Вызывающий может пожелать передать вызовы files() в always_iterable или иным образом защитить от этого условия, если целевое распределение не известно, что метаданные присутствуют.

Зависимости дистрибутива

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

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

Distribution

Хотя вышеупомянутый 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.

Расширение алгоритма поиска

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

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

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

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

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

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

Сноски

[1]Технически возвращаемый объект распространения метаданных является экземпляром email.message.EmailMessage, но это деталь реализации, а не часть стабильного API. Для доступа к содержимому метаданных следует использовать только словарные методы и синтаксис.