$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогЗапуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”

Запуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”

$ sudo teach IT
·16 сентября 2026 г.·10 мин·105
Запуск и поддержка проекта на Ruff в команде: формат “как в CI”, а не “как договорились”

Как правильно выстроить конфигурацию Ruff (линтинг, форматирование, автозамены) и сделать так, чтобы одинаковые правила гарантировались в локальной сборке и на CI без ручного шаманства.

Содержание
Архитектура: что именно делает Ruff в проектеКонфиг “как в CI”: один источник истиныГде хранить конфигБазовый скелет pyproject.tomlЛинтинг и правила: “похожее локально” — не подходитЧто сделать, чтобы не расходилосьИнкрементальная стратегия: не “сломать всё сразу”Форматирование: Ruff Format не должен “ехать” относительно BlackРекомендованный сценарий: “один форматтер”Частая ошибка: не фиксировать формат автоматическиИсправления (fix): как сделать “автозамены” стабильнымиОтдельный контроль для командыСтандарт команд: Makefile как “контракт”Пример Makefilepre-commit: гарантировать правила до CI (а не после)Пример .pre-commit-config.yamlНужно ли pre-commit, если CI уже есть?CI: как именно добиться равенства с локальной сборкойПример GitHub ActionsВерсионирование и согласованность: почему “одинаково” не значит “одно и то же”Практика: фиксируйте версиюПрактика: фиксируйте target-versionСтратегия обновления правил без хаосаРекомендованный процесс в командеТипичный подводный камень: ignore слишком широкоПолезные команды для локальной отладкиБыстро понять, какие файлы проверяютсяУзнать, какие правила срабатывают (и почему)Применить автозамены локальноПроверить формат в “точности как CI”Набор “правил игры”: чему учить командуКак это выглядит в конфигурации “под проект”Вывод: Ruff в команде — это дисциплина конфигурации и команд

Ruff за последние годы стал де-факто стандартом для линтинга и форматирования в Python-проектах: он быстрый, конфигурируется прозрачно, умеет автозамены и ориентируется на реальные практики (а не на “ручной стиль как договорились”). Но именно в командной работе чаще всего вскрывается проблема: локально разработчики “подгоняют” код под правила, а на CI внезапно всплывают расхождения. Итог — лишние итерации PR, споры о конфигурации и ощущение, что Ruff “то работает, то нет”.

Хорошая новость: это лечится правильной схемой “как в CI”. Нужно добиться трёх эффектов:

  1. Один и тот же набор правил применяется и локально, и в пайплайне.
  2. Результаты одинаковые, независимо от того, кто запускает (Windows/macOS/Linux) и как именно.
  3. Автозамены и форматирование происходят предсказуемо: либо разработчик запускает ровно те команды, что стоят в CI, либо они выполняются автоматически в pre-commit/IDE/тех же целях.

Ниже — практичный разбор, как выстроить Ruff-конфигурацию, чтобы команда жила по одним законам. Плюс разберём типичные ошибки, которые ломают “одинаковость”.


Архитектура: что именно делает Ruff в проекте

Ruff — это набор инструментов под капотом. В зависимости от режима он может:

  • Lint: проверять стиль и качество (правила из наборов rule codes, например E, F, B, I, UP и т.д.).
  • Format: форматировать код, приближаясь к “черновику” конкретного стиля (схема ближе к black, но есть нюансы).
  • Fix / Auto-fix: предлагать и применять исправления части линтерных проблем.

Важный момент для команды: линтинг, форматирование и исправления — это не одно действие, и их нужно связать общей конфигурацией и одинаковыми параметрами запуска.


Конфиг “как в CI”: один источник истины

Ключ к стабильности — хранить конфигурацию в репозитории и запускать Ruff одними и теми же флагами.

Где хранить конфиг

Самый практичный вариант — один файл конфигурации в корне проекта:

  • pyproject.toml (часто используется в Python-проектах)
  • реже ruff.toml

Почти всегда выбирают pyproject.toml, потому что он уже используется для инструментов (mypy/pytest/black/coverage).

Базовый скелет pyproject.toml

Пример структуры (адаптируйте под свой репозиторий):

code
[tool.ruff]
line-length = 100
target-version = "py311"
src = ["src", "tests"]

[tool.ruff.lint]
# Включаем базовые категории правил (пример).
select = ["E", "F", "W", "I", "UP", "B"]
# Игнорируем конкретные правила, если они объективно мешают проекту.
ignore = ["E501"]

