Pull to refresh

Poetry: from zero to hero

Level of difficultyEasy
Reading time13 min
Views42K

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

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

Tags:
Hubs:
Total votes 4: ↑4 and ↓0+4
Comments15

Articles