Применение 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 сущность:

>>> d.metadata['Requires-Python']  
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
>>> d.metadata['License']  
'MIT'

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

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

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

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

Резюме class: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.Message сущностью, но это деталь реализации, а не часть стабильного API. Для доступа к содержимому метаданных следует использовать только словарные методы и синтаксис.