Примечания и мысли о том, как разработать и настроить чистую структуру пакета 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, соглашений по настройке и общих инструментов, позволяющих выполнять работу по написанию как можно реже.
Вот и все!
использованная литература
- [1] Опрос разработчиков Stack Overflow 2019: самые популярные технологии | Языки программирования, сценариев и разметки: https://insights.stackoverflow.com/survey/2019#technology-_-programming-scripting-and-markup-languages
- [2] Создание и распространение пакетов с помощью Setuptools, используя find_packages (): https://setuptools.readthedocs.io/en/latest/setuptools.html#using-find-packages
Журнал изменений
- 2019–10–23: содержимое файла
pytest.ini
перемещено вsetup.cfg
и файл удален. - 2019–10–30: добавлены упоминания к
__init__.py
файлам и в раздел Ссылки.