Примечания и мысли о том, как разработать и настроить чистую структуру пакета Python

По словам разработчиков, Python входит в пятерку лучших языков программирования в 2019 году [1]. Учитывая силу сообщества разработчиков ПО с открытым исходным кодом и высокий уровень внедрения в новых областях, таких как большие данные, аналитика и машинное обучение, никто не должен удивляться, заметив, что его популярность растет в ближайшие годы. Количество пакетов, доступных разработчикам Python, также будет расти. И, возможно, вы будете ответственны за некоторые из них.

Когда это происходит, имейте в виду, что Python очень гибок с точки зрения настройки пакетов ... Кстати, на эту тему есть много документов и сообщений в блогах. Но иногда мы можем запутаться в таком большом количестве вариантов, в основном, когда начинаем разработку и распространение пакетов.

Цель этой статьи - описать чистую структуру пакета, упростив разработчикам его тестирование, сборку и публикацию, написание как можно меньшего количества конфигураций и использование соглашений.

Чистая структура пакета

Предлагаемые папки и файлы, включая материалы для тестирования, представлены ниже:

project-root
├──src/
   └──package_name/
      └──__init__.py
├──tests/
   └──package_name/
├──.coveragerc
├──.gitignore
├──LICENSE
├──README.md
├──setup.cfg
└──setup.py

Я изучу их все, кроме .gitignore, LICENSE и README.md, поскольку это широко известные файлы. Если у вас есть вопросы по их содержанию, обратитесь в Google.

Начнем с setup.py, файла дескриптора пакета. Он состоит из сценария Python, в котором можно декларативно установить несколько свойств, как показано ниже. Свойства, объявленные в этом файле, распознаются менеджерами пакетов, такими как pip и IDE, такими как PyCharm, что означает, что это обязательное условие для любого пакета.

Значения некоторых свойств довольно просты: name, version, author… но другие требуют небольшого пояснения:

  • packages, package_dir: используются для установки расположения исходных файлов вашего пакета и объявляемых ими пространств имен. В приведенном выше примере src настроен как корневая папка источников. Если в нем есть вложенные папки, они сканируются по умолчанию. Пакеты распознаются, только если они включают __init__.py файл [2];
  • install_requires: кортеж со всеми зависимостями, необходимыми для работы вашего пакета - предупреждение: добавляйте только рабочие зависимости; здесь не должно быть ничего, связанного с тестированием или сборкой (тестовые зависимости рассматриваются в следующем разделе). Вы также можете рассматривать это как частичную замену requirements.txt, если вам это знакомо.

Эти свойства позволяют нам объединять только те файлы, которые нужны пользователям при работе с пакетами, которые мы им предоставляем, что приводит к уменьшению размера файлов дистрибутива. Кроме того, они будут загружать только те зависимости, которые необходимы каждому пакету для запуска, избегая ненужного использования сети и хранилища.

Чтобы увидеть это в действии, есть практическое упражнение. Пример кода для этой статьи доступен на GitHub (https://github.com/ricardolsmendes/python-package-cheat-sheet), и pip позволяет нам устанавливать размещенные там пакеты. Пожалуйста, установите Python 3.6+ и активируйте virtualenv. Потом:

pip install git+https://github.com/ricardolsmendes/python-package-cheat-sheet
pip freeze

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

python-package-cheat-sheet==1.0.0
stringcase==1.2.0

Первый объявлен в setup.py, доступном в репозитории GitHub. Вторая - это обязательная (рабочая) зависимость для этого пакета.

Теперь давайте вызовем метод package_cheat_sheet.StringFormatter.format_to_snakecase с помощью интерактивной оболочки Python:

python
>>> from package_cheat_sheet import StringFormatter
>>> print(StringFormatter.format_to_snakecase('FooBar'))
foo_bar
>>> exit()

Как видите, foo_bar - это результат для StringFormatter.format_to_snakecase('FooBar'), что означает, что установка пакета работает должным образом. Это быстрая демонстрация того, как вы можете настроить пакет Python и сделать его доступным для пользователей с помощью нескольких строк кода.

Пакеты также нуждаются в автоматических тестах

Современное программное обеспечение полагается на автоматизированные тесты, и мы даже не можем думать о начале разработки пакета Python без них. Pytest - наиболее часто используемая библиотека для этой цели, поэтому давайте посмотрим, как интегрировать ее в настройку пакета.

В первом упражнении мы надели шляпу пользователя - теперь пора надеть шляпу разработчика.

Прежде всего, удалите пакет, собранный с GitHub, клонируйте полный пример кода и переустановите из локальных источников:

pip uninstall python-package-cheat-sheet
git clone https://github.com/ricardolsmendes/python-package-cheat-sheet.git
cd python-package-cheat-sheet
pip install --editable .

Команда для запуска набора тестов на основе файла установки - python setup.py test. По умолчанию он не использует pytest, но есть способ заменить инструмент тестирования по умолчанию: создать setup.cfg файл в корневой папке пакета, задав псевдоним для команды test. И можно установить pytest аргумента в том же файле, как показано ниже:

[aliases]
test = pytest
[tool:pytest]
addopts = --cov --cov-report html --cov-report term-missing
  • pytest зависимости требуются с этого момента; в противном случае команда завершится ошибкой после создания псевдонима. Зависимости будут добавлены к setup.py с использованием различных свойств:
setup_requires=(
    'pytest-runner',
),
tests_require=(
    'pytest-cov',
)
  • pytest-runner отвечает за добавление pytest поддержки для задач настройки
  • pytest-cov поможет нам сгенерировать статистику покрытия для нашего кода, как мы увидим далее.

В корневую папку пакета должен быть включен еще один файл конфигурации:

  • .coveragerc управляет областью сценария покрытия. Это очень полезно, когда в вашем проекте есть папки, за которыми не нужно следить с помощью инструмента. В предлагаемой чистой структуре должна быть закрыта только папка src:
[run]
source =
    src

И мы готовы к запуску python setup.py test, теперь на базе pytest. По умолчанию pytest ищет тестовые файлы в папке tests. Ожидаемый результат для только что клонированного репозитория GitHub:

plugins: cov-2.8.1
collected 10 items
tests/package_cheat_sheet/string_formatter_test.py ..........                                                                                                                                              [100%]
---------- coverage: platform darwin, python 3.7.4-final-0 ---------
Name                                          Stmts   Miss  Cover   
--------------------------------------------------------------------
src/package_cheat_sheet/__init__.py               2      0   100%
src/package_cheat_sheet/string_formatter.py      17      0   100%
--------------------------------------------------------------------
TOTAL                                            19      0   100%
Coverage HTML written to dir htmlcov

Также проверьте htmlcov/index.html после запуска pytest, так как вывод HTML очень помогает, когда вам нужно более глубокое понимание отчетов о покрытии.

Резюме

В этой статье представлена ​​чистая структура пакета Python, охватывающая как общую настройку, так и инструменты тестирования. Он предлагает явное разделение исходных и тестовых файлов с использованием стандартов Python, соглашений по настройке и общих инструментов, позволяющих выполнять работу по написанию как можно реже.

Вот и все!

использованная литература

Журнал изменений

  • 2019–10–23: содержимое файла pytest.ini перемещено в setup.cfg и файл удален.
  • 2019–10–30: добавлены упоминания к __init__.py файлам и в раздел Ссылки.