# Если хотите, чтобы Ruff учитывал типовые ограничения:
# (в зависимости от вашей стратегии, может быть полезно)
fixable = ["ALL"]
unfixable = []

[tool.ruff.lint.isort]
# Если используете isort через Ruff
known-first-party = ["your_package"]
# known-third-party = ["requests", "numpy"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false

Этот пример иллюстрирует принципы, а не “универсальную идеальность”. В реальном проекте обычно начинают с “скопировать существующий стиль”, а затем постепенно ужесточают правила.


Линтинг и правила: “похожее локально” — не подходит

Самая частая причина рассинхронизации: локально разработчики запускают Ruff без тех же опций, что в CI. Например:

  • локально: ruff check .
  • в CI: ruff check . --select E,F --fix или с другим target-version
  • или: различаются src/target и exclude
  • или: в одном месте действует .ruff.toml, а в другом — иной файл/флаги.

Что сделать, чтобы не расходилось

  1. Все правила — в pyproject.toml, а не только во флагах команды.
  2. В CI и локально использовать одну и ту же команду (или команды-обёртки, которые повторяют флаги).
  3. Явно задать src и исключения, чтобы Ruff сканировал одинаковые директории.

Например, добавьте в pyproject.toml:

code
[tool.ruff]
src = ["src"]
exclude = [
  ".git",
  ".venv",
  "build",
  "dist",
  "*.egg-info",
]

Если вы используете tests, добавьте их в src, иначе CI может проверять иначе (если у вас в CI стоит ruff check ., это покроет всё, включая tests, а локально вы могли запускать только src).

Инкрементальная стратегия: не “сломать всё сразу”

Если проект большой и сейчас стиль неоднородный, “включить всё” сразу может быть болезненно: будут сотни/тысячи ошибок, и никто не сможет быстро довести код до нормы.

Честная стратегия:

  • Включите набор базовых правил (обычно E, F, W, плюс то, что реально исправимо).
  • Сначала настройте fixable, чтобы команда могла применять автозамены.
  • Затем постепенно расширяйте select/ужесточайте.

Например, стартовать можно так:

code
[tool.ruff.lint]
select = ["E", "F", "W"]
fixable = ["ALL"]

А затем добавить I (import sorting), UP (обновления), B (bugbear) и т.д.


Форматирование: Ruff Format не должен “ехать” относительно Black

Важно понимать роль ruff format. Если в проекте параллельно используется black, нужно определиться, кто “истина”.

В идеале Ruff Format используется как единый форматтер. Если вы оставляете black, то Ruff будет использоваться только для lint/check/fix. Но если вы хотите единообразие “как в CI”, то:

  • либо CI запускает ruff format,
  • либо CI запускает black,
  • и локально разработчик делает то же самое.

Рекомендованный сценарий: “один форматтер”

Если вы выбираете Ruff Format:

code
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "auto"

И затем в CI выполняете:

  • ruff format --check .

А локально —:

  • ruff format .

Частая ошибка: не фиксировать формат автоматически

Если CI только проверяет формат (--check), но не форматирует, то разработчик обязан держать код в форме руками. Это снова ведёт к “договорились”.

Лучшее решение для команды — связать форматирование с автозамена/фиксацией, например через pre-commit (ниже) или через единые команды в Makefile/justfile.


Исправления (fix): как сделать “автозамены” стабильными

ruff check --fix применяется для некоторых классов нарушений. Важно:

  • не все правила автоматически чинятся;
  • комбинации select/ignore влияют на набор применённых исправлений;
  • форматирование (ruff format) и исправления (ruff check --fix) лучше разделять по логике: fix меняет код под линтерные правила, format приводит к стилю.

Отдельный контроль для команды

Хорошая практика: выделить явно 2 шага:

  1. применить автозамены линтера
  2. прогнать форматтер

Пример команд:

  • ruff check --fix
  • ruff format

В CI эти шаги либо выполняются в “режиме проверки”, либо выполняются при наличии stage-брейка (часто не дают коммитить изменение автоматически, а требуют, чтобы разработчик прислал исправления).


Стандарт команд: Makefile как “контракт”

Чтобы “как в CI”, нужен не только одинаковый конфиг, но и одинаковые команды. Команда разработчиков должна быть одинаковой, иначе вы снова получите расхождение.

На практике удобнее сделать небольшую оболочку — через Makefile или justfile.

Пример Makefile

code
.PHONY: lint lint-fix format format-check ci

lint:
	ruff check .

lint-fix:
	ruff check . --fix

format:
	ruff format .

format-check:
	ruff format --check .

ci: lint format-check

