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

importlib.metadata.md

429 строк · 33.8 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3/library/importlib.metadata.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `importlib.metadata` – Доступ к метаданным пакета89Добавлено в версии 3.8.1011Изменено в версии 3.10: `importlib.metadata` больше не является предварительным.1213**Исходный код:** [Lib/importlib/metadata/\_\_init\_\_.py](https://python-all.ru/src/3.14/Lib/importlib/metadata/__init__.py)1415`importlib.metadata` – это библиотека, предоставляющая доступ к метаданным установленного [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html), таким как его точки входа или имена верхнего уровня ([импортируемые пакеты](https://python-all.ru/3/library/importlib.metadata.html), модули, если таковые имеются). Построенная отчасти на системе импорта Python, эта библиотека предоставляет API точек входа и метаданных, которые ранее были доступны через ныне удалённый пакет `pkg_resources`. Вместе с [`importlib.resources`](https://python-all.ru/3/library/importlib.resources.html#module-importlib.resources) она заменяет `pkg_resources`.1617`importlib.metadata` работает со сторонними *дистрибутивными пакетами*, установленными в каталог `site-packages` Python с помощью таких инструментов, как [pip](https://python-all.ru/3/library/importlib.metadata.html). В частности, он работает с дистрибутивами, имеющими обнаруживаемые каталоги `dist-info` или `egg-info`, и метаданными, определёнными [спецификациями основных метаданных](https://python-all.ru/3/library/importlib.metadata.html).1819> **Важно**20>21> Они *не* обязательно эквивалентны или соответствуют 1:1 именам *импортируемых пакетов* верхнего уровня, которые можно импортировать в коде Python. Один *дистрибутивный пакет* может содержать несколько *импортируемых пакетов* (и отдельных модулей), а один *импортируемый пакет* верхнего уровня может соответствовать нескольким *дистрибутивным пакетам*, если он является пакетом пространства имён. Вы можете использовать [packages\_distributions()](https://python-all.ru/3/library/importlib.metadata.html#package-distributions), чтобы получить отображение между ними.2223По умолчанию метаданные дистрибутива могут находиться в файловой системе или в zip-архивах в [`sys.path`](https://python-all.ru/3/library/sys.html#sys.path). Благодаря механизму расширений метаданные могут находиться почти где угодно.2425> **См. также**26>27> **[https://importlib-metadata.readthedocs.io/](https://python-all.ru/3/library/importlib.metadata.html)**28>29> Документация для `importlib_metadata`, который предоставляет обратный порт `importlib.metadata`. Она включает [справочник API](https://python-all.ru/3/library/importlib.metadata.html) для классов и функций этого модуля, а также [руководство по миграции](https://python-all.ru/3/library/importlib.metadata.html) для существующих пользователей `pkg_resources`.3031## Обзор3233Предположим, вы хотите получить строку версии для [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html), установленного с помощью `pip`. Начнём с создания виртуального окружения и установки чего-нибудь в него:3435```console36$ python -m venv example37$ source example/bin/activate38(example) $ python -m pip install wheel39```4041Вы можете получить строку версии для `wheel`, выполнив следующее:4243```pycon44(example) $ python45>>> from importlib.metadata import version46>>> version('wheel')47'0.32.3'48```4950Вы также можете получить коллекцию точек входа, выбираемых по свойствам EntryPoint (обычно 'group' или 'name'), например `console_scripts`, `distutils.commands` и другие. Каждая группа содержит коллекцию объектов [EntryPoint](https://python-all.ru/3/library/importlib.metadata.html#entry-points).5152Вы можете получить [метаданные для дистрибутива](https://python-all.ru/3/library/importlib.metadata.html#metadata):5354```python55>>> list(metadata('wheel'))56['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']57```5859Вы также можете получить [номер версии дистрибутива](https://python-all.ru/3/library/importlib.metadata.html#version), перечислить его [составные файлы](https://python-all.ru/3/library/importlib.metadata.html#files) и получить список [зависимостей дистрибутива](https://python-all.ru/3/library/importlib.metadata.html#requirements).6061#### `exception importlib.metadata.PackageNotFoundError`6263Подкласс [`ModuleNotFoundError`](https://python-all.ru/3/library/exceptions.html#ModuleNotFoundError), вызываемый несколькими функциями в этом модуле при запросе дистрибутивного пакета, который не установлен в текущем окружении Python.6465## Функциональное API6667Этот пакет предоставляет следующие функции через свой публичный API.6869### Точки входа7071#### `importlib.metadata.entry_points(**select_params)`7273Возвращает экземпляр [`EntryPoints`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.EntryPoints), описывающий точки входа для текущего окружения. Любые заданные именованные параметры передаются методу `select()` для сравнения с атрибутами отдельных определений точек входа.7475Примечание: в настоящее время невозможно запрашивать точки входа на основе их атрибута `EntryPoint.dist` (поскольку разные экземпляры `Distribution` в настоящее время не сравниваются как равные, даже если они имеют одинаковые атрибуты)7677#### `class importlib.metadata.EntryPoints`7879Подробности о коллекции установленных точек входа.8081Также предоставляет атрибут `.groups`, который сообщает все идентифицированные группы точек входа, и атрибут `.names`, который сообщает все идентифицированные имена точек входа.8283#### `class importlib.metadata.EntryPoint`8485Подробности об установленной точке входа.8687Каждый экземпляр `EntryPoint` имеет атрибуты `.name`, `.group` и `.value` и метод `.load()` для разрешения значения. Также есть атрибуты `.module`, `.attr` и `.extras` для получения компонентов атрибута `.value` и `.dist` для получения информации о дистрибутивном пакете, который предоставляет точку входа.8889Запрос всех точек входа:9091```python92>>> eps = entry_points()93```9495Функция `entry_points()` возвращает объект `EntryPoints`, коллекцию всех объектов `EntryPoint` с атрибутами `names` и `groups` для удобства:9697```python98>>> sorted(eps.groups)99['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']100```101102`EntryPoints` имеет метод `select()` для выбора точек входа, соответствующих определённым свойствам. Выберите точки входа в группе `console_scripts`:103104```python105>>> scripts = eps.select(group='console_scripts')106```107108Аналогично, поскольку `entry_points()` передаёт ключевые аргументы в select:109110```python111>>> scripts = entry_points(group='console_scripts')112```113114Выбрать конкретный скрипт с именем «wheel» (находится в проекте wheel):115116```python117>>> 'wheel' in scripts.names118True119>>> wheel = scripts['wheel']120```121122Аналогично, запросить эту точку входа во время выбора:123124```python125>>> (wheel,) = entry_points(group='console_scripts', name='wheel')126>>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')127```128129Проверить разрешённую точку входа:130131```python132>>> wheel133EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')134>>> wheel.module135'wheel.cli'136>>> wheel.attr137'main'138>>> wheel.extras139[]140>>> main = wheel.load()141>>> main142<function main at 0x103528488>143```144145Значения `group` и `name` задаются автором пакета произвольно, и обычно клиенту требуется разрешить все точки входа для определённой группы. Дополнительные сведения о точках входа, их определении и использовании см. в [документации setuptools](https://python-all.ru/3/library/importlib.metadata.html).146147Изменено в версии 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](https://python-all.ru/3/library/importlib.metadata.html).148149Изменено в версии 3.13: Объекты `EntryPoint` больше не предоставляют кортежеподобный интерфейс ([`__getitem__()`](https://python-all.ru/3/reference/datamodel.html#object.__getitem__)).150151### Метаданные дистрибутива152153#### `importlib.metadata.metadata(distribution_name)`154155Возвращает метаданные дистрибутива для указанного пакета дистрибутива в виде экземпляра [`PackageMetadata`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageMetadata).156157Вызывает [`PackageNotFoundError`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageNotFoundError), если указанный пакет дистрибутива не установлен в текущем окружении Python.158159#### `class importlib.metadata.PackageMetadata`160161Конкретная реализация протокола [PackageMetadata](https://python-all.ru/3/library/importlib.metadata.html).162163В дополнение к предоставлению определённых методов и атрибутов протокола, обращение к экземпляру по индексу эквивалентно вызову метода `get()`.164165Каждый [пакет дистрибутива](https://python-all.ru/3/library/importlib.metadata.html) содержит метаданные, которые можно извлечь с помощью функции `metadata()`:166167```python168>>> wheel_metadata = metadata('wheel')169```170171Ключи возвращаемой структуры данных соответствуют ключевым словам метаданных, а значения возвращаются без разбора из метаданных дистрибутива:172173```python174>>> wheel_metadata['Requires-Python']175'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'176```177178[`PackageMetadata`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageMetadata) также предоставляет атрибут `json`, который возвращает все метаданные в JSON-совместимой форме согласно [**PEP 566**](https://python-all.ru/3/library/importlib.metadata.html):179180```python181>>> wheel_metadata.json['requires_python']182'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'183```184185Полный набор доступных метаданных здесь не описан. Дополнительные сведения см. в [спецификации основных метаданных PyPA](https://python-all.ru/3/library/importlib.metadata.html).186187Изменено в версии 3.10: Теперь `Description` включается в метаданные при представлении через полезную нагрузку (payload). Символы продолжения строки удалены.188189Добавлен атрибут `json`.190191### Версии дистрибутивов192193#### `importlib.metadata.version(distribution_name)`194195Возвращает [версию](https://python-all.ru/3/library/importlib.metadata.html) установленного пакета дистрибутива для указанного пакета дистрибутива.196197Вызывает [`PackageNotFoundError`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageNotFoundError), если указанный пакет дистрибутива не установлен в текущем окружении Python.198199Функция `version()` – это самый быстрый способ получить номер [версии пакета дистрибутива](https://python-all.ru/3/library/importlib.metadata.html) в виде строки:200201```python202>>> version('wheel')203'0.32.3'204```205206### Файлы дистрибутива207208#### `importlib.metadata.files(distribution_name)`209210Возвращает полный набор файлов, содержащихся в указанном пакете дистрибутива.211212Вызывает [`PackageNotFoundError`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageNotFoundError), если указанный пакет дистрибутива не установлен в текущем окружении Python.213214Возвращает [`None`](https://python-all.ru/3/library/constants.html#None), если дистрибутив найден, но в базе данных установки отсутствуют записи о файлах, связанных с пакетом дистрибутива.215216#### `class importlib.metadata.PackagePath`217218Объект, производный от [`pathlib.PurePath`](https://python-all.ru/3/library/pathlib.html#pathlib.PurePath), с дополнительными свойствами `dist`, `size` и `hash`, соответствующими метаданным установки пакета дистрибутива для этого файла.219220Функция `files()` принимает имя [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html) и возвращает все файлы, установленные этим дистрибутивом. Каждый файл представлен как экземпляр [`PackagePath`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackagePath). Например:221222```python223>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]224>>> util225PackagePath('wheel/util.py')226>>> util.size227859228>>> util.dist229<importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>230>>> util.hash231<FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>232```233234Получив файл, можно также прочитать его содержимое:235236```python237>>> print(util.read_text())238import base64239import sys240...241def as_bytes(s):242    if isinstance(s, text_type):243        return s.encode('utf-8')244    return s245```246247Также можно использовать метод `locate()`, чтобы получить абсолютный путь к файлу:248249```python250>>> util.locate()251PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')252```253254Если файл метаданных со списком файлов (`RECORD` или `SOURCES.txt`) отсутствует, `files()` вернёт [`None`](https://python-all.ru/3/library/constants.html#None). Вызывающий код может обернуть вызовы `files()` в [always\_iterable](https://python-all.ru/3/library/importlib.metadata.html) или иным образом защититься от такой ситуации, если неизвестно, что целевой дистрибутив содержит эти метаданные.255256### Зависимости дистрибутивов257258#### `importlib.metadata.requires(distribution_name)`259260Возвращает объявленные спецификаторы зависимостей для именованного дистрибутивного пакета.261262Вызывает [`PackageNotFoundError`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageNotFoundError), если именованный дистрибутивный пакет не установлен в текущем окружении Python.263264Чтобы получить полный набор зависимостей для [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html), используйте функцию `requires()`:265266```python267>>> requires('wheel')268["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]269```270271### Отображение импортов на дистрибутивы272273#### `importlib.metadata.packages_distributions()`274275Возвращает отображение имён модулей верхнего уровня и импортируемых пакетов, найденных с помощью [`sys.meta_path`](https://python-all.ru/3/library/sys.html#sys.meta_path), на имена дистрибутивных пакетов (если таковые есть), предоставляющих соответствующие файлы.276277Для поддержки пакетов пространств имён (которые могут содержать компоненты от нескольких дистрибутивов) каждое имя импорта верхнего уровня сопоставляется со списком имён дистрибутивов, а не напрямую с одним именем.278279Удобный метод для разрешения имени [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html) (или имён, в случае пакета пространства имён), который предоставляет каждый импортируемый модуль или [импортируемый пакет](https://python-all.ru/3/library/importlib.metadata.html) верхнего уровня:280281```python282>>> packages_distributions()283{'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}284```285286Некоторые редактируемые установки [не предоставляют имён верхнего уровня](https://python-all.ru/3/library/importlib.metadata.html), поэтому эта функция не является надёжной для таких установок.287288Добавлено в версии 3.10.289290## Дистрибутивы291292#### `importlib.metadata.distribution(distribution_name)`293294Возвращает экземпляр [`Distribution`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.Distribution), описывающий именованный дистрибутивный пакет.295296Вызывает [`PackageNotFoundError`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.PackageNotFoundError), если именованный дистрибутивный пакет не установлен в текущем окружении Python.297298#### `class importlib.metadata.Distribution`299300Сведения об установленном дистрибутивном пакете.301302Примечание: разные экземпляры `Distribution` в настоящее время не считаются равными, даже если они относятся к одному и тому же установленному дистрибутиву и, соответственно, имеют одинаковые атрибуты.303304Хотя описанный выше API на уровне модуля является наиболее распространённым и удобным, всю эту информацию можно получить из класса `Distribution`. `Distribution` – это абстрактный объект, представляющий метаданные [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html) Python. Конкретный экземпляр подкласса `Distribution` для установленного дистрибутивного пакета можно получить, вызвав функцию [`distribution()`](https://python-all.ru/3/library/importlib.metadata.html#importlib.metadata.distribution):305306```python307>>> from importlib.metadata import distribution308>>> dist = distribution('wheel')309>>> type(dist)310<class 'importlib.metadata.PathDistribution'>311```312313Таким образом, альтернативный способ получить номер версии – через экземпляр `Distribution`:314315```python316>>> dist.version317'0.32.3'318```319320На экземплярах `Distribution` доступны самые разные дополнительные метаданные:321322```python323>>> dist.metadata['Requires-Python']324'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'325>>> dist.metadata['License']326'MIT'327```328329For editable packages, an `origin` property may present [**PEP 610**](https://python-all.ru/3/library/importlib.metadata.html) metadata:330331```python332>>> dist.origin.url333'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'334```335336Полный набор доступных метаданных здесь не описывается. За дополнительными сведениями обратитесь к [спецификации основных метаданных PyPA](https://python-all.ru/3/library/importlib.metadata.html).337338Добавлено в версии 3.13: Добавлено свойство `.origin`.339340## Обнаружение дистрибутивов341342По умолчанию этот пакет предоставляет встроенную поддержку поиска метаданных для [дистрибутивных пакетов](https://python-all.ru/3/library/importlib.metadata.html) файловой системы и zip-файлов. Поиск метаданных по умолчанию использует `sys.path`, но трактует эти значения немного иначе, чем другие механизмы импорта. В частности:343344- `importlib.metadata` не учитывает объекты [`bytes`](https://python-all.ru/3/library/stdtypes.html#bytes) из `sys.path`.345- `importlib.metadata` случайно будет учитывать объекты [`pathlib.Path`](https://python-all.ru/3/library/pathlib.html#pathlib.Path) из `sys.path`, хотя такие значения игнорируются при импорте.346347## Реализация пользовательских провайдеров348349`importlib.metadata` охватывает два API: один для *потребителей*, а другой для *провайдеров*. Большинство пользователей являются потребителями, потребляя метаданные, предоставляемые пакетами. Однако есть и другие сценарии, в которых пользователи хотят предоставлять метаданные через какой-то другой механизм, например, вместе с пользовательским импортёром. Такой сценарий требует *пользовательского провайдера*.350351Поскольку метаданные [дистрибутивного пакета](https://python-all.ru/3/library/importlib.metadata.html) недоступны напрямую через поиск [`sys.path`](https://python-all.ru/3/library/sys.html#sys.path) или загрузчики пакетов, метаданные для дистрибутива находятся через [искатели](https://python-all.ru/3/reference/import.html#finders-and-loaders) системы импорта. Чтобы найти метаданные дистрибутивного пакета, `importlib.metadata` опрашивает список [искателей путей метаданных](https://python-all.ru/3/glossary.html#term-meta-path-finder) из [`sys.meta_path`](https://python-all.ru/3/library/sys.html#sys.meta_path).352353Реализация включает хуки, встроенные в `PathFinder`, которые предоставляют метаданные для дистрибутивных пакетов, найденных в файловой системе.354355Абстрактный класс [`importlib.abc.MetaPathFinder`](https://python-all.ru/3/library/importlib.html#importlib.abc.MetaPathFinder) определяет интерфейс, ожидаемый от искателей системой импорта Python. `importlib.metadata` расширяет этот протокол, проверяя наличие опционального вызываемого объекта `find_distributions` у искателей из [`sys.meta_path`](https://python-all.ru/3/library/sys.html#sys.meta_path), и предоставляет этот расширенный интерфейс как абстрактный базовый класс `DistributionFinder`, который определяет следующий абстрактный метод:356357```python358@abc.abstractmethod359def find_distributions(context=DistributionFinder.Context()) -> Iterable[Distribution]:360    """Возвращает итерируемый объект со всеми экземплярами Distribution, способными361    загружать метаданные для пакетов для указанного ``context``.362    """363```364365Объект `DistributionFinder.Context` предоставляет свойства `.path` и `.name`, указывающие путь для поиска и имя для сопоставления, и может предоставлять другой релевантный контекст, запрашиваемый потребителем.366367На практике, чтобы поддерживать поиск метаданных дистрибутивных пакетов в местах, отличных от файловой системы, создайте подкласс `Distribution` и реализуйте абстрактные методы. Затем из пользовательского искателя возвращайте экземпляры этого производного `Distribution` в методе `find_distributions()`.368369### Пример370371Представьте пользовательский искатель, который загружает модули Python из базы данных:372373```python374class DatabaseImporter(importlib.abc.MetaPathFinder):375    def __init__(self, db):376        self.db = db377378    def find_spec(self, fullname, target=None) -> ModuleSpec:379        return self.db.spec_from_name(fullname)380381sys.meta_path.append(DatabaseImporter(connect_db(...)))382```383384Этот импортёр предположительно предоставляет импортируемые модули из базы данных, но не предоставляет метаданные или точки входа. Чтобы этот пользовательский импортёр мог предоставлять метаданные, ему также необходимо реализовать `DistributionFinder`:385386```python387from importlib.metadata import DistributionFinder388389class DatabaseImporter(DistributionFinder):390    ...391392    def find_distributions(self, context=DistributionFinder.Context()):393        query = dict(name=context.name) if context.name else {}394        for dist_record in self.db.query_distributions(query):395            yield DatabaseDistribution(dist_record)396```397398Таким образом, `query_distributions` будет возвращать записи для каждого дистрибутива, обслуживаемого базой данных, которые соответствуют запросу. Например, если `requests-1.0` находится в базе данных, `find_distributions` вернёт `DatabaseDistribution` для `Context(name='requests')` или `Context(name=None)`.399400Ради простоты этот пример игнорирует `context.path`. Атрибут `path` по умолчанию равен `sys.path` и представляет собой набор путей импорта, которые следует учитывать при поиске. `DatabaseImporter` может потенциально функционировать без учёта пути поиска. Если предположить, что импортёр не выполняет разделение, «путь» не будет иметь значения. Чтобы проиллюстрировать назначение `path`, пример должен показать более сложный `DatabaseImporter`, поведение которого зависит от `sys.path`/`PYTHONPATH`. В таком случае `find_distributions` должен соблюдать `context.path` и возвращать только `Distribution`, относящиеся к этому пути.401402Тогда `DatabaseDistribution` будет выглядеть примерно так:403404```python405class DatabaseDistribution(importlib.metadata.Distribution):406    def __init__(self, record):407        self.record = record408409    def read_text(self, filename):410        """411        Читает файл, например "METADATA", для текущего Distribution.412        """413        if filename == "METADATA":414            return f"""Name: {self.record.name}415Version: {self.record.version}416"""417        if filename == "entry_points.txt":418            return "\n".join(419              f"""[{ep.group}]\n{ep.name}={ep.value}"""420              for ep in self.record.entry_points)421422    def locate_file(self, path):423        raise RuntimeError("This distribution has no file system")424```425426Эта базовая реализация должна предоставлять метаданные и точки входа для пакетов, обслуживаемых `DatabaseImporter`, при условии, что `record` предоставляет подходящие атрибуты `.name`, `.version` и `.entry_points`.427428`DatabaseDistribution` может также предоставлять другие файлы метаданных, например `RECORD` (требуется для `Distribution.files`), или переопределять реализацию `Distribution.files`. За дополнительными идеями обращайтесь к исходному коду.429