Spec-Zone.ru › Python 3.13

importlib.metadata — доступ к метаданным пакетов

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

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

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

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

importlib.metadata работает со сторонними дистрибутивными пакетами, установленными в каталог site-packages Python с помощью таких инструментов, как pip. В частности, он работает с дистрибутивами, имеющими доступные для обнаружения каталоги dist-info или egg-info, и метаданными, определёнными в спецификациях основных метаданных.

Важно

Они не обязательно эквивалентны именам пакетов импорта верхнего уровня, которые можно импортировать в коде Python, и не всегда соответствуют им один к одному. Один дистрибутивный пакет может содержать несколько пакетов импорта (и отдельные модули), а один пакет импорта верхнего уровня может соответствовать нескольким дистрибутивным пакетам, если он является пакетом пространства имён. Чтобы получить соответствие между ними, используйте packages_distributions().

По умолчанию метаданные дистрибутивов могут храниться в файловой системе или в ZIP-архивах в sys.path. Благодаря механизму расширения метаданные могут храниться практически где угодно.

См. также

https://importlib-metadata.readthedocs.io/

Документация к importlib_metadata, предоставляющему обратный перенос importlib.metadata. Она включает справочник по API классов и функций этого модуля, а также руководство по миграции для существующих пользователей pkg_resources.

Обзор

Предположим, вы хотите получить строку версии дистрибутивного пакета, установленного с помощью 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.

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

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

Точки входа

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: «Выбираемые» точки входа появились в 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__()).

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

importlib.metadata.metadata(distribution_name)

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

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

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

Добавлен атрибут json.

Версии дистрибутивов

importlib.metadata.version(distribution_name)

Возвращает версию установленного дистрибутивного пакета с указанным именем.

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

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

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

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

importlib.metadata.files(distribution_name)

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

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

Возвращает 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 или иным способом предусмотреть это условие.

Требования дистрибутива

importlib.metadata.requires(distribution_name)

Возвращает объявленные спецификаторы зависимостей для указанного дистрибутивного пакета.

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

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

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

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

importlib.metadata.packages_distributions()

Возвращает соответствие имён модулей и пакетов импорта верхнего уровня, обнаруженных через sys.meta_path, именам дистрибутивных пакетов (если таковые имеются), предоставляющих соответствующие файлы.

Чтобы поддерживать пакеты пространств имён (чьи компоненты могут предоставляться несколькими дистрибутивными пакетами), каждому имени импорта верхнего уровня сопоставляется список имён дистрибутивов, а не одно имя.

Вспомогательный метод для определения имени дистрибутивного пакета (или имён, если это пакет пространства имён), предоставляющего каждый импортируемый модуль Python или пакет импорта верхнего уровня:

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

Некоторые редактируемые установки не предоставляют имена верхнего уровня, поэтому эта функция ненадёжна при работе с такими установками.

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

Дистрибутивы

importlib.metadata.distribution(distribution_name)

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

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

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'

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

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

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

Добавлено в версии 3.13: Добавлено свойство .origin.

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

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

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

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

Поскольку метаданные дистрибутивных пакетов недоступны при поиске через sys.path или напрямую через загрузчики пакетов, метаданные дистрибутива ищутся с помощью поисковых механизмов системы импорта. Чтобы найти метаданные дистрибутивного пакета, importlib.metadata опрашивает список поисковых механизмов meta path в 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()):
    """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().

Пример

Рассмотрим, например, пользовательский поисковый механизм, который загружает модули 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):
        """
        Read a file like "METADATA" for the current 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. Дополнительные идеи можно найти в исходном коде.

© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/library/importlib.metadata.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API