Тогда локально разработчик запускает:

  • make lint-fix format
  • и затем make ci для проверки.

В CI используется ровно make ci.

Это не “магия”, а банальное устранение расхождения: одинаковые флаги и одинаковая последовательность действий.


pre-commit: гарантировать правила до CI (а не после)

Если вы хотите, чтобы код никогда не попадал в PR с очевидными нарушениями, pre-commit помогает сильно. Он запускает команды перед коммитом, снижая шум.

Пример .pre-commit-config.yaml

code
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.4
    hooks:
      - id: ruff
        args: ["check"]
      - id: ruff-format

Пара замечаний:

  • Фиксируйте rev — иначе при обновлении Ruff поведение может измениться.
  • Если у вас строгая стратегия автозамены, можно добавлять отдельный hook под ruff check --fix (но осторожно: это меняет файлы при коммите).

Нужно ли pre-commit, если CI уже есть?

Не взаимоисключающее. CI — это “последняя граница”. pre-commit — “раннее предупреждение”. В командной работе pre-commit часто снижает число циклов “почему CI ругается”.


CI: как именно добиться равенства с локальной сборкой

Суть требования “как в CI” сводится к тому, что CI должен запускать те же команды, что и вы рекомендуете разработчикам.

Пример GitHub Actions

code
name: CI

on:
  push:
  pull_request:

jobs:
  lint-format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff

      - name: Lint
        run: ruff check .

      - name: Format check
        run: ruff format --check .

Проблема, которая часто встречается: разработчики ставят ruff версии “последняя” (или из lock’а), а CI — другой версии. Тогда правила могут отличаться (особенно в новых релизах).

Решение: зафиксировать версию Ruff. Например, в requirements-dev.txt или через pip install ruff==0.x.y и обновлять осознанно.


Версионирование и согласованность: почему “одинаково” не значит “одно и то же”

Команда может иметь одинаковый pyproject.toml, но всё равно получить разные результаты из-за:

  1. Разных версий Ruff (семантика правил и автозамены могут меняться).
  2. Разных версий Python (влияет target-version и некоторые правила).
  3. Разных платформ (реже, но бывает через line endings, импорт сортировки, пути).
  4. Разных параметров запуска (сканируется разный набор файлов).

Практика: фиксируйте версию

Пример добавления в requirements-dev.txt:

code
ruff==0.6.4

И в CI, и локально устанавливайте из этого списка (или через pip-tools/uv/pdm — зависит от стека).

Практика: фиксируйте target-version

В pyproject.toml:

code
[tool.ruff]
target-version = "py311"

Это снижает разъезды между командами, когда один разработчик запускает под Python 3.10, а другой — под 3.12, и Ruff начинает по-разному оценивать совместимость.


Стратегия обновления правил без хаоса

Ruff хорош тем, что правила можно включать/выключать конфигом. Но если делать это “по желанию” каждого, получите хаос.

Рекомендованный процесс в команде

  1. Вводите новые правила через PR, где:
    • фиксируется одна версия Ruff,
    • выполняется ruff check --fix,
    • выполняется ruff format,
    • и PR содержит только необходимые изменения.
  2. В pyproject.toml используйте комментарии рядом с select/ignore (в разумных пределах), чтобы не терять смысл.
  3. Отдельно согласуйте политику по “дорогим” правилам: например, правила, требующие исправления импорта (I), могут поменять много строк.

Типичный подводный камень: ignore слишком широко

ignore = ["E501"] — нормальная локальная история. Но если вы начинаете игнорировать большие группы (например, ignore = ["ALL"] “пока не разберёмся”), проект деградирует. Лучше:

  • игнорировать точечно,
  • использовать постепенное включение,
  • и по возможности доводить код до целевого состояния.

Полезные команды для локальной отладки

Когда вы настраиваете Ruff “как в CI”, полезно иметь набор диагностических команд.

Быстро понять, какие файлы проверяются

code
ruff check --show-files .

Узнать, какие правила срабатывают (и почему)

code
ruff check . --select E,F --output-format=concise

Применить автозамены локально

code
ruff check . --fix
ruff format .

Проверить формат в “точности как CI”

code
ruff format --check .

Если вы видите расхождение между локальным запуском и CI, почти всегда причина в одном из четырёх пунктов: другая версия Ruff, другой target-version, другая команда/флаги, другой набор файлов.


Набор “правил игры”: чему учить команду

Командный процесс — не менее важная часть, чем конфиг.

