Основная статья на хабре: https://habr.com/ru/post/483512/ Далее - черновик
Очень давно хотел создать полезный open-source пакет для питона, который каждый желающий сможет установить заветной командой:
pip install my-perfect-package
Это небольшой мануал/история о том как это сделать в немного неформальном стиле с подробностями за ссылками. Расчитана на новичков и с желанием привлечь профессионалов с целью улучшения “идеального”.
Что значит идеальный?
В моем понимании, такой пакет должет удовлетворять следующим требованиям:
-
open source на github;
каждый должен иметь возможность внести свой вклад в развитие и поблагодарить автора -
поддержка всех актуальных версий питона (2.7, 3.3, 3.4, 3.5, 3.6, 3.7); питоны бывают разные, и где-то до сих пор активно пишут на 2.7, нужно быть полезным всем, а нам ведь не сложно
-
100% покрытие юнит тестами;
юнит тесты улучшает архитектуру, позволяет делать автоматизировать регрессионные проверки
бейдж с заветным числом повышает ЧСВ и задает планку другим -
использование CI
автоматические проверки - это очень удобно!а еще куча клевых бейджей-
запуск юнит тестов на всех платформах и на всех версиях питона;
не стоит верить тем, кто утверждает что питон и устанавливаемые пакеты кроссплатформенные. -
проверка код стайла;
единый стиль улучшает читаемость и уменьшает количество пустых дискуссий в ревью -
статический анализатора кода;
автоматический поиск багов в коде? дайте два!
-
-
пакет полезен и делает мир лучше.
самое сложно требование, так как судя по количеству пакетов в pypi (~180к) разработчики - дикие альтруисты и многое уже написано.
С чего начать?
Хороших идей не было, поэтому тему выбрал избитую и очень популярную - работа со системами счисления. Первая версия должна уметь переводить числа в римские и обратно. Для мануала сложнее и не нужно.
Ах, да, самое важное - это название: numsys - как расшифровка numeral systems. numeral-system.
Тесты
Взял python3.7 и первым делом написал тесты с заглушками фукнций (мы ведь за TDD) с использованием стандартного модуля unittests. Для запуска решил использовать pytest (выглядит немного не логично, но стадартные тесты мне кажутся более практичными (ИМХО), а pytest умеет в плагины). Делаю следующую структуру:
numeral-system/
__init__.py
roman.py
tests
__init__.py
test_roman.py
Тесты в пакет класть не будем, поэтому отделяем ~зёрна от плевел~. И тут сразу встал вопрос, как запускать-то?
Как управлять зависимостями?
Можно быть старовером и использовать virtualenv. Можно быть прогрессивным и использовать poetry. Я буду чуть более консервативен и воспользуюсь tox. Создаю простой конфиг.
[tox]
envlist = py37 ; запускать на одной из предопределенной среде
[testenv] ; секция описания
deps = pytest ; ставим последнюю версию pytest
commands = pytest ; запускаем
Далее заполняю тело функций и заставляю тесты проходить. На этом моменте обычно большинство разработчиков останавливается, публикуют пакет и отгребают баги. Но мы не такие, мы идем дальше.
Как управлять версиями?
В конфигурации tox указываю запуск тестов на всех актуальных версиях питона:
[tox]
envlist = py{27,34,35,36,37,py}
С помощью pyenv доставляю нужные версии к себе локально, чтобы tox их нашел и создал тестовые среды.
Где заветные 100%?
Добавлю замер покрытия кода - для этого есть отличный пакет coverage и не менее прекрасная интеграция с pytest - pytest-cov. Меняю команду запуска теста:
commands = pytest \
--cov=numeral-system/ \
--cov=tests/ \
--cov-config "{toxinidir}/tox.ini" \
--cov-append
Делаю сбор статистики покрытия для кода самого пакета (numeral-system/) и обязательно для кода тестов (tests/) - я же не хочу, чтобы сами тесты содержали неисполняющиеся части?
Коммандой --cov-append
всю собранную статистику для каждого вызова под различной версией питона суммирую в одну, потому что покрытие для второго и третьего питона может быть различным (привет зависимый от версии код и модуль six!), но по итогу давать 100% покрытие. Простой пример:
if (sys.version_info > (3, 0)):
# Python 3 code in this block
else:
# Python 2 code in this block
Доблаяю новую среду для создания coverage отчета.
[testenv:coverage_report]
deps = coverage
commands =
coverage html
coverage report --include="numeral-system/*,tests/*" --fail-under=100 -m
И добавляю в список сред после всех тестов.
[tox]
envlist =
py{27,34,35,36,37,py}
coverage_report
Для заветного бейджа в 100% интегрирую с codecov, который поможет также просмотреть историю измений покрытия и сделает интеграцию с github.
Как анализировать код?
Интегрирую со статическими анализаторами кода pylint и flake8 - они не только ищут проблемы в коде, но проверяют на соответствие PEP8. Много анализаторов не бывает, потому что они по большей части дополняют друг другу.
Интеграция элементарная - добавляю новые тестовые среды:
[tox]
envlist =
flake8
pylint
py{27,34,35,36,37,py}
coverage_report
[testenv:flake8]
deps = flake8
commands = flake8
[testenv:pylint]
deps = pylint
commands = pylint --rcfile=tox.ini numeral_system/ tests/
Сразу же напарываюсь на странные ограничения - 100 символов в строке, имена функций в 30 символов (да, я пишу очень длинные имена тестовых методов) и предупрждения на наличие TODO в коде. Приходится слегка подтюнить пару игноров:
[MESSAGES CONTROL]
disable=fixme,invalid-name
[flake8]
max-line-length = 120
Так же неприятный момент в том, что разработчики pylint уже похранили python2.7 и не развивают большего пакет для него. Поэтому проверки стоит запускать на актуальном пакете для python3.7. Добавляю соответствующую строчку в конфигурацию.
[tox]
envlist =
flake8
pylint
py{27,34,35,36,37,py}
coverage_report
basepython = python3.7
Кстати, это так же важно для запуска тестов на различных платформах, так как дефолтная версия питона в системах различная.
Что там с CI?
Интегрирую с appveyor - CI под виндой. Настройка простая - все можно сделать в интерфейсе, затем скачать yaml файл и закоммитеть его в репозиторий.
version: 0.0.{build}
init:
- cmd: choco install python.pypy
install:
- cmd: >-
python -m pip install --upgrade pip
pip install tox
build: off
test_script:
- cmd: tox
После интегрирую с travis - CI под линуксом и маком. Настройка чуть сложнее, так как конфигурационный файл будет использоваться уже закоммиченный в репозиторий. Пару итераций проб и ошибок - конфигурация готова. Сквошим и один красивый коммит уже в репозитории.
language: python
python: 3.7
dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
addons:
apt:
sources:
- deadsnakes
packages:
- python3.4
- python3.5
- python3.6
- pypy
install:
- pip install tox
script:
- tox
(И почему CI проектам так нравится yaml формат?)
Как залить на pypi?
Далее делаю среды для сборки wheel пакета и заливки в pypi:
[testenv:build_wheel]
deps =
wheel
docutils
whitelist_externals =
/bin/sh
/bin/rm
commands =
/bin/rm -rf build dist venv_upload
python setup.py sdist bdist_wheel
[testenv:test_upload]
deps = twine
commands =
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
[testenv:pypi_upload]
skip_install = True
deps =
twine
commands =
python -m twine upload dist/*
Изначально проект назывался numsys, но при попытке заливки сталкнулся с тем, что пакет с таким именем уже есть! И что самое обидное, что он тоже умеет конвертировать в римские цифры :) Сильно не расстроился и переименовал все numeral-system. Срезаю релиз на github, собираю пакет и заливаю в продовский pypi.
А вcе работает?
Проверяю простыми командами:
> virtualenv venv
> source venv/bin/activate
(venv) > pip install numeral-system
(venv) > python
>>> import numeral_system
>>> numeral_system.roman.encode(7)
'VII'
Все отлично!
А можно лучше?
На этом останавливаться не стоит, можно сделать следующие улучшения:
- добавить перфоманс регрессию;
- добавить поддержку MacOS;
- 100% покрытие кода далеко не показатель качества, тесты должны покрывать все ветки исполнения. Как это мерить?
- зависимости обновляются, это следует отслеживать;
- добавить модную документацию на Sphinx.
- нужно больше статик анализаторов. Что есть еще? Ваши предложения?