Продолжаю рассказывать, как 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.
ссылка на оригинал статьи https://habr.com/ru/company/first/blog/721732/
Добавить комментарий