importlib.metadata
– Доступ к метаданным пакета¶
Added in version 3.8.
Изменено в версии 3.10: importlib.metadata
больше не является предварительным.
Источник: Lib/importlib/metadata/__init__.py
importlib.metadata
- это библиотека, предоставляющая доступ к метаданным установленного Distribution Package, таким как точки входа или имена верхнего уровня (Import Packages, модули, если таковые имеются). Построенная частично на системе импорта Python, эта библиотека призвана заменить аналогичную функциональность в entry point API и metadata API из pkg_resources
. Вместе с importlib.resources
этот пакет может избавить от необходимости использовать более старый и менее эффективный пакет pkg_resources
.
importlib.metadata
работает со сторонними дистрибутивными пакетами, установленными в каталог site-packages
Python с помощью таких инструментов, как pip. В частности, он работает с дистрибутивами с обнаруживаемыми каталогами dist-info
или egg-info
и метаданными, определенными Core metadata specifications.
Важно
Они не обязательно эквивалентны или соответствуют 1:1 именам импортных пакетов верхнего уровня, которые могут быть импортированы в код Python. Один распределительный пакет может содержать несколько импортных пакетов (и отдельных модулей), а один импортный пакет верхнего уровня может быть сопоставлен с несколькими распределительными пакетами, если он является пакетом пространства имен. Вы можете использовать packages_distributions(), чтобы получить сопоставление между ними.
По умолчанию метаданные дистрибутива могут находиться в файловой системе или в zip-архивах на sys.path
. С помощью механизма расширения метаданные могут находиться практически в любом месте.
См.также
- https://importlib-metadata.readthedocs.io/
Документация для
importlib_metadata
, который является бэкпортомimportlib.metadata
. Она включает API reference для классов и функций этого модуля, а также migration guide для существующих пользователейpkg_resources
.
Обзор¶
Допустим, вы хотите получить строку версии для Distribution Package, который вы установили с помощью 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'
Вы также можете получить коллекцию точек входа, выбираемых по свойствам точки входа (обычно „group“ или „name“), таким как 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 для получения дополнительной информации о точках входа, их определении и использовании.
Изменено в версии 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__()
).
Метаданные о распространении¶
Каждый Distribution Package содержит некоторые метаданные, которые можно извлечь с помощью функции metadata()
:
>>> wheel_metadata = metadata('wheel')
Ключи возвращаемой структуры данных, a 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.*'
Примечание
Фактический тип объекта, возвращаемого metadata()
, является деталью реализации и должен быть доступен только через интерфейс, описанный PackageMetadata protocol.
Изменено в версии 3.10: Теперь символ Description
включается в метаданные при представлении через полезную нагрузку. Символы продолжения строки были удалены.
Был добавлен атрибут json
.
Версии распространения¶
Функция version()
- это самый быстрый способ получить номер версии Distribution Package в виде строки:
>>> version('wheel')
'0.32.3'
Файлы распространения¶
Вы также можете получить полный набор файлов, содержащихся в дистрибутиве. Функция files()
принимает имя Distribution Package и возвращает все файлы, установленные этим дистрибутивом. Каждый возвращаемый файловый объект - это 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 или иным образом защититься от этого условия, если неизвестно, что в целевом дистрибутиве присутствуют метаданные.
Требования к распределению¶
Чтобы получить полный набор требований для Distribution Package, используйте функцию requires()
:
>>> requires('wheel')
["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
Сопоставление импорта с дистрибутивными пакетами¶
Удобный метод для определения Distribution Package имени (или имен, в случае пакета пространства имен), которое предоставляет каждый импортируемый модуль Python верхнего уровня или Import Package:
>>> packages_distributions()
{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
Некоторые редактируемые установки - do not supply top-level names, и поэтому эта функция не работает с такими установками.
Added in version 3.10.
Распределения¶
Хотя приведенный выше API является наиболее распространенным и удобным вариантом использования, вы можете получить всю эту информацию из класса Distribution
. Класс Distribution
- это абстрактный объект, который представляет метаданные для Distribution Package в 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'
Для редактируемых пакетов свойство origin
может представлять метаданные PEP 610:
>>> dist.origin.url
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
Полный набор доступных метаданных здесь не описан. Дополнительные сведения см. в разделе Core metadata specifications.
Added in version 3.13: Было добавлено свойство .origin
.
Открытие дистрибуции¶
По умолчанию этот пакет обеспечивает встроенную поддержку поиска метаданных для файловой системы и zip-файлов Distribution Package. Этот поиск метаданных по умолчанию принимает значение sys.path
, но немного отличается в интерпретации этих значений от того, как это делают другие механизмы импорта. В частности:
importlib.metadata
не уважает объектыbytes
наsys.path
.importlib.metadata
будет случайно учитывать объектыpathlib.Path
наsys.path
, хотя такие значения будут игнорироваться при импорте.
Расширение алгоритма поиска¶
Поскольку метаданные Distribution Package недоступны ни через поиск sys.path
, ни через загрузчики пакетов напрямую, метаданные дистрибутива находятся через систему импорта finders. Чтобы найти метаданные дистрибутива, importlib.metadata
запрашивает список meta path finders на sys.meta_path
.
По умолчанию importlib.metadata
устанавливает программу поиска дистрибутивов, найденных в файловой системе. Этот искатель не находит дистрибутивы, но может найти их метаданные.
Абстрактный класс importlib.abc.MetaPathFinder
определяет интерфейс, ожидаемый от finders системой импорта Python. importlib.metadata
расширяет этот протокол, ища необязательный find_distributions
вызываемый на 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()
.
Пример¶
Рассмотрим, например, пользовательский поисковик, который загружает модули 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.Distributon):
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
. Смотрите источник для большего вдохновения.