null

Python-пакеты, venv и pip

Во время разработки проектов на Python вы часто будете использовать готовые модули, такие, как, например библиотеки по типу requests, numpy и другие. 
Также вам, возможно, понадобится дописать модули к готовому проекту, не изменяя его исходный код.  

В этой статье будут рассмотрены создание и установка Python-пакетов, а также pip и venv, как инструменты для управления зависимостями.
Примеры команд из статьи приведены для Unix/MacOS. 

Введение в терминолгию

Пакет (Python package) - модуль с кодом, который можно установить как зависимость для вашего проекта.
pip - утилита для управления пакетами.
venv (или virtualenv в python2) - система для создания изолированной среды для хранения пакетов.
PyPI - репозиторий для публикации Python-пакетов.

Создание и публикация пакетов

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

packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│   └── example_package_YOUR_USERNAME_HERE/
│       ├── __init__.py
│       └── example.py
└── tests/

Нетрудно заметить, что помимо директорий с кодом и тестами (src и tests) в корне проекта имеются 3 файла. Среди них файл с описанием проекта README, файл с описанием лицензированием пакета, а также файл pyproject.toml.
В современных версиях pip этот файл является стандартом для задания метаинформации о пакете. В более ранних версиях стандартом был файл setup.py, который все еще может понадобиться для установки пакета в режиме разработки (для редактирования его файлов после установки).
Наиболее важной частью его содержимого является информация, необходимая для сборки пакета:

[build-system]
requires = [
    "hatchling",
    "my_pkg_name @ git+ssh://git@github.com/my-github-name/my_repo"]
build-backend = "hatchling.build"

В поле requires задается список пакетов, являющихся build-зависимостями вашего проекта. Например, можно использовать пакеты из PyPI, пакеты из git репозиториев, пакеты из архивов и.т.д.
В поле build-backend выбирается система сборки для пакета. На начальном уровне в целом не имеет значения, какую систему использовать: setuptools, hatchling, flit или прочие. 

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

[project]
name = "example_package_YOUR_USERNAME_HERE"
version = "0.0.1"
authors = [
  { name="Example Author", email="author@example.com" },
]
description = "A small example package"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.urls]
"Homepage" = "https://github.com/pypa/sampleproject"
"Bug Tracker" = "https://github.com/pypa/sampleproject/issues"

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

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

python3 -m pip install --upgrade build
python3 -m build

В директории dist появится 2 файла: .whl - собранный пакет, и архив .tar.gz - сжатый архив с проектом. Оба файла будут необходимы в дальнейшем для установки пакета: система управления пакетами сначала попытается использовать .whl, но при необходимости скачает архив и выполнит сборку .whl уже на устройстве пользователя. 

python3 -m pip install --upgrade twine
python3 -m twine upload --repository testpypi dist/*

В результате ваш пакет будет доступен по ссылке https://test.pypi.org/project/example_package_YOUR_USERNAME_HERE, где последняя часть будет эквивалентна названию, заданному в pyproject.toml.

Подробнее о пакетах

Установка пакетов через pip и venv

pip - стандартный пакетный менеджер Python, используемый для установки и обновления пакетов. Для понимания базовых механик работы pip рассмотрим наиболее используемый скрипт pip install
Аргументы скрипта - пары {пакет, ограничения на версию}, описывающие необходимые для установки пакеты, либо, при указании ключа -r, аргументом становится путь до текстового файла с описанием зависимостей в аналогичной форме. 

При его запуске последовательно запускается несколько этапов для установки заданных зависимостей. 
Во-первых, pip определяет, в каком формате ему задана зависимость. Это может быть ссылка на git или архив, .whl файл, локальная директория и.т.д. 
Для удовлетворения зависимости pip необходимо узнать ее имя и версию. Из названия .whl файлов эти параметры определяются однозначно, для локальных директорий используется команда setup.py egg_info.
Далее pip выбирает наиболее актуальную версию пакета, удовлетворяющую заданным ограничениям (>=min_version & <=max_version).
Наконец, pip устанавливает runtime зависимости в порядке зависимости пакетов друг от друга снизу вверх.


По умолчанию пакеты устанавливаются в директорию /usr/bin/pythonX.Y/site-packages, где X.Y - версия Python, установленная в системе как дефолтная.
Очевидна главная проблема этого подхода: нет возможности для разных проектов установить одинаковый пакет с различными версиями. Для этого в Python существует механизм виртуальной среды исполнения venv (или vitualenv в python2). Создать venv для проекта можно запустив следующие команды в директории проекта:

python3 -m pip install --user virtualenv
python3 -m venv env

При создании venv в директорию env скопируются все необходимые исполняемые файлы, фактически эмулируя директорию интерпретатора по-умолчанию.  
Также по в директории env создастся скрипт bin/activate, который позволяет установить venv как интерпретатор по-умолчанию для текущей сессии терминала, выполнив:

source env/bin/activate

Теперь установленные пакеты будут храниться в env/lib/pythonX.Y/site-packages, не конфликтуя с пакетами, установленными в директории дефолтного интерпретатора. 

Подробнее об установке зависимостей