Привет, Хабр! Меня зовут Тимур, я тружусь 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 создает виртуальное окружение, выбрав базовый интерпретатор по следующей логике:
Poetry проверяет, активировано ли уже какое-то виртуальное окружение. Если да, то оно будет использовано
Если никакое виртуальное окружение не активировано, то Poetry попытается использовать Python, который был использован при установке Poetry
Если версия 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 в ваших проектах, с какими трудностями столкнулись и как их преодолевали. Может быть, вы вообще используете какой-то более крутой инструмент?
Спасибо за внимание, и буду рад ответить на ваши вопросы в комментариях!