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

Добавлено в версии 3.8.

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

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

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

Под «установленным пакетом» обычно подразумевается пакет стороннего производителя, установленный в каталог site-packages Python с помощью таких инструментов, как 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.

Вы можете получить metadata for a distribution:

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

Вы также можете получить distribution’s version number, перечислить его constituent files и получить список Требования к распределению дистрибутива.

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

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

Точки входа

Функция 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 - это произвольные значения, определенные автором пакета, и обычно клиент хочет разрешить все точки входа для определенной группы. Читайте the setuptools docs для получения дополнительной информации о точках входа, их определении и использовании.

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

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

Метаданные распространения

Каждый дистрибутив содержит некоторые метаданные, которые можно извлечь с помощью функции 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 теперь включается в метаданные при представлении через полезную нагрузку. Символы продолжения строки были удалены.

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

Версии распространения

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

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

Файлы распределения

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

Требования к распределению

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

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

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

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

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

Добавлено в версии 3.10.

Распределения

Хотя приведенный выше 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, ни непосредственно через загрузчики пакетов, метаданные пакета находятся через систему импорта finders. Чтобы найти метаданные дистрибутивного пакета, importlib.metadata запрашивает список meta path finders на sys.meta_path.

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

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

@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()):
    """Return an iterable of all Distribution instances capable of
    loading the metadata for packages for the indicated ``context``.
    """

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

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

Вернуться на верх