Продолжаю рассказывать, как open source проект Python Дайджест спустя 5 лет без обновлений удалось актуализировать по всему стэку технологий. В первой части рассказал, как удалось outdated проект с Python 3.4 обновить до Python 3.11 и Django 4.1.
В этой части расскажу, как удалось максимально дешево привести кодовую базу в актуальное состояние.
Содержание цикла статей
- 1 часть: Как обновиться с Python 3.4 до Python 3.11, если pip уже сломан
-
2 часть: Как актуализировать всю кодовую базу с помощью pre-commit
— вы здесь — - 3 часть: Как сделать CI для OpenSource проекта с Github Actions
- 4 часть: Как ускорить Django проект до (почти) максимума
Состояние после обновления до Python 3.11
- Приложение запускается, тесты проходят.
- Код написан для работы с Python 3.4 и имеет вставки для поддержки Python 2.7.
- Куча неиспользуемых импортов, переменных, код не соблюдает даже части PEP8, форматирование строк сильно прыгает.
- Нет времени и какого-либо желания руками переписывать код для поддержки 3.6+ фич.
Задача (часть 2) — перейти от ручного форматирования кода к автоматизированному
Приступим к решению.
План работ
- Подобрать автоформатер, который не ломает код
- Настроить автозапуск перед коммитом
- Отформатировать код
Зачем форматируем код
Исходный код — текстовое представление замысла разработчика. Можно понять смысл программы и на COW-language, только потребуется больше усилий, да и меньшее количество коллег захотят это делать. Человеку проще запомнить один способ чтения исходного кода программы, чем под каждый новый файл перенастраивать себя и свои глаза.
Начинающие специалисты еще не привыкли, что перечитывать код нужно чаще, чем писать его. Поэтому склонны к оформлению в стиле «как пойдет». И сложность не столько в нестандартном оформлении, сколько в очень частом изменении стиля. После года-двух специалист уже может держать один стиль дольше и жить становится проще.
В командах у специалистов разный уровень и, чтобы получить единый стиль у всех, выбирается единый code style. И дальше возникает вопрос — как его проверять или выполнять. Желательно автоматически.
Как делать автоформатирование кода
Когда говорим «автоформатирование Python кода», то сразу вспоминается black/autopep8/flake8/isort/redbaron/yapf и инструменты на их базе. Это инструменты, которые позволяют привести код к единому формату. Про них есть статьи на habr — раз, два, три, а sobolevn сделал много докладов на эту тему.
Одни инструменты редактируют только 1 вид кода, например, isort — импорты. Другие инструменты редактируют весь код на Python, а иногда могут применяться и для других языков.
В Python Дайджест нет «исторически сложившихся» ограничений, связанных с кодом, командой и процессом разработки, поэтому автоформатировать хочется все сразу и без лишних настроек. Значит, основным форматтером будет black, а остальные будут дополнять его.
Как обновить код под новую версию Python
Синтаксис Python расширяется/дорабатывается с каждой версией, меняются API даже стандартных библиотек, появляются конструкции, которые выглядят понятнее, устанавливаются более корректные значения «по умолчанию» для функций. Все это можно читать в changelog'ах к версиям Python.
Появление f-string в 3.6 — это наиболее заметное изменение в форматировании строк. Строки стали выглядеть легче и понятнее. При этом пройтись по всей кодовой базе и .format переделать на f-string — занятие на много часов. Подобные изменения можно делать автоматически.
Если раньше активно применяли six
и future flags
для плавного обновления кода, то теперь можно использовать pyupgrade.
pyupgrade — утилита, которая может обновить код с версии X до версии Y. Утилита аккуратно обновляет «старые» конструкции на «новые». Работает хорошо и не ломает код, поэтому можно запускать при каждом коммите.
Чем запускал скрипты форматирования — pre-commit
Раз инструменты есть, теперь нужно научиться не забывать их запускать. Для этого хорошо подходит концепт с pre-commit git hooks, чтобы непосредственно перед коммитом запускать форматирование и корректировать неточности. Общеупотребимой реализацией концепции является проект pre-commit
.
pre-commit
— это расширяемый (за счет hook/плагинов) способ запускать различные скрипты перед коммитом.
Про него на Хабре писали здесь и здесь.
Этот проект позволяет настроить автоматическую проверку чего только вздумаешь — можете самостоятельно ознакомиться со страницей плагинов.
Из всего перечня плагинов мне нужны:
- Форматирование кода с помощью black, удаление неиспользуемых импортов с isort и autoflake
- Поиск случайно оставленных секретов/паролей в коде
- Валидация всех конфигурационных файлов (JSON, YAML), в том числе для CI
- Поиск устаревших пакетов на основе dependabot
- Возможность автоматически сконвертировать код до Python 3.11 версии с помощью pyupgrade
Это все умеют плагины pre-commit
. Вот конфигурация, которая получилась — .pre-commit-config.yaml, pyproject.yaml
Как настроил pre-commit
Для регулярного запуска я выбрал такие плагины для pre-commit
:
# copy-paste .pre-commit-config.yaml
# ....
exclude: "^docs/|/migrations/"
default_stages: [commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-merge-conflict
- id: detect-private-key
- id: debug-statements
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py311-plus]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
exclude: ^.*\b(migrations)\b.*$
- repo: https://github.com/PyCQA/autoflake
rev: v2.0.1
hooks:
- id: autoflake
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.21.0
hooks:
- id: check-github-workflows
- id: check-dependabot
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-use-type-annotations
- id: python-check-blanket-noqa
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
exclude: package.lock.json
А чтобы black
и isort
не ссорились — внес такие изменения в конфигурацию проекта.
# copy isort/black settings in pyproject.toml
# ...
[tool.black]
line-length = 119
target-version = ['py311']
include = '\.pyi?$'
[tool.isort]
profile = "black"
line_length = 119
multi_line_output = 3
include_trailing_comma = true
use_parentheses = true
ensure_newline_before_comments = true
Как использовал pre-commit
Чтобы использовать выполняем:
# install
poetry add pre-commit --group dev
pre-commit install
# run check
pre-commit run --show-diff-on-failure --color=always --all-files
Последнюю строку можно поместить в Makefile с названием check
и руками запускать.
Установив pre-commit
, теперь перед формированием коммита, выполнятся все скрипты.
Что отформатировал в коде
Сразу после первого запуска скрипты смогли изменить:
- Удалены строки
from __future__ import unicode_literals
и# -*- encoding: utf-8 -*-
.- # -*- coding: utf-8 -*- - from __future__ import unicode_literals
-
Скорректировано формирование строк — двойные кавычки для строк и f-string.
- name = '{} - {}'.format(package.name, package_version) + name = f"{package.name} - {package_version}"
- Исправлено форматирование множественных импортов — теперь импорты организованы в группы — системные, django и из приложения.
-
Удалены неиспользуемые/модифицированы импорты
- from mock import patch + from unittest.mock import patch
-
Использована корректная форма Dictionary Comprehension, вместо преобразования через dict.
- headers = dict((n, n) for n in fieldnames) + headers = {n: n for n in fieldnames}
-
Удалены/добавлены переносы строк, где требуется.
- list_filter = ('is_activated', 'if_element', 'if_action', 'then_element', 'then_action',) + list_filter = ( "is_activated", "if_element", "if_action", "then_element", "then_action", )
И теперь разработчик может писать код как угодно — форматтеры приведут все в порядок.
Прелесть pre-commit
, что его можно запускать в CI (про это в следующей части) и таким образом получать дополнительную проверку на соответствие формата кода. И тогда тот, кто «забыл» поставить себе pre-commit
или предпочитает кодить из браузера, не сможет залить некачественное изменение на сервер.
Выводы
- Современные автоформатеры Python кода уже не склонны ломать код и можно их использовать активнее (не забудьте настроить свой формат).
- pre-commit — это проект, который заметно уменьшает вероятность залить в репозиторий не то или не так. Готовых плагинов очень много и каждый для своей команды найдет, что взять оттуда. Форматирование кода и поиск секретов — берите точно.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.