Управление зависимостями в Python с Poetry
Стандартный инструмент для установки пакетов в Python - pip - прекрасно справляется с базовыми сценариями: установил пакет командой pip install и используешь его. Но что произойдёт, когда разные проекты требуют разных версий одной и той же библиотеки? Или когда библиотека A требует версию 1.0 пакета C, а библиотека B - версию 2.0 того же пакета? Ситуация усложняется многократно, когда речь идёт о командной разработке, где каждый участник должен воспроизвести идентичное окружение. Традиционно мы обходили эти сложности с помощью виртуальных окружений и файлов requirements.txt. Но этот подход имеет свои минусы: requirements.txt часто содержит нелогичный список всех установленных пакетов, без разделения на прямые и транзитивные зависимости. Кроме того, такой файл не хранит информацию о допустимых диапазонах версий, что затрудняет обновление пакетов без риска сломать проект. Не меньшую проблему представляет и сам процесс создания пакетов Python. Исторически для этого использовались файлы setup.py, работа с которыми требовала знания различных нюансов системы распространения пакетов. Простая задача публикации собственного пакета превращалась в квест по настройке множества параметров.В попытках решить эти проблемы появлялись различные инструменты: virtualenv, pipenv, conda и другие. Каждый из них решал часть проблем, но ни один не предлагал комплексного подхода к управлению зависимостями и упаковке проектов. Появившись относительно недавно, Poetry быстро завоевал популярность благодаря элегантному подходу к управлению зависимостями. Он объединяет лучшие практики из мира Node.js (npm, yarn) и Ruby (bundler), адаптируя их под особенности экосистемы Python. Философия Poetry заключается в том, чтобы сделать управление зависимостями в Python таким же простым и предсказуемым, каким оно должно быть. Вместо множества разрозненных инструментов Poetry предлагает единое решение для создания проектов, управления зависимостями, сборки и публикации пакетов. При этом все конфигурации хранятся в едином файле pyproject.toml, что соответствует современным стандартам Python (PEP 518, PEP 621). Ключевая особенность Poetry - его система разрешения зависимостей. В отличие от pip, который может устанавливать несовместимые версии пакетов, Poetry анализирует все зависимости, включая транзитивные, и находит комбинацию версий, удовлетворяющую всем требованиям. Эта информация фиксируется в файле poetry.lock, гарантируя, что все разработчики используют идентичные версии всех пакетов. Установка и настройка PoetryПрежде чем погрузиться в мир Poetry, необходимо правильно установить и настроить этот инструмент. В отличие от большинства Python-пакетов, Poetry не рекомендуется устанавливать с помощью pip непосредственно в окружение проекта. Причина проста: Poetry сам управляет зависимостями проекта, и установка его в то же окружение может привести к конфликтам. Существует несколько способов установки Poetry, каждый из которых имеет свои преимущества: 1. Официальный установщик - наиболее рекомендуемый способ. Для Windows можно использовать PowerShell:
Poetry (version 1.7.1).Для обеспечения изоляции Poetry от системного Python и предотвращения конфликтов зависимостей, инструмент по умолчанию создает виртуальные окружения в отдельном каталоге. Местоположение этого каталога зависит от операционной системы: Windows: C:\Users\<username>\AppData\Local\pypoetry\CachemacOS: /Users/<username>/Library/Caches/pypoetryLinux: /home/<username>/.cache/pypoetryМожно изменить это поведение с помощью конфигурации. Poetry предлагает гибкие возможности настройки через команду poetry config. Например, чтобы увидеть текущие настройки:
virtualenvs.path - изменяет местоположение папки с виртуальными окружениями,cache-dir - указывает директорию для кэширования пакетов,repositories - позволяет добавлять пользовательские репозитории пакетов.Для работы в корпоративной среде с приватными репозиториями можно настроить Poetry на использование внутренних источников пакетов:
~/.config/pypoetry/config.toml (на Unix-системах) или %APPDATA%\pypoetry\config.toml (на Windows). Этот файл использует формат TOML, который значительно улучшает читаемость по сравнению с традиционными форматами конфигурации.При обновлении Poetry важно учитывать совместимость с существующими проектами. Наиболее безопасный способ обновления зависит от того, как был установлен инструмент:
Важный момент при работе с Poetry - его взаимодействие с различными версиями Python. По умолчанию Poetry использует ту версию Python, с которой он был установлен. Однако для конкретного проекта может потребоваться другая версия интерпретатора. Это легко настраивается с помощью команды:
Pipenv vs Poetry vs Pyenv Распространение бинарных библиотек с зависимостями Как сделать модель с уникальными зависимостями? Через requirements установка либы с зависимостями Основной рабочий процессПосле успешной установки Poetry пора познакомиться с основным рабочим процессом. Создание нового проекта с Poetry существенно отличается от традиционного подхода в Python. Вместо ручного создания необходимых файлов и папок, Poetry предлагает готовые шаблоны, существенно упрощающие старт. Для создания нового проекта используется команда poetry new:
src/, можно использовать флаг --src:
pyproject.toml. Это современная замена традиционным setup.py и requirements.txt. Базовая структура этого файла выглядит примерно так:
[tool.poetry] содержит метаданные проекта, такие как название, версия, описание и авторы. Эта информация используется при публикации пакета в PyPI. В секции [tool.poetry.dependencies] перечисляются зависимости проекта. По умолчанию там есть только требование к версии Python, определенное на основе интерпретатора, который использовался при установке Poetry. Для добавления зависимостей используется команда poetry add:
1. Обновляет pyproject.toml, добавляя новую зависимость.2. Анализирует совместимость с существующими зависимостями. 3. Устанавливает пакет и его зависимости в виртуальное окружение. 4. Обновляет файл poetry.lock с точными версиями всех пакетов.В результате в pyproject.toml появится новая строка:
^ перед версией указывает, что можно использовать любую совместимую версию, начиная с указанной и до следующей мажорной версии. Это соответствует семантическому версионированию, где изменения мажорной версии могут содержать несовместимые изменения API. Если требуется конкретная версия пакета, её можно указать:
Одним из главных преимуществ Poetry является интеллектуальное разрешение зависимостей. При добавлении нового пакета Poetry проверяет, совместимы ли его требования с уже установленными зависимостями. Если обнаружен конфликт, Poetry попытается найти набор версий, удовлетворяющий всем ограничениям, или выдаст понятную ошибку, если такого набора не существует. Результат разрешения зависимостей сохраняется в файле poetry.lock. Этот файл содержит точные версии всех пакетов, включая транзитивные зависимости, а также их контрольные суммы. Наличие lock-файла гарантирует, что все разработчики проекта получат идентичное окружение вне зависимости от того, когда и где они устанавливают зависимости.Для установки зависимостей из существующего проекта используется команда:
poetry.lock, то будут установлены точно те версии, которые указаны в нём. Если такого файла нет, Poetry сначала разрешит зависимости на основе pyproject.toml и создаст poetry.lock. Poetry позволяет группировать зависимости по их назначению. Например, можно выделить зависимости для разработки, тестирования или документации:
pyproject.toml:
poetry install устанавливаются все группы зависимостей. Но можно установить только определенные группы:
pyproject.toml:
pyproject.toml, и обновит файл poetry.lock. Можно обновить только конкретные пакеты:
requirements.txt, например, для CI/CD систем, не поддерживающих Poetry, можно использовать плагин export:
pyproject.toml версия Python обычно указывается с использованием синтаксиса ограничений:
poetry run:
При работе с существующими проектами, которые не были инициализированы с помощью Poetry, можно преобразовать их:
pyproject.toml. После этого можно импортировать существующие зависимости из requirements.txt:
Если проект уже настроен с использованием Poetry, к нему можно легко присоединиться. Достаточно клонировать репозиторий и выполнить:
poetry.lock будут установлены точно те же версии пакетов, что и у других участников проекта, обеспечивая полную воспроизводимость окружения.Часто возникает необходимость выяснить, какие именно версии пакетов установлены или доступны. Poetry предоставляет команду show для изучения зависимостей:
Одна из мощных возможностей Poetry – синхронизация окружения с зависимостями, объявленными в проекте. Со временем в виртуальном окружении могут накапливаться пакеты, которые больше не нужны. Команда:
pyproject.toml, обеспечивая чистоту окружения.Иногда при работе с крупными проектами может потребоваться блокировка зависимостей без их установки. Это полезно в CI/CD пайплайнах или при подготовке релиза:
poetry.lock, но не будет устанавливать пакеты.Для тонкой настройки процесса разрешения зависимостей Poetry предлагает параметр solver-strategy в настройках, который определяет, как именно будут разрешаться конфликты версий:
conservative предпочитает оставлять существующие зависимости неизменными минимизируя количество обновлений.При создании библиотеки важно правильно определить её API. Poetry помогает в этом, позволяя указать публичные интерфейсы:
scripts определяет консольные команды, которые будут созданы при установке пакета. Это удобный способ предоставить пользователям инструменты командной строки. Секция plugins позволяет определить [точки расширения](https://packaging.python.org/g... g-plugins/) – механизм, через который другие пакеты могут расширять функциональность вашей библиотеки.Для проверки правильности настройки проекта Poetry предоставляет команду:
pyproject.toml на наличие синтаксических ошибок и несоответствий.Отдельного внимания заслуживает работа с приватными репозиториями. Если ваш проект зависит от пакетов, которые не опубликованы в публичных индексах, можно настроить дополнительные источники:
При разработке, особенно в командных проектах, крайне важно правильно управлять файлом poetry.lock. Этот файл фиксирует не только прямые зависимости, но и их транзитивные зависимости с точными версиями и хешами. Вопрос о том, следует ли добавлять этот файл в систему контроля версий, зависит от типа вашего проекта. Для приложений настоятельно рекомендуется фиксировать poetry.lock в репозитории. Это гарантирует, что все разработчики и среды развертывания будут использовать идентичные версии зависимостей, что снижает риск проблемы "у меня работает, а у тебя нет". Для библиотек ситуация иная. Поскольку библиотеки должны быть совместимы с различными окружениями, жесткая фиксация версий может создать проблемы. Обычно для библиотек рекомендуется добавлять poetry.lock в .gitignore.При работе с ограничениями версий в pyproject.toml полезно понимать синтаксис Poetry:^1.2.3 – совместимо с >=1.2.3,<2.0.0 (обновления в рамках мажорной версии),~1.2.3 – совместимо с >=1.2.3,<1.3.0 (обновления только в патч-версии),>=1.2.3,<1.5 – явный диапазон версий,* – любая версия,1.2.3 или ==1.2.3 – только конкретная версия.Указание слишком строгих ограничений может привести к "dependency hell" (аду зависимостей), когда разные пакеты требуют несовместимые версии одной библиотеки. С другой стороны, слишком свободные ограничения могут привести к непредсказуемым поломкам при появлении новых версий. Золотое правило – указывать минимально необходимую версию и максимальную проверенную:
pyproject.toml:
develop = true устанавливает пакет в режиме разработки (как pip install -e), что позволяет изменениям в исходном коде немедленно отражаться без переустановки.Для пакетов, находящихся в разработке или не опубликованных в PyPI, можно использовать Git-репозитории:
poetry add используется алгоритм, который учитывает все ограничения версий и находит оптимальный набор пакетов. Этот процесс может занять некоторое время для сложных проектов с большим количеством зависимостей, но результат стоит ожидания – надежная и воспроизводимая среда разработки.Продвинутые возможностиPoetry не ограничивается базовым управлением зависимостями — это многогранный инструмент с богатым набором продвинутых функций. Когда вы освоите основы, стоит погрузиться глубже, чтобы раскрыть полный потенциал этого инструмента. Работа с виртуальными окружениямиХотя мы уже затрагивали тему виртуальных окружений, стоит глубже рассмотреть их особенности в Poetry. По умолчанию Poetry создаёт виртуальные окружения в системном кэше, но существуют дополнительные возможности управления:
Публикация пакетовPoetry значительно упрощает публикацию пакетов в PyPI или других репозиториях. Базовый процесс выглядит так:
build создаёт дистрибутивы пакета в формате wheel и tar.gz в директории dist/. Команда publish отправляет эти дистрибутивы в репозиторий пакетов.Если вам нужно опубликовать пакет в тестовом PyPI или приватном репозитории:
Кэширование зависимостей и оптимизация сборкиPoetry кэширует установленные пакеты, что значительно ускоряет повторные установки. По умолчанию кэш находится в системной директории, но его местоположение можно изменить:
poetry.lock, что гарантирует целостность устанавливаемых пакетов:
--no-dev при установке в продакшн-окружении, чтобы пропустить зависимости разработки:
Стратегии оптимизации времени установки зависимостейРешение задачи удовлетворения всех зависимостей иногда может занимать много времени, особенно в крупных проектах. Существуют стратегии оптимизации: 1. Предварительное разрешение зависимостей: Выполните poetry lock на мощной машине и зафиксируйте poetry.lock в репозитории.2. Параллельная установка: Используйте флаг --parallel для одновременной установки нескольких пакетов:
4. Избегайте избыточности: Регулярно проверяйте и чистите неиспользуемые зависимости:
Использование Poetry в контейнерах DockerИнтеграция Poetry в Docker-образы требует особого подхода. Вот оптимальный шаблон Dockerfile:
virtualenvs.create false отключает создание виртуального окружения, поскольку Docker-контейнер уже обеспечивает изоляцию.Для оптимизации кэширования слоёв Docker, разделите копирование файлов:
Интеграция с CI/CDPoetry прекрасно подходит для использования в непрерывной интеграции. Пример конфигурации для GitHub Actions:
Для GitLab CI/CD можно использовать похожий подход:
Расширение функциональности Poetry через плагиныНачиная с версии 1.2.0, Poetry поддерживает систему плагинов, позволяющую расширять его функциональность. Некоторые полезные плагины: 1. poetry-plugin-export: Экспорт зависимостей в формат requirements.txt:
pyproject.toml:
Использование Poetry с монорепозиториямиМонорепозитории, содержащие несколько связанных проектов, становятся все более популярными. Poetry предлагает несколько подходов к работе с ними: 1. Ссылки на локальные пакеты:
develop = true означает, что пакет будет установлен в режиме разработки, и изменения в исходном коде будут сразу видны без переустановки.2. Использование инструментов управления монорепозиториями таких как Pants, Bazel или Nx вместе с Poetry для каждого подпроекта. 3. Создание метапакета для управления всеми подпроектами:
Продвинутое управление версиямиПри работе над библиотекой важно правильно управлять версиями. Poetry предоставляет команду version, которая помогает автоматизировать этот процесс:
Тонкая настройка виртуальных окруженийХотя основы работы с виртуальными окружениями были рассмотрены, есть несколько дополнительных параметров конфигурации:
Безопасность зависимостейС ростом числа атак на цепочки поставок программного обеспечения, безопасность зависимостей становится критически важной. Poetry предлагает несколько механизмов для минимизации рисков: 1. Верификация хэшей: Poetry автоматически проверяет хэши загруженных пакетов против значений в poetry.lock, что защищает от подмены пакетов.2. Интеграция с инструментами аудита безопасности: Можно использовать инструменты вроде Safety или Snyk в сочетании с Poetry:
4. Ограничение транзитивных зависимостей: Можно использовать технику "dependency pinning" для явного контроля над всеми зависимостями:
poetry audit (через плагин) помогает выявлять проблемные зависимости.Отладка проблем с зависимостямиПри возникновении проблем с разрешением зависимостей Poetry предлагает несколько инструментов для диагностики:
-v (verbose) показывает больше информации о процессе установки, а -vvv выводит максимально детальные логи, включая информацию о процессе разрешения зависимостей.Если возникают конфликты, которые Poetry не может разрешить автоматически, команда show поможет понять, какие пакеты их вызывают:
Обработка данных и ресурсов проектаПри создании пакетов часто требуется включать неисполняемые файлы — конфигурации, шаблоны, данные. Poetry позволяет указать включаемые файлы в pyproject.toml:
importlib.resources (в Python 3.7+) или пакет importlib_resources для более ранних версий.Поддержка Jupyter NotebooksДля проектов с Jupyter Notebooks Poetry также предлагает удобные возможности. После установки зависимостей можно зарегистрировать виртуальное окружение как ядро Jupyter:
Для упрощения запуска Jupyter из окружения Poetry можно добавить скрипт в секцию [tool.poetry.scripts]:
Сравнение с альтернативамиВыбор инструмента для управления зависимостями — важное решение, влияющее на весь процесс разработки. Poetry не является единственным решением в экосистеме Python, и понимание его отличий от альтернатив поможет сделать осознанный выбор. Poetry vs PipPip — стандартный и наиболее распространённый инструмент для установки пакетов в Python. Сравнивая его с Poetry, можно выделить ключевые различия: Разрешение зависимостей: Pip устанавливает пакеты последовательно, не учитывая потенциальные конфликты, возникающие между транзитивными зависимостями. Poetry анализирует весь граф зависимостей и находит комбинацию версий, удовлетворяющую всем ограничениям. Версионирование: Pip работает с явно указанными версиями в requirements.txt, но не имеет встроенного механизма для работы с семантическим версионированием. Poetry позволяет указывать диапазоны совместимых версий. Виртуальные окружения: Pip требует отдельного инструмента для создания виртуальных окружений (venv, virtualenv), тогда как Poetry управляет ими автоматически. Метаданные проекта: При использовании pip требуется отдельно настраивать setup.py и requirements.txt, в то время как Poetry объединяет всё в pyproject.toml. Типичный рабочий процесс с pip выглядит так:
Poetry vs PipenvPipenv был создан с той же целью, что и Poetry — объединить управление виртуальными окружениями и зависимостями. Основные отличия: Формат файлов: Pipenv использует Pipfile и Pipfile.lock, которые менее гибкие по сравнению с поддерживаемым PEP 518 pyproject.toml в Poetry. Публикация пакетов: Poetry предлагает встроенные команды для сборки и публикации пакетов, чего нет в Pipenv. Производительность: Многие пользователи отмечают, что Poetry работает быстрее при разрешении зависимостей, особенно в проектах с большим количеством библиотек. Группы зависимостей: Poetry позволяет определять произвольные группы зависимостей, в то время как Pipenv ограничен разделением на основные и разработческие зависимости. Poetry vs CondaConda — мощная система управления пакетами и окружениями, популярная в мире науки о данных. В отличие от Poetry, Conda: Управляет не только Python-пакетами, но и библиотеками на C/C++, R-пакетами и другими системными зависимостями. Это делает её незаменимой для проектов, требующих сложных научных библиотек. Использует собственный формат пакетов вместо wheel/sdist, что обеспечивает более предсказуемую работу на разных платформах. Управляет самой Python-средой, позволяя легко переключаться между различными версиями интерпретатора без необходимости их отдельной установки. При этом Poetry выигрывает в:
Сравнение производительностиПри сравнении скорости работы различных инструментов управления зависимостями, производительность может существенно отличаться в зависимости от сценария: Первоначальная установка: Conda обычно работает медленнее из-за необходимости разрешать зависимости для бинарных пакетов, в то время как Poetry и pip с предварительно запиненными версиями работают быстрее. Разрешение зависимостей: Poetry использует более эффективный алгоритм разрешения, чем Pipenv, что заметно на проектах с большим количеством зависимостей. Кэширование: Все инструменты используют кэширование, но Poetry и Conda обеспечивают более эффективное повторное использование кэша. Когда Poetry может не подойтиНесмотря на все преимущества, Poetry не является универсальным решением. Вот некоторые случаи, когда стоит рассмотреть альтернативы: Научные проекты с нестандартными зависимостями: Если ваш проект требует сложных нативных библиотек или зависимостей за пределами PyPI, Conda может быть более подходящим выбором. Корпоративная среда с устоявшимися процессами: В организациях с существующими пайплайнами CI/CD и процессами, основанными на pip и requirements.txt, переход на Poetry может создать дополнительную сложность. Простые скрипты или прототипы: Для небольших проектов или быстрых экспериментов Poetry может оказаться излишне сложным. Проекты с экзотическими системами сборки: Если ваш проект использует кастомные шаги сборки или редко встречающиеся инструменты, интеграция с Poetry может потребовать дополнительных усилий. Каждый инструмент имеет свою нишу, и выбор между ними должен основываться на конкретных требованиях проекта, предпочтениях команды и уже существующих рабочих процессах. Poetry отлично подходит для большинства стандартных Python-проектов, предлагая современный и удобный подход к управлению зависимостями, но в некоторых специфических случаях другие инструменты могут оказаться более подходящими. Практические рекомендации и хитростиРаботая с Poetry в повседневных проектах, разработчики сталкиваются с различными ситуациями, требующими нестандартных решений. Рассмотрим несколько практических советов, которые помогут избежать типичных проблем и расширят возможности использования этого инструмента. Решение типичных проблемКонфликты зависимостей Иногда Poetry не может разрешить зависимости из-за конфликтующих требований. В таких случаях полезно выяснить источник проблемы:
1. Скорректировать ограничения версий в pyproject.toml. 2. Временно ослабить ограничения одной из зависимостей. 3. Найти альтернативные пакеты с более совместимыми требованиями. Проблемы с кэшем Poetry Сбои в кэше могут вызывать странные ошибки при установке пакетов. Если вы столкнулись с необъяснимыми проблемами, попробуйте очистить кэш:
При работе с приватными источниками пакетов иногда возникают проблемы аутентификации. Убедитесь, что правильно настроили учетные данные:
Нестандартные сценарии использованияРабота с локальными зависимостями в разработке Для библиотек, над которыми вы активно работаете, удобно использовать локальные пути вместо версий из PyPI:
develop = true устанавливает пакет в режиме разработки, что позволяет изменениям сразу же отражаться без переустановки.Временная замена зависимости форком Иногда требуется использовать форк библиотеки с исправлением, которое еще не попало в основную версию:
Poetry позволяет определять команды, которые будут доступны после установки пакета:
poetry run start-app, так и после установки пакета простым start-app.Миграция существующих проектов на PoetryПеревод существующего проекта на Poetry может значительно упростить управление зависимостями. Вот пошаговая стратегия: 1. Инициализация Poetry в существующем проекте:
Для простого импорта всех зависимостей:
3. Разделение зависимостей по группам: Проанализируйте требования и разделите их на логические группы:
Интеграция с IDE и редакторамиVisual Studio Code VS Code хорошо работает с Poetry через расширение Python. Чтобы использовать окружение Poetry: 1. Создайте проект и установите зависимости: poetry install,2. Получите путь к окружению: poetry env info --path,3. В VS Code: Command Palette → Python: Select Interpreter → Enter interpreter path → Укажите путь /.../bin/python из предыдущего шага.Альтернативно настройте Poetry для создания окружений внутри проекта:
.venv.PyCharm PyCharm Professional имеет встроенную поддержку Poetry: 1. Settings → Project → Python Interpreter → Add → Poetry Environment 2. Выберите существующее окружение или создайте новое В PyCharm Community Edition потребуется ручная настройка: 1. Получите путь к интерпретатору: poetry env info --path2. Settings → Project → Python Interpreter → Add → Existing Environment 3. Укажите путь к интерпретатору из первого шага Neovim/Vim Для интеграции с Neovim можно использовать plugins вроде pyright или coc.nvim: 1. Настройте Poetry для создания окружений в проекте: poetry config virtualenvs.in-project true,2. Создайте окружение: poetry install,3. Плагины автоматически обнаружат .venv директорию.Отладка и диагностикаПри возникновении сложных проблем с зависимостями, Poetry предлагает несколько инструментов диагностики:
pyproject.toml и poetry.lock до коммита.Автоматизация версионированияДля автоматического увеличения версии проекта можно использовать Git хуки. Например, в хуке pre-push:
Организация монорепозитория с PoetryПри работе с монорепозиторием полезный паттерн — создание метапакета с общими инструментами:
Работа с Poetry в DockerПри создании многослойных Docker-образов используйте раздельное копирование зависимостей:
Поддержка разных версий PythonДля проектов, которые должны поддерживать несколько версий Python, используйте GitHub Actions для тестирования:
Перенос проекта на Github вместе со всеми зависимостями Распознавание лиц: Python + Arduino (Управление Servo+Arduino из Python+OpenCV) Управление приложениями через Python Python daemon управление GPIO Управление потоками в Python Управление потоками в Python Управление потоками в Python(2) Управление музыкой Windows через Python Управление несколькими версиями Python Управление python приложением через сайт Как из Python скрипта выполнить другой python скрипт? Почему синтаксис Python 2.* и Python 3.* так отличается? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||