Минимальный “контракт разработчика” может быть таким:

  1. Перед коммитом — pre-commit (или минимум: ruff check . и ruff format --check .).
  2. Если добавляете изменения — запускаете ruff check --fix (если используете автозамены) и затем ruff format.
  3. CI не “другая реальность”, а проверка того, что вы сделали локально.

Если команда следит за контрактом, CI становится предсказуемым, а не карательным.


Как это выглядит в конфигурации “под проект”

Соберём пример более цельного pyproject.toml, который можно взять за основу и адаптировать:

code
[tool.ruff]
line-length = 100
target-version = "py311"
src = ["src", "tests"]
exclude = ["build", "dist", ".venv", ".git"]

[tool.ruff.lint]
# Начинаем с базового набора
select = ["E", "F", "W", "I", "UP", "B"]
ignore = []
fixable = ["ALL"]

# Опционально: чтобы правила не мешали переходному периоду
# unfixable = []

[tool.ruff.lint.isort]
known-first-party = ["your_package"]

[tool.ruff.format]
quote-style = "double"

И затем в CI всегда:

  • ruff check .
  • ruff format --check .

А локально:

  • ruff check . --fix
  • ruff format .
  • ruff format --check . (опционально как контроль)

Вывод: Ruff в команде — это дисциплина конфигурации и команд

Ruff хорошо справляется с задачей “единый стиль и единые правила”. Но в команде он становится предсказуемым только при выполнении двух условий:

  1. Единый источник правды: вся логика — в конфиге (pyproject.toml), а не в случайных командах.
  2. Единый контракт запуска: локальные команды должны совпадать с тем, что делает CI (вплоть до последовательности check/fix/format и до фиксированной версии Ruff).

Если вам нужно систематизировать подход — полезно пройти структурное введение, например по теме “ruff – для начинающих!” (/course/ruff-free). Это один из способов быстрее “собрать в голове” логику линтинга/форматирования/автозамен и перестать воспринимать настройку Ruff как набор разрозненных флагов.

Когда контракт соблюдён, CI перестаёт быть местом, где “ломается стиль”, и превращается в формальную проверку того, что правила действительно одинаковы для всех.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

ruff – для начинающих!

Курс по теме

ruff – для начинающих!

С нуля до уверенной работы с линтингом и форматированием Python-кода. Подходит для начинающих и тех, кто уже пишет на Python. Разберитесь с Ruff раз и навсегда — и перестаньте гадать, какой инструмент запускать.

Бесплатно
Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

Как выбрать структуру проекта на Python: src-layout, нейминг модулей и где хранить конфиги
выбрать

Как выбрать структуру проекта на Python: src-layout, нейминг модулей и где хранить конфиги

Разберём рабочие схемы организации репозитория (src-layout vs flat), правила именования, расположение настроек/секретов и типовые ошибки, которые мешают тестированию и сборке. Поймёте, как подготовить проект под рост и CI.

7 августа 2026 г.
780
SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI
sqlmodel

SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI

Сравниваем два подхода к работе с базами данных в Python-экосистеме: где SQLModel упрощает жизнь, а где мощность SQLAlchemy незаменима. С примерами моделей, запросов и миграций.

18 июля 2026 г.
770
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1070
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Архитектура: что именно делает Ruff в проектеКонфиг “как в CI”: один источник истиныГде хранить конфигБазовый скелет pyproject.tomlЛинтинг и правила: “похожее локально” — не подходитЧто сделать, чтобы не расходилосьИнкрементальная стратегия: не “сломать всё сразу”Форматирование: Ruff Format не должен “ехать” относительно BlackРекомендованный сценарий: “один форматтер”Частая ошибка: не фиксировать формат автоматическиИсправления (fix): как сделать “автозамены” стабильнымиОтдельный контроль для командыСтандарт команд: Makefile как “контракт”Пример Makefilepre-commit: гарантировать правила до CI (а не после)Пример .pre-commit-config.yamlНужно ли pre-commit, если CI уже есть?CI: как именно добиться равенства с локальной сборкойПример GitHub ActionsВерсионирование и согласованность: почему “одинаково” не значит “одно и то же”Практика: фиксируйте версиюПрактика: фиксируйте target-versionСтратегия обновления правил без хаосаРекомендованный процесс в командеТипичный подводный камень: ignore слишком широкоПолезные команды для локальной отладкиБыстро понять, какие файлы проверяютсяУзнать, какие правила срабатывают (и почему)Применить автозамены локальноПроверить формат в “точности как CI”Набор “правил игры”: чему учить командуКак это выглядит в конфигурации “под проект”Вывод: Ruff в команде — это дисциплина конфигурации и команд