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

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

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

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

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

importlib.metadata – это библиотека, предоставляющая доступ к метаданным установленного дистрибутивного пакета, таким как его точки входа или имена верхнего уровня (импортируемые пакеты, модули, если таковые имеются). Построенная отчасти на системе импорта Python, эта библиотека предоставляет 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.

exception importlib.metadata.MetadataNotFound

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

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

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

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

importlib.metadata.entry_points(**select_params)

Возвращает экземпляр EntryPoints, описывающий точки входа для текущего окружения. Любые заданные именованные параметры передаются методу select() для сравнения с атрибутами отдельных определений точек входа.

Примечание: для запроса точек входа на основе атрибута EntryPoint.dist следует использовать Distribution.entry_points() (поскольку разные экземпляры 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.

Возбуждает MetadataNotFound, если дистрибутивный пакет присутствует, но файл METADATA отсутствует.

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.15: Ранее, если файл METADATA отсутствовал в дистрибутиве, возвращался пустой PackageMetadata, неотличимый от пустого файла METADATA. Теперь отсутствующий файл METADATA вызывает исключение MetadataNotFound.

Изменено в версии 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)

Возвращает полный набор файлов, содержащихся в указанном дистрибутивном пакете, в виде экземпляров PackagePath.

Вызывает PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.

Возвращает None, если дистрибутив найден, но в базе данных установки отсутствуют записи о файлах, связанных с пакетом дистрибутива.

class importlib.metadata.PackagePath

Объект, производный от pathlib.PurePath, с дополнительными свойствами dist, size и hash, соответствующими метаданным установки дистрибутивного пакета для этого файла, а также:

locate()

Если возможно, возвращает конкретный SimplePath, позволяющий получить доступ к данным, иначе возбуждает NotImplementedError.

class importlib.metadata.SimplePath

Протокол, представляющий минимальное подмножество pathlib.Path, которое позволяет проверить, exists() ли он, выполнять обход с помощью joinpath() и parent, а также получать данные с помощью read_text() и read_bytes().

Функция files() принимает имя дистрибутивного пакета и возвращает все файлы, установленные этим дистрибутивом. Например:

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

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

>>> from importlib.metadata import distribution
>>> dist = distribution('wheel')
>>> type(dist)
<class 'importlib.metadata.PathDistribution'>
importlib.metadata.distribution(distribution_name)

Возвращает экземпляр Distribution, описывающий именованный дистрибутивный пакет.

Вызывает PackageNotFoundError, если именованный дистрибутивный пакет не установлен в текущем окружении Python.

Таким образом, альтернативный способ получить, например, номер версии – через атрибут Distribution.version:

>>> dist.version
'0.32.3'

То же самое относится к entry_points() и files().

class importlib.metadata.Distribution

Сведения об установленном дистрибутивном пакете.

Примечание: разные экземпляры Distribution в настоящее время не считаются равными, даже если они относятся к одному и тому же установленному дистрибутиву и, соответственно, имеют одинаковые атрибуты.

static at(path)
classmethod from_name(name)

Возвращает экземпляр Distribution по указанному пути или с указанным именем.

classmethod discover(*, context=None, **kwargs)

Возвращает итерируемый объект с экземплярами Distribution для всех пакетов (см. distribution-discovery).

Необязательный аргумент context – это экземпляр DistributionFinder.Context, используемый для изменения поиска дистрибутивов. Альтернативно, kwargs может содержать именованные аргументы для создания нового DistributionFinder.Context.

metadata: PackageMetadata

Вызывает MetadataNotFound, если файл METADATA отсутствует в дистрибутиве.

На экземплярах Distribution доступны разнообразные дополнительные метаданные в виде экземпляра PackageMetadata:

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

Полный набор доступных метаданных здесь не описывается. За дополнительными сведениями обратитесь к спецификации основных метаданных PyPA.

name: str
requires: list[str]
version: str

Некоторые поля метаданных также доступны как сокращённые свойства.

Добавлено в версии 3.10: Было добавлено сокращённое свойство name.

origin

Для редактируемых пакетов свойство origin может содержать метаданные PEP 610 (для нередактируемых пакетов origin равно None):

>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'

Объект origin соответствует структуре данных прямого URL.

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

entry_points: EntryPoints

Точки входа, предоставляемые этим пакетом дистрибутива.

files: list[PackagePath] | None

Все файлы, содержащиеся в этом пакете дистрибутива. Как и files(), возвращает None, если записи отсутствуют.

Следующие два абстрактных метода необходимо реализовать при реализации пользовательских провайдеров:

locate_file(path)

Как и PackagePath.locate(), возвращает SimplePath для указанного пути. Принимает os.PathLike или str.

read_text(filename)

Сокращённая запись для distribution.locate_file(filename).read_text().

Обнаружение дистрибутивовDistribution Discovery

По умолчанию этот пакет предоставляет встроенную поддержку поиска метаданных для дистрибутивных пакетов файловой системы и zip-файлов. Поиск метаданных по умолчанию использует sys.path, но трактует эти значения немного иначе, чем другие механизмы импорта. В частности:

  • importlib.metadata не учитывает объекты bytes из sys.path.

  • importlib.metadata случайно будет учитывать объекты pathlib.Path из sys.path, хотя такие значения игнорируются при импорте.

class importlib.metadata.DistributionFinder

Подкласс MetaPathFinder, способный обнаруживать установленные дистрибутивы.

Пользовательские провайдеры должны реализовать этот интерфейс для предоставления метаданных.

class Context(**kwargs)

Context даёт пользовательскому провайдеру возможность запрашивать дополнительные сведения у вызывающих сторон функций поиска дистрибутивов, таких как distributions() или Distribution.discover(), помимо .name и .path при поиске дистрибутивов.

Например, провайдер может предоставлять наборы пакетов в «публичном» или «частном» realm. Вызывающая сторона функций поиска дистрибутивов может захотеть запросить только дистрибутивы в определённой области и может вызвать distributions(realm="private"), чтобы указать пользовательскому провайдеру включать только дистрибутивы из этой области.

Каждый DistributionFinder должен принимать любые параметры и по возможности стараться соблюдать канонические параметры, определённые ниже.

См. раздел Реализация пользовательских провайдеров для получения дополнительных сведений.

name

Конкретное имя, которому должен соответствовать искатель дистрибутивов.

.name со значением None соответствует всем дистрибутивам.

path

Свойство, предоставляющее последовательность путей к каталогам, которые должен просматривать поисковик дистрибутивов.

Обычно относится к путям установленных пакетов Python, например, к каталогам site-packages, и по умолчанию равно sys.path.

importlib.metadata.distributions(**kwargs)

Возвращает итерируемый объект с экземплярами Distribution для всех пакетов.

Аргумент kwargs может содержать именованный аргумент context, экземпляр DistributionFinder.Context или ключевые аргументы для создания нового DistributionFinder.Context. DistributionFinder.Context используется для изменения поиска дистрибутивов.

Реализация пользовательских провайдеровImplementing Custom Providers

importlib.metadata охватывает два API: один для потребителей, а другой для провайдеров. Большинство пользователей являются потребителями, потребляя метаданные, предоставляемые пакетами. Однако есть и другие сценарии, в которых пользователи хотят предоставлять метаданные через какой-то другой механизм, например, вместе с пользовательским импортёром. Такой сценарий требует пользовательского провайдера.

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

Реализация включает хуки, встроенные в PathFinder, которые предоставляют метаданные для дистрибутивных пакетов, найденных в файловой системе.

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

@abc.abstractmethod
def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:
    """Возвращает итерируемый объект со всеми экземплярами 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. За дополнительными идеями обращайтесь к исходному коду.