Ruff в продакшене: автозамены (fix), правила для команды и контроль качества перед релизом
Покажем, как настроить Ruff так, чтобы он не просто линтовал, а безопасно исправлял код, как выбрать набор правил под ваш стиль и как встроить проверки в релизный пайплайн.
Содержание
Ruff в продакшене: автозамены (fix), правила для команды и контроль качества перед релизом
Ruff давно перестал быть «ещё одним линтером». На практике он становится частью инженерной культуры: разработчики получают быстую обратную связь локально, а CI превращает стиль и качество кода в управляемый процесс. Самый частый следующий шаг — включить автозамены (--fix) и сделать Ruff не только «показывающим ошибки», но и «исправляющим» их перед тем, как изменения попадут в продакшн.
Однако автозамены в команде — это зона, где легко сломать доверие к инструменту. Ошибка в настройках приводит к неожиданным правкам, конфликтам с форматтером, различиям между локальной и CI-версией, а иногда — к изменению поведения кода. В этой статье разберёмся, как настроить Ruff в продакшене так, чтобы:
fixработал безопасно и предсказуемо;- правила соответствовали вашему стилю, а не «стандартному вкусу» автора конфигурации;
- проверки были встроены в релизный пайплайн;
- перед релизом появлялась измеримая гарантия качества.
Почему именно Ruff и где он «ломается», если сделать по-быстрому
Ruff выполняет несколько разных задач одновременно: статический анализ (линтинг), проверка типов/семантики в рамках выбранных правил, сортировки импортов, иногда — автозамены (fixers). Важный момент: не все фиксы одинаковы по риску.
Условно можно разделить правила на классы:
- Чисто стилистические фиксы, которые не меняют семантику.
- Фиксы, которые меняют структуру кода, но считаются безопасными Ruff'ом (например, замена синтаксиса, реорганизация выражений).
- Фиксы с потенциальным риском — там, где автозамена может повлиять на читаемость, сложность или даже поведение (хотя Ruff старается маркировать такие случаи).
Проблемы, которые обычно возникают при «быстром старте»:
- Включили
--fixбез ограничений: Ruff начинает править больше, чем ожидали. - Не согласовали правила с форматтером (Black/others): Ruff чинит формат, а форматтер снова меняет обратно.
- Конфигурация в CI и локально различается: один разработчик получает одни результаты, другой — другие.
- Нет «контракта» для команды: люди обсуждают, кто «имеет право» отключать правила, а кто — нет.
- Нет контроля перед релизом: ленты кода проходят мимо, пока не обнаружится регрессия.
Поэтому настройка для продакшена — это не «включить Ruff», а выстроить систему: правила → фикс → форматирование → проверка в пайплайне → контроль качества.
Настройка Ruff для безопасных автозамен (fix)
Базовая идея: сначала выберите, что считать «исправляемым»
Ruff поддерживает разные режимы автозамены. На практике удобно мыслить так: включаем фикс только там, где риск приемлемый для вашей команды. Для этого важно:
- ограничить набор правил, которые включены;
- управлять тем, какие классы исправлений применяются;
- отделить «быстрые безопасные фиксы» от «более спорных».
Начнём с минимальной конфигурации в pyproject.toml.
[tool.ruff]
target-version = "py311"
line-length = 100
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors (часть стилистики)
"F", # pyflakes
"I", # isort
"UP", # upgrades (в более новых версиях Python)
"B", # flake8-bugbear (часть помогает находить ошибки)
]
ignore = [
"E501", # line too long — если у вас line-length соблюдается иначе
]
Это уже задаёт базовую рамку: мы не просим Ruff «чинить всё подряд», а выбираем конкретные семейства правил.
Дальше — ключевой слой: как включать fix.
Рекомендованный подход
- В локальной разработке:
ruff check --fix(илиruff check --fix --unsafe-fixesтолько по решению команды). - В CI: проверять, что после фикса код не меняется (идемпотентность), или запускать фиксы и снова прогонять проверки.
Ниже — типичная схема для «надёжного продакшена».
Вариант 1: фикс только безопасных изменений и проверка идемпотентности в CI
В CI сначала запускается фикс (без unsafe), затем Ruff проверяет, что состояние репозитория чистое.
ruff check --fix .
ruff check .
Но это недостаточно жёстко: изменения могли быть применены, но вы всё равно их закоммитили не полностью. Поэтому лучше сделать «жёсткую» проверку: после автозамены не должно оставаться диффа.
Пример: проверка, что фиксы ничего не меняют
ruff check --fix .
git diff --exit-code
И только потом:
ruff check .
Так вы превращаете --fix в контролируемый шаг: либо CI применяет фикс и код становится «принятым», либо CI обнаруживает, что разработчик не подтянул автоисправления.
Вариант 2: фикс по желанию в отдельном шаге (и отдельная политика)
Если команда не хочет, чтобы CI всегда менял код, можно разделить процессы:
- локально разработчик делает
ruff check --fix; - CI просто проверяет
ruff checkбез--fix; - перед релизом запускается «валидация с фиксом» в режиме проверки идемпотентности.
Идея в том, что основной pipeline остаётся быстрым и предсказуемым, а строгая проверка — только перед выпуском.
Пример для релизной задачи:
ruff check --fix .
git diff --exit-code
ruff check .
Что с --unsafe-fixes?
У Ruff есть режимы, связанные с «unsafe fixes». Практически это означает: не все фиксы, которые Ruff умеет применять, одинаково безопасны для автоматической правки без ревью.
Правило команды можно сформулировать так:
- По умолчанию:
ruff check --fixбез--unsafe-fixes. - Unsafe-fixes: только в отдельном, согласованном шаге (например, nightly или перед массовой реорганизацией кода), и обязательно с ревью/чекером диффа.
В pyproject.toml можно управлять частично и через выбор правил. Но окончательное решение «unsafe или нет» лучше закреплять не в голове, а в документации команды и в скриптах CI.
Автозамены ≠ форматирование
Частая ошибка — считать, что Ruff «всё приведёт к виду». Форматтер (Black, ruff format и т.п.) отвечает за формат. Ruff — за линтинг и часть структурных правок (и иногда — за импорты/связанные вещи).
Чтобы избежать гонки инструментов:
- либо используйте единый форматтер и разрешите Ruff правки только там, где формат не затрагивается;
- либо включайте в pipeline форматирование после фиксов, но тогда фикс должен быть устойчив к форматированию.
Обычно рабочий порядок для Python-проектов выглядит так:
ruff check --fix(без unsafe)ruff formatилиblackruff check(финальная проверка)
Если вы используете Black, пример:
ruff check --fix .
black .
ruff check .
А лучше — зафиксировать один порядок и не менять его без причины: иначе в диффах будет «шум», и команда начнёт отключать проверки.
Выбор набора правил под стиль команды: практическая методика
1) Не выбирайте правила «из головы». Соберите исходную базу качества
Перед тем как включать автозамены, полезно сделать «аудит» текущего состояния:
ruff check .
Затем соберите список правил/категорий, которые чаще всего:
- уже дают полезные подсказки;
- создают много шума и не дают ценности;
- потенциально затрагивают места, где автозамена спорна.
Для команды это превращается в управляемую таблицу: что включаем сразу, что включаем постепенно, что игнорируем до тех пор, пока кодовая база не «очистится».
В Ruff удобно настраивать select и ignore. Но не делайте «ignore всего подряд» — он со временем начинает маскировать реальные проблемы.
2) Разделяйте правила по слоям жизненного цикла
В реальных проектах полезно вести политику примерно так:
- Layer A (обязательные, высокое отношение пользы к шуму):
F, базовыеE,I. - Layer B (качество и потенциальные ошибки):
B, часть правил о логике/ошибках. - Layer C (стиль/улучшения по вкусу):
UP, расширенныеflake8-*, сложные правила.
Тогда автозамены активируются прежде всего для Layer A и частично для Layer B. Layer C — либо без fix, либо с осторожным включением, когда команда договорилась о стиле.
3) Договоритесь о числовых параметрах: line-length, target-version, исключения
Неприятный нюанс: даже одинаковые правила могут давать разные результаты, если:
target-versionне совпадает с реальным интерпретатором,line-lengthотличен от того, что ожидает форматтер,- вы используете разные версии Ruff у разработчиков и в CI.
В продакшене минимум:
- зафиксировать версию Ruff (в
requirements-devили lockfile); - закрепить
target-versionиline-lengthвpyproject.toml; - убедиться, что форматтер и Ruff используют совместимые параметры.
4) Настройте правила для разных директорий
Часто в проектах есть src/ и tests/ — требования к ним отличаются. Например, тестам проще простить некоторые вещи, но нельзя допускать очевидные ошибки.
Ruff позволяет задавать конфигурацию по файлам. Типовой пример: игнорировать некоторые предупреждения в тестах.
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101"] # пример: asserts в тестах (условно)
Здесь главное — не превращать per-file-ignores в «корзину». Это должно быть точечное решение, закреплённое аргументом: «мы осознанно разрешаем X в тестах, потому что…».
5) Включите «правила для команды» через политику фиксов
Технически можно разрешить Ruff править многое, но культурно лучше разделить:
- что разработчик должен чинить до отправки (pre-commit);
- что будет автоматически чиниться в CI;
- что требует отдельного ревью.
Практическая политика:
- обязательные исправления → pre-commit / local
--fix; - остальные исправления → CI проверяет, что код после фикса идемпотентен;
- unsafe-fixes → только по решению команды, в отдельном релизном процессе.
Интеграция в релизный пайплайн: контроль качества перед выпуском
Модель контроля: «быстро локально, строго в CI, идеально перед релизом»
Релизный пайплайн обычно состоит из стадий: сборка, тесты, линтинг, security scanning. Ошибки качества на ранних стадиях полезны, но наиболее важен этап перед релизом — там вы исключаете ситуации, когда «вроде всё прошло», но код несогласован с контрактом репозитория.
Рекомендуемый сценарий с Ruff:
- On PR (быстро):
ruff check(без--fix);- опционально
ruff format --check(илиblack --check).
- Перед релизом (строго):
ruff check --fix(без unsafe);- проверка идемпотентности
git diff --exit-code; ruff checkфинально;- форматирование (если вы не форматируете в CI автоматически).
Это даёт эффект: если на сервере автозамены меняют код — значит в репозитории ещё не закреплены правила и команда должна согласовать дифф.
Пример пайплайна в GitHub Actions
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install deps
run: |
pip install ruff
pip install black
- name: Ruff check (PR)
if: github.event_name != 'release'
run: |
ruff check .
- name: Format check (PR)
if: github.event_name != 'release'
run: |
black --check .
- name: Ruff fix + idempotency (release)
if: github.event_name == 'release'
run: |
ruff check --fix .
git diff --exit-code
ruff check .
black .
git diff --exit-code
Если у вас GitHub не релиз, а тег, можно заменить условие. Смысл не в платформе, а в механике: на релизе вы обязаны получить чистое состояние после фиксов/форматирования.
Подводные камни: git diff и CI
- Чистый diff зависит от того, коммитите ли вы фиксы. В нашем сценарии мы предполагаем: CI не коммитит, а проверяет, что изменений быть не должно.
- Локальные правки и CRLF/LF могут создать «фальшивые» диффы. Это решается через настройки .gitattributes и единое окружение форматирования.
- Разные версии Ruff/Black ведут к разным фиксам. В продакшене лучше фиксировать версии.
Контроль качества: как измерять эффект и не утонуть в шуме
Индикатор 1: доля файлов, которые меняются на --fix
Если при включении --fix внезапно меняется половина репозитория — это не «автоматика», а потенциальный сбой в стратегии. Такой запуск лучше делать поэтапно:
- сначала включить
checkбезfix; - закрепить набор правил и исключения;
- затем включить
--fixи ожидать, что дифф будет локальным и предсказуемым.
Индикатор 2: число уникальных правил и динамика по времени
В проекте полезно вести список правил, которые добавились/убрали. Это можно делать через логирование конфигурации или через отчёты из Ruff.
Идея: команда должна видеть, что правила — управляемый продукт, а не «магия настройки навсегда».
Индикатор 3: стабильность результата (идемпотентность)
Один из самых практичных признаков зрелости настройки:
- после
ruff check --fixи последующегоruff checkне должно быть новых правок, которые CI пытается снова применить.
Если идемпотентность «прыгает», обычно виноваты:
- конфликт с форматтером;
- нестабильные правила по версиям Python;
- не совпадают исключения per-file;
- разные версии Ruff.
Конкретные настройки: безопасный профиль для команды
Ниже — пример конфигурации, которую часто берут как основу для продакшена (адаптируйте под ваш код и форматтер).
[tool.ruff]
target-version = "py311"
line-length = 100
src = ["src", "tests"]
respect-gitignore = true
[tool.ruff.lint]
# Базовая стратегия: сначала полезное, потом расширения
select = [
"E",
"F",
"I",
"B",
"UP",
]
ignore = [
"E203", # пример: часто конфликтует с Black (условно)
"W503", # пример: иногда конфликтует со стилями интерпретаций
"E501", # если ограничение управляет форматтер
]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # импортов ради экспорта пакета
[tool.ruff.format]
# Если вы используете ruff format вместо black — согласуйте это
quote-style = "double"
indent-style = "space"
Дальше — набор команд для разработчика и CI:
- локально:
ruff check --fix .- форматтер (
black ./ruff format .)
- в CI:
ruff check .- на релизе:
ruff check --fix . && git diff --exit-code
Ошибки, которые чаще всего приводят к проблемам в продакшене
1) Включили fix для всего набора правил
Это ускоряет первую очистку, но потом ломает процесс: команда начинает бороться с диффами, а не с качеством.
Правильнее: стартовать с ограниченного набора правил и расширять.
2) Не согласовали Ruff и форматтер
Если Ruff применяет правки, которые меняют переносы/структуру, а форматтер — по другой логике, вы получите постоянный «маятник».
Стабилизируйте порядок: fix → format → check и держите его неизменным.
3) Разные версии Ruff в окружениях
Проблема часто всплывает на CI: разработчик сказал «у меня всё чисто», а CI прислал новые фиксы. В продакшене фиксируйте версии.
4) Нет «контракта» для команды по unsafe-fixes
Если кто-то включает unsafe-fixes локально и коммитит неожиданно большие изменения, а кто-то отключает — правила конфликта начинают происходить не в коде, а в коммуникации.
Нужна договорённость: unsafe-fixes — только по расписанию/решению/процессу.
5) Нет проверки идемпотентности
Если вы не проверяете «что было бы, если применить fix ещё раз», вы можете пропустить сценарии, когда фиксы зависят от очередности или от частично изменённых файлов.
Как выстроить процесс обучения команды (и зачем это нужно Ruff)
Ruff — инструмент, но качество — это процесс. Чтобы автозамены не превратились в «внезапные диффы», нужно:
- короткое правило: как запускать fix локально;
- точный порядок команд (и кто его определил);
- политику по исключениям (
per-file-ignores); - политику по unsafe-fixes;
- практику: что делать, если Ruff предлагает исправление, но это влияет на читаемость/архитектуру.
Полезно выделить один рабочий сценарий для PR:
ruff check .должен быть зелёным.- Перед коммитом разработчик может выполнить
ruff check --fix .. - Если CI всё равно правит — значит разработчик не прогнал те же шаги, либо конфигурация несовместима с окружением.
Так вы формируете привычку и минимизируете «неожиданные правки».
Вывод: Ruff как механизм контроля качества, а не просто набор правил
Ruff в продакшене — это не про «настроить линтер», а про построение цепочки доверия: какие правила считаются обязательными, какие исправляются автоматически, как предотвращаются конфликты с форматированием и как CI/релиз подтверждает, что код соответствует контракту.
Ключевые принципы, которые стоит закрепить в документации команды:
- автозамены включайте постепенно и ограничивайте риск;
- отделяйте fix от форматирования и фиксируйте порядок
fix → format → check; - делайте релизный шаг строгим: применить fix и проверить идемпотентность;
- согласуйте конфигурацию, версии инструментов и исключения;
- ведите правила как управляемый актив (обсуждение, расширение, исключения по аргументам).
Если вы хотите глубже разобраться в том, как системно подходить к настройке и внедрению Ruff в кодовую базу (включая практики организации правил и работы с fix), можно дополнительно изучить материал в рамках курса: [ /course/ ] — как один из способов упорядочить знания и быстрее довести настройки до «рабочего уровня команды», а не до разового эксперимента.
Комментарии
Пока нет комментариев