Poetry: from zero to hero

от автора

Привет, Хабр! Меня зовут Тимур, я тружусь ML-инженером в одной сибирской IT-компании.

Данная статья представляет собой руководство по Poetry. Я постарался покрыть все основные сценарии использования и возможности данного инструмента: создание проекта, работа с зависимостями из различных источников, управление виртуальными окружениями, сборка и публикация.

В качестве бонусов — готовая GitLab CI джоба для сборки и публикации пакетов, а также шаблон Dockerfile для multi-stage сборки образов в проектах с использованием Poetry.

Добро пожаловать под кат!

Что такое Poetry?

Poetry — это инструмент для управления проектами на Python, который предоставляет следующие возможности:

  • управление зависимостями с воспроизводимыми установками и резолвером конфликтов

  • автоматическое управление виртуальными окружениями

  • сборка и публикация.

Установка

Установка Poetry выполняется очень просто как на Unix-системах:

curl -sSL https://install.python-poetry.org | python3 -

Так и на Windows:

(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -

Далее, в зависимости от вашей системы, необходимо добавить соответствующую директорию в PATH:

# Linux, MacOS, WSL $HOME/.local/bin    # Windows %APPDATA%\Python\Scripts

Перезагружаем оболочку и проверяем корректность установки:

poetry --version   # Вывод должен быть примерно таким # Poetry (version 1.4.2)

Использование

Создание проекта

Как создать проект через терминал

Для создания проекта с нуля воспользуемся командой new:

poetry new my-project

При выполнении этой команды Poetry создаст папку со следующей структурой. Наиболее интересен здесь файл pyproject.toml, который мы рассмотрим в следующей секции:

my-project/ ├── README.md ├── my_project │   └── __init__.py ├── pyproject.toml └── tests     └── __init__.py   2 directories, 4 files

Если же мы хотим начать использовать Poetry в уже существующем проекте, то нам поможет команда init:

cd my-project2 poetry init

Далее, Poetry задаст нам несколько вопросов о нашем проекте (имя пакета, версия, описание, лицензия и поддерживаемые версии Python), а также предложит в интерактивном режиме указать зависимости (что, как по мне, не очень удобно):

This command will guide you through creating your pyproject.toml config.   Package name [my-project2]:  Version [0.1.0]:  Description []:  My Project, but second    Author [None, n to skip]:  Timur Kasimov License []:  MIT Compatible Python versions [^3.8]:    Would you like to define your main dependencies interactively? (yes/no) [yes] no Would you like to define your development dependencies interactively? (yes/no) [yes] no Generated file   [tool.poetry] name = "my-project2" version = "0.1.0" description = "My Project, but second" authors = ["Timur Kasimov"] license = "MIT" readme = "README.md" packages = [{include = "my_project2"}]   [tool.poetry.dependencies] python = "^3.8"     [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"     Do you confirm generation? (yes/no) [yes] yes

Подготовка виртуального окружения

Команды new и init не создают виртуальных окружений. При первом выполнении команд, связанных с установкой зависимостей, Poetry создает виртуальное окружение, выбрав базовый интерпретатор по следующей логике:

  1. Poetry проверяет, активировано ли уже какое-то виртуальное окружение. Если да, то оно будет использовано

  2. Если никакое виртуальное окружение не активировано, то Poetry попытается использовать Python, который был использован при установке Poetry

  3. Если версия Python с предыдущего шага несовместима с версией, указанной в pyproject.toml, то Poetry попросит явно активировать нужную версию

Советую сразу указать корректный базовый интерпретатор, выполнив в папке проекта следующую команду:

poetry env use python3.8  # Если python3.8 есть в PATH poetry env use /path/to/python  # Можно указать и полный путь

Если вы используете pyenv, можно использовать экспериментальную фичу Poetry:

poetry config virtualenvs.prefer-active-python true pyenv install 3.9.8 pyenv local 3.9.8

По умолчанию, Poetry создает виртуальные окружения в папке {cache_dir}/virtualenvs. Если вы хотите, чтобы виртуальное окружение находилось в папке проекта, можно выполнить следующую команду:

poetry config virtualenvs.in-project true

Теперь создаваемое виртуальное окружение будет находиться в папке .venv в корне проекта.

Как создать проект через PyCharm

PyCharm поддерживает интеграцию с Poetry. Можно выбрать Poetry как при создании нового проекта:

Так и в существующем проекте. Для этого необходимо в правом нижнем углу нажать «No Interpreter» (если у вас до этого не был настроен интерпретатор в проекте) или на имя интерпретатора, далее «Add New Interpreter» → «Add Local Interpreter», и в открывшемся окне выбрать «Poetry environment»:

pyproject.toml и poetry.lock

pyproject.toml — это файл, который содержит в себе всю информацию о проекте: метаданные (имя, версия и т.п.) и зависимости, а также в нем могут присутствовать настройки других инструментов.

Файл poetry.lock же содержит в себе все зависимости проекта с зафиксированными версиями и формируется автоматически (пожалуйста, не редактируйте его вручную).

[tool.poetry] содержит в себе метаданные:

  • name — имя проекта. Должно быть валидным по PEP 508;

  • version — версия проекта. Должна быть валидной по PEP 440;

  • description — короткое описание проекта;

  • license — лицензия;

  • authors — авторы проекта в формате «name <email>». Должен присутствовать как минимум один автор.

Остальные спецификаторы можно найти в документации, они не являются обязательными.

[tool.poetry.dependencies] содержит в себе версию Python и основные зависимости проекта (так называемую main-группу).

В PEP-517 был представлен стандартный способ определять альтернативные системы сборки для Python-проектов. Poetry совместим с PEP-517 и использует poetry-core для сборки, что и обозначено в секции build-system:

[build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api"

Многие питоновские инструменты поддерживают конфигурацию через pyproject.toml. Например, в моих проектах в нем уютно расположились настройки isort и mypy:

[tool.isort] line_length = 120 ... force_grid_wrap = 2  [tool.mypy] python_version = 3.9 ... follow_imports = "skip"

Установка и удаление пакетов

Стандартная установка + версионирование

Команда add добавляет зависимость в pyproject.toml, выполняет разрешение зависимостей и устанавливает зависимость:

poetry add fastapi

В секции [tool.poetry.dependencies] появилась наша зависимость:

[tool.poetry.dependencies] python = "^3.8" fastapi = "^0.95.2"

Рассмотрим, как указывать версии при установке. Первый вариант — это Caret Requirements:

Требование

Допустимые версии

^1.2.3

>=1.2.3, <2.0.0

^1.2

>=1.2.0, <2.0.0

^1

>=1.0.0, <2.0.0

^0.2.3

>=0.2.3, <0.3.0

^0.0.3

>=0.0.3, <0.0.4

^0.0

>=0.0.0, <0.1.0

^0

>=0.0.0, <1.0.0

Второй вариант — это Tilde requirements, что позволяет указывать минимальную допустимую версию с некоторой возможностью обновления:

Требование

Допустимые версии

~1.2.3

>=1.2.3,<1.3.0

~1.2

>=1.2.0,<1.3.0

~1

>=1.0.0,<2.0.0

Wildcard requirements позволяют обновление до последней версии в позиции, где расположен символ «*»:

Требование

Допустимые версии

*

>=0.0.0

1.*

>=1.0.0,<2.0.0

1.2.*

>=1.2.0,<1.3.0

Inequality requirements позволяют вручную указать диапазон допустимых версий, или же точную версию:

>=1.2.0 >1 <2 !=1.2.3 ==1.5.2  # Можно комбинировать >=1.2,<1.5

Стоит обратить внимание на следующий момент: если вы указываете точную версию, и другим зависимостям требуется другая версия, то резолвер не сможет разрешить все конфликты и процедура установки (или обновления) не будет выполнена.

Extras и groups

Прежде чем идти дальше, хочется ввести 2 понятия — extras и groups. Они довольно схожи, но на деле служат для разных целей.

Dependency groups (далее просто группы) содержат в себе опциональные зависимости, используемые только при разработке. Установить зависимости из групп можно только через Poetry. Каждый проект содержит в себе одну неявную обязательную группу — main, которая находится в секции [tool.poetry.dependencies].

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

poetry add --group test pytest

Группа вместе с зависимостью появилась в pyproject.toml:

[tool.poetry.group.test.dependencies] pytest = "^7.3.1"

Extras же предназначены для введения дополнительных зависимостей, которые включают какую-либо функциональность в вашем проекте. Установить зависимость как extra можно вот так:

poetry add --extras postgres psycopg2-binary poetry add --extras mysql --extras database mysql-connector-python  # Можно перечислять несколько extras

Зависимости появились в [tool.poetry.dependencies] с пометкой extras:

[tool.poetry.dependencies] python = "^3.8" psycopg2-binary = {version = "^2.9.6", extras = ["postgres"]} mysql-connector-python = {version = "^8.0.33", extras = ["mysql", "database"]}

После сборки и публикации можно будет устанавливать ваш пакет как обычно:

poetry add "my-project[postgres,database]" poetry add "my-project[mysql]

Установка с Git и приватных package registries

Установка из публичных репозиториев выполняется следующим образом:

poetry add "git+https://github.com/psf/requests"  # будет использован последний коммит с основной ветки poetry add "git+https://github.com/psf/requests#update-3.0"  # будет использован последний коммит с ветки update 3.0  # Все то же самое, но через SSH poetry add "git+ssh://git@github.com:requests/requests.git" poetry add "git+ssh://git@github.com:requests/requests.git#update-3.0"

Для установки из приватных git-репозиториев можно воспользоваться SSH, но в таком случае будет выполняться сборка.

Если же у вас есть приватный package registry, последовательность действия следующая (рассматривать будем на примере GitLab). Добавляем источник:

poetry source add my-repo "https://my.gitlab.com/projects/1/packages/pypi/simple"

Добавленный источник появился в файле pyproject.toml:

[[tool.poetry.source]] name = "my-repo" url = "https://my.gitlab.com/projects/1/packages/pypi/simple" default = false secondary = false

Настраиваем аутентификацию (для GitLab Package Registry рекомендую использовать Personal Access Token):

poetry config http-basic.my-repo <token-name> <secret-token>

Выполняем установку, указав источник:

poetry add --source my-repo my-package

Не забудьте изменить my.gitlab.com на адрес вашего GitLab, а также указать корректный ID проекта. Имя источника в примере используется my-repo, но можно выбрать любое другое на ваше усмотрение.

Установка из файлов

Poetry позволяет устанавливать зависимости как из локальных файлов и папок:

poetry add package-1.0.0.tar.gz poetry add package-1.0.0.whl poetry add ~/my/local/package

Так и с удаленных серверов:

poetry add https://download.pytorch.org/whl/cpu/torch-2.0.0%2Bcpu-cp39-cp39-linux_x86_64.whl

Опции при установке

В этом разделе кратко пробежимся по основным опциям, которые можно использовать при установке зависимостей:

Опция

Пояснение

—group (-G)

Группа, в которую необходимо добавить зависимость. Если такой группы не существует, она будет создана

—editable

Установить зависимость в editable режиме

—extras

Extras для активации зависимости

—optional

Добавить зависимость как опциональную

—python

Версия Python, для которой зависимость должна быть установлена

—platform

Платформа, для которой зависимость должна быть установлена (linux, darwin или win32)

—source

Имя источника, из которого будет установлена зависимость. По умолчанию используется PyPI, о настройке собственных источников ниже

—allow-prereleases

Разрешить установку пререлизов

—dry-run

Вывести последовательность действий, но не выполнять никаких операций

—lock

Не выполнять установку, только обновить lock-файл

poetry install

Команда install при запуске выполняет следующую последовательность действий:

  • читает файл pyproject.toml

  • если существует файл poetry.lock, то версии зависимостей берутся из него. Если его не существует, Poetry выполнит разрешение зависимостей и создаст его

  • устанавливает зависимости

Рассмотрим основные опции:

Опция

Пояснение

—without

Группы, которые будут проигнорированы при установке

—with

Группы, которые будут установлены

—only

Установить только определенные группы (в этом случае —without и —with будут проигнорированы)

—only-root

Установить только проект, проигнорировав все зависимости

—sync

Синхронизировать виртуальное окружение с lock-файлом

—no-root

Не устанавливать сам проект

—dry-run

Вывести последовательность действий, но не выполнять никаких операций

—extras (-E)

Extras, которые необходимо установить

—all-extras

Включить все extras в установку

—compile

Транслировать исходники в байт-код

Удаление пакетов

Для удаления какой-либо зависимости можно воспользоваться командой remove:

poetry remove requests

Если зависимость находится в какой-то группе, используйте опцию —group:

poetry remove --group my-group requests

Фиксация зависимостей

Команда lock позволяет зафиксировать зависимости, обновив файл poetry.lock:

poetry lock

Будьте внимательны! По умолчанию, poetry lock попытается выполнить обновление всех зависимостей до последних допустимых версий. Чтобы этого избежать, используйте опцию —no-update.

Запуск команд через Poetry

poetry shell

С помощью poetry shell можно запустить оболочку с активированным виртуальным окружением. Если его не существует, то оно будет создано.

Т.к. poetry shell не просто активирует виртуальное окружение, а именно создает оболочку, то стоит использовать для выхода exit, а не deactivate.

Скрипты в pyproject.toml

В файл pyproject.toml можно включить секцию [tool.poetry.scripts], которая содержит в себе описание скриптов, которые будут доступны к использованию при установке проекта:

[tool.poetry.scripts] my-script = "my_package.console:run"

Здесь мы описываем скрипт my-script, при запуске которого выполнится функция run из модуля console из пакета my_package. При обновлении или добавлении скриптов не забывайте выполнять команду poetry install, чтобы сделать их доступными в виртуальном окружении проекта.

poetry run

Команда run позволяет запускать команды в виртуальном окружении проекта. Например:

poetry run python --version

Что более интересно, с помощью run можно запускать скрипты, определенные в pyproject.toml:

poetry run my-script

Сборка и публикация проекта

Представим, что вы разрабатываете какую-то библиотеку, и настал торжественный момент сборки и публикации. Используя Poetry, сделать это можно вот так.

Собираем наш пакет:

poetry build  # собираем как sdist, так и wheel poetry build --format sdist  # собираем только sdist poetry build --format wheel  # собираем только wheel

Для публикации на PyPI предварительно получаем API token и устанавливаем его:

poetry config pypi-token.pypi <my-token>

И, наконец, публикуем:

poetry publish

Если же необходимо опубликовать пакет в приватный registry (например, в GitLab), то настройка репозитория немного усложняется. Предварительно не забываем сгенерировать Personal Access Token:

poetry config repositories.gitlab "my.gitlab.com/projects/1/packages/pypi" poetry config http-basic.gitlab <token-name> <secret-token>  # здесь token-name и secret-token - ваш Personal Access Token

Не забудьте изменить my.gitlab.com на адрес вашего GitLab, а также указать корректный ID проекта. Здесь имя репозитория в Poetry выбрано gitlab, можно использовать другое на ваше усмотрение. Пора публиковать:

poetry publish --repository gitlab

В качестве бонуса — пример публикации при использовании GitLab CI:

build_and_publish:   stage: build_and_publish   script:     - poetry install --without dev     - poetry build     - poetry config repositories.gitlab "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi"     - poetry config http-basic.gitlab gitlab-ci-token "$CI_JOB_TOKEN"     - poetry publish --repository gitlab

Настройка poetry

В этом разделе кратко пробежимся по основным командам для настройки и управления непосредственно Poetry.

Обновление Poetry

Обновиться можно как на более новую, так и на более старую версию. Будьте аккуратны — если обновиться на версию ниже 1.2, то обновление на более высокую версию будет невозможно и придется переустанавливать Poetry:

poetry self update 1.3.2

Управление плагинами

Для установки, удаления и обновления плагинов можно пользоваться командами self add, self remove и self update соответственно. Механизм работы этих команд аналогичен механизму работы команд add, remove, update за исключением того, что команды в пространстве имен self выполняется в виртуальном окружении самого Poetry. 

При управлении плагинами нам также доступны команды self lock и self install. Работают они аналогично вышеупомянутым командам.

Настройка Poetry

Команда config, помимо управления репозиториями, позволяет редактировать настройки Poetry:

# [setting-key] - имя настройки # [setting-value] - значение poetry config [options] [setting-key] [setting-value1] ... [setting-valueN]

Чаще всего используются следующие опции:

  • cache-dir (строка) — директория для кэша Poetry

  • virtualenvs.create (true / false) — создавать ли виртуальные окружения для проектов (если не существуют). Будьте внимательны: если вы установите данную настройку в false и Poetry не обнаружит виртуальное окружение в папках {cache-dir}/virtualenvs или {project-dir}/.venv, то установка зависимостей будет выполняться в системный Python.

  • virtualenvs.in-project (true / false) — создавать виртуальные окружения в корне проекта. По умолчанию, Poetry создает виртуальные окружения в папке {cache-dir}/virtualenvs.

Остальные опции можно найти в документации.

Бонус — Docker-образ в проектах с использованием Poetry

Давайте рассмотрим, как собрать максимально компактный Docker-образ, если разработку нашего проекта мы вели с помощью Poetry. Имеем следующий pyproject.toml:

[tool.poetry] name = "my-project" version = "0.1.0" description = "" authors = ["Your Name <you@example.com>"] readme = "README.md"  [tool.poetry.dependencies] python = ">=3.8.1,<4.0" fastapi = "^0.95.2" sqlalchemy = "^2.0.15" uvicorn = "^0.22.0"  [tool.poetry.group.test.dependencies] pytest = "^7.3.1"   [tool.poetry.group.dev.dependencies] flake8 = "^6.0.0"  [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"

Наше приложение — это вот такое замечательное REST API:

from fastapi import FastAPI  app = FastAPI()   @app.get('/hello') def hello():     return 'hello, world!'

Первый способ, который сразу приходит в голову — установим Poetry в образ, через него поставим зависимости и запустим наш сервис. Dockerfile в таком случае выглядит примерно вот так:

FROM python:3.8.16-slim-bullseye  WORKDIR /app  COPY poetry.lock pyproject.toml ./  RUN python -m pip install --no-cache-dir poetry==1.4.2 \     && poetry config virtualenvs.create false \     && poetry install --without dev,test --no-interaction --no-ansi \     && rm -rf $(poetry config cache-dir)/{cache,artifacts}  COPY app.py ./  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]

Собираем образ:

docker image build -t poetry-tutorial-naive:latest -f Dockerfile.naive .

Получаем образ размером 225MB. Неплохо, но можно и меньше. Давайте попробуем применить multi-stage сборку. На первом этапе с помощью Poetry подготовим виртуальное окружение, а потом скопируем его в наш итоговый образ. Итак, Dockerfile:

FROM python:3.8.16-slim-bullseye AS builder  WORKDIR /app COPY poetry.lock pyproject.toml ./  RUN python -m pip install --no-cache-dir poetry==1.4.2 \     && poetry config virtualenvs.in-project true \     && poetry install --without dev,test --no-interaction --no-ansi  FROM python:3.8.16-slim-bullseye  COPY --from=builder /app /app COPY app.py ./  CMD ["/app/.venv/bin/uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]

Собираем:

docker image build -t poetry-tutorial-copy:latest -f Dockerfile.copy .

Получаем образ размером 159MB. Уже гораздо лучше!

Может быть, если экспортировать все зависимости в привычный requirements.txt и установить через pip, то получится еще компактнее? Попробуем:

FROM python:3.8.16-slim-bullseye AS builder  COPY poetry.lock pyproject.toml ./ RUN python -m pip install --no-cache-dir poetry==1.4.2 \     && poetry export --without-hashes --without dev,test -f requirements.txt -o requirements.txt  FROM python:3.8.16-slim-bullseye  WORKDIR /app  COPY --from=builder requirements.txt ./ RUN python -m pip install --no-cache-dir -r requirements.txt  COPY app.py ./  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]

Собираем образ:

docker image build --no-cache -t poetry-tutorial-requirements:latest -f Dockerfile.requirements

Получилось 163MB, что немного больше, чем у предыдущего способа, который и вышел победителем.

Итоговая таблица:

Способ

Размер образа

Наивный

225MB (-0.0%)

Экспорт requirements.txt и установка через pip

163MB (-27.5%)

Копирование venv

159MB (-29.3%)

Обратите внимание на следующие вещи:

  • жестко фиксируйте версию Poetry в ваших Dockerfile’ах. Разработчики Poetry очень любят что-то ломать от релиза к релизу, или объявлять ставшие привычными вещи deprecated.

  • не тащите лишние зависимости в ваши образы. Используйте флаг —without.

На этом у меня все.

Делитесь своими впечатлениями от использования Poetry в ваших проектах, с какими трудностями столкнулись и как их преодолевали. Может быть, вы вообще используете какой-то более крутой инструмент?  

Спасибо за внимание, и буду рад ответить на ваши вопросы в комментариях!


ссылка на оригинал статьи https://habr.com/ru/articles/740376/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *