Продолжаю рассказывать, как open source проект Python Дайджест спустя 5 лет без обновлений удалось актуализировать по всему стэку технологий. В первой части рассказал, как удалось outdated проект с Python 3.4 обновить до Python 3.11 и Django 4.1.


В этой части расскажу, как удалось максимально дешево привести кодовую базу в актуальное состояние.



Содержание цикла статей



Состояние после обновления до 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.


Комментарии (0)