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

importlib.metadata – Доступ к метаданным пакетаimportlib.metadata – Accessing package metadata

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

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

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

importlib.metadata – библиотека, предоставляющая доступ к метаданным установленного дистрибутивного пакета (Distribution Package), например, к его точкам входа (entry points) или именам верхнего уровня (импортируемые пакеты (Import Package), модули, если они есть). Основанная на системе импорта Python, эта библиотека призвана заменить аналогичную функциональность из API точек входа и 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.

Функциональное APIFunctional 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, хотя такие значения игнорируются при импорте.

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

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

По умолчанию importlib.metadata устанавливает находильщик для дистрибутивных пакетов, найденных в файловой системе. Этот находильщик на самом деле не находит никакие дистрибутивы, но может находить их метаданные.

Абстрактный класс 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().

Пример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. За дополнительными идеями обращайтесь к исходному коду.