importlib.metadata – Доступ к метаданным пакетов
Добавлено в версии 3.8.
Изменено в версии 3.10: importlib.metadata больше не является предварительной версией.
Исходный код: Lib/importlib/metadata/__init__.py
importlib.metadata — это библиотека, предоставляющая доступ к метаданным установленного дистрибутивного пакета, например к его точкам входа или именам верхнего уровня (пакетам импорта, модулям, если они есть). Эта библиотека, частично основанная на системе импорта Python, предоставляет API точек входа и метаданных, которые ранее предоставлялись пакетом pkg_resources, впоследствии удалённым. Вместе с importlib.resources она заменяет pkg_resources.
importlib.metadata работает со сторонними дистрибутивными пакетами, установленными в каталог Python site-packages с помощью таких инструментов, как 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.Возбуждает исключение
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 включается в метаданные при их представлении через полезную нагрузку. Символы продолжения строки удалены.
Добавлен атрибут json.
Версии дистрибутивов
-
importlib.metadata.version(distribution_name) -
Возвращает версию установленного дистрибутивного пакета с указанным именем.
Возбуждает исключение
PackageNotFoundError, если указанный дистрибутивный пакет не установлен в текущем окружении Python.
Функция version() — самый быстрый способ получить номер версии дистрибутивного пакета в виде строки:
>>> version('wheel')
'0.32.3'
Файлы дистрибутива
-
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 или иным образом предусмотреть эту ситуацию.
Требования дистрибутива
-
importlib.metadata.requires(distribution_name) -
Возвращает объявленные спецификаторы зависимостей для указанного дистрибутивного пакета.
Возбуждает исключение
PackageNotFoundError, если указанный дистрибутивный пакет не установлен в текущем окружении Python.
Чтобы получить полный набор требований дистрибутивного пакета, используйте функцию 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, описывающий указанный дистрибутивный пакет.Возбуждает исключение
PackageNotFoundError, если указанный дистрибутивный пакет не установлен в текущем окружении Python.
-
class importlib.metadata.Distribution -
Сведения об установленном дистрибутивном пакете.
Примечание: разные экземпляры
Distributionв настоящее время не считаются равными, даже если относятся к одному установленному дистрибутиву и, соответственно, имеют одинаковые атрибуты.
Хотя описанный выше API уровня модуля является наиболее распространённым и удобным способом использования, всю эту информацию можно получить и с помощью класса Distribution. Distribution — это абстрактный объект, представляющий метаданные дистрибутивного пакета Python Distribution Package. Получить экземпляр конкретного подкласса 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.
Обнаружение дистрибутивов
По умолчанию этот пакет поддерживает обнаружение метаданных дистрибутивных пакетов Distribution Package в файловой системе и ZIP-файлах. По умолчанию этот поиск метаданных выполняется по sys.path, однако интерпретация этих значений несколько отличается от интерпретации другими механизмами импорта. В частности:
-
importlib.metadataне учитывает объектыbytesвsys.path. -
importlib.metadataучитывает объектыpathlib.Pathвsys.path, хотя при импорте такие значения игнорируются.
Реализация пользовательских поставщиков
importlib.metadata предоставляют два уровня API: один для потребителей, другой для поставщиков. Большинство пользователей являются потребителями и используют метаданные, предоставленные пакетами. Однако существуют и другие сценарии, в которых пользователям требуется предоставлять метаданные через иной механизм, например вместе с пользовательским импортёром. В таком случае нужен пользовательский поставщик.
Поскольку метаданные дистрибутивного пакета недоступны через поиск в sys.path или непосредственно через загрузчики пакетов, метаданные дистрибутива ищутся с помощью поисковых механизмов системы импорта. Чтобы найти метаданные дистрибутивного пакета, importlib.metadata запрашивает список поисковых механизмов meta path в 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]:
"""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 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/library/importlib.metadata.html