Опирается на правила:
PY-RUFF-1,PY-RUFF-2,PY-RUFF-3,PY-RUFF-4,PY-RUFF-X1из Python Style Guide → раздел 9. Enforcement.
Важно знать
ruffобязателен на всех Python-сервисах; конфигурация — вpyproject.toml, не в разрозненных.flake8/.isort.cfg.mypy --strictзапускается в CI; ослабление — только per-module override с обоснованием вpyproject.toml.ruff check+ruff format --check+mypy— на каждом CI-прогоне; любое нарушение — fail.# noqaи# type: ignoreдопустимы только с кодом правила и justify-комментарием.ruffпокрывает механику: нейминг (PY-2.*), импорты (PY-3.*), формат (PY-5.*); в findings review эти нарушения не дублируются.- Семантику (
PY-4.*,PY-6.*,PY-7.*,PY-8.*) ловитucp-py-style-review, не инструменты.- Отключение правил «потому что мешают» без командного обсуждения — источник расхождения conventions между сервисами.
ruff заменяет flake8, isort, pep8, pyflakes и black одним инструментом. mypy --strict закрывает дыры в типах, которые ruff не видит. Вместе они формируют детерминированный барьер на CI: всё, что ловится механически, ловится автоматически — ревью остаётся для семантики.
Конфигурация в pyproject.toml
PY-RUFF-1: единственный допустимый источник конфигурации — pyproject.toml. Никаких .flake8, .isort.cfg, setup.cfg рядом.
[tool.ruff]
line-length = 120
[tool.ruff.lint]
select = [
"E",
"W",
"F",
"I",
"N",
"B",
"UP",
"S",
"RUF",
]
ignore = []
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
line-length = 120 — override от дефолтного 88 (см. PY-5.2); team-дефолт 88 или 120 фиксируется однократно и применяется везде. Смешивать разные значения между сервисами — PY-RUFF-X1.
Запуск в CI
PY-RUFF-3: три проверки запускаются в CI как единый барьер.
# .github/workflows/ci.yml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install ruff mypy
- run: ruff check .
- run: ruff format --check .
- run: mypy .
ruff check — нарушения lint-правил. ruff format --check — форматирование без изменения файлов; при расхождении — non-zero exit. mypy — типовая проверка. Все три — отдельные шаги: при падении первого второй не запускается, что ускоряет обратную связь.
Локально перед коммитом:
ruff check . --fix
ruff format .
mypy .
--fix применяет автоисправления для правил, где это безопасно (исправление импортов, удаление unused, pyupgrade). Форматтер вносит изменения in-place.
Разделение зон ответственности
PY-RUFF-4: ruff — механика, ucp-py-style-review — семантика.
| Зона | Инструмент | Примеры |
|---|---|---|
Нейминг (PY-2.*) | ruff (набор N) | snake_case модулей, PascalCase классов, UPPER_SNAKE_CASE констант |
Импорты (PY-3.*) | ruff (наборы F, I) | порядок групп, unused, wildcard |
Формат (PY-5.*) | ruff format | отступы, длина строки, trailing comma |
Код (PY-4.*) | ucp-py-style-review | guard clause, mutable default, exception без re-raise |
Типы (PY-6.*) | mypy, ucp-py-style-review | X | None вместо Optional, Protocol, Decimal |
Комментарии (PY-7.*) | ucp-py-style-review | inline-комментарии в коде, noqa без justify |
Современные фичи (PY-8.*) | ucp-py-style-review | match/case, @dataclass(frozen=True, slots=True) |
Findings review никогда не цитируют нарушения E/W/F/I/N — всё это уже поймал ruff до merge.
mypy --strict и per-module override
PY-RUFF-2: mypy --strict — базовая строгость. Флаги strict включают:
--disallow-untyped-defs
--disallow-incomplete-defs
--check-untyped-defs
--disallow-any-generics
--warn-redundant-casts
--warn-unused-ignores
--no-implicit-reexport
Сервис с новым кодом идёт сразу в strict = true. Если подключается существующая база без аннотаций — ослабление per-module с явным обоснованием:
[tool.mypy]
strict = true
[[tool.mypy.overrides]]
module = ["payment_gateway.*", "third_party_client.*"]
ignore_missing_imports = true
disallow_untyped_defs = false
Комментарий в pyproject.toml объясняет причину: «third_party_client не имеет stubs, обновить после добавления py.typed». Без обоснования — нарушение PY-RUFF-2.
noqa и type: ignore
PY-RUFF-3, PY-7.X2: # noqa и # type: ignore допустимы только с кодом и justify.
result = external_sdk.call() # type: ignore[no-any-return] # SDK не имеет py.typed, стабов нет — #123
from sber_notify_client import helper # noqa: F401 # используется в __init__.py через re-export
Запрещённые формы:
result = external_sdk.call() # type: ignore
from sber_notify_client import helper # noqa
Без кода правила подавление неотличимо от «я не разобрался» — ревьюер не может оценить, правомерно ли оно. warn_unused_ignores = true в mypy автоматически снимает устаревшие # type: ignore, когда тип исправлен.
Запрет разрозненных конфигов
PY-RUFF-X1: отключение правил без командного обсуждения — расхождение conventions.
[tool.ruff.lint]
ignore = [
"E501",
"N806",
]
Если правило мешает на конкретном файле по объективной причине (автогенерация, proto-output, seed-данные) — # noqa: <code> с justify на строке или per-file-ignores:
[tool.ruff.lint.per-file-ignores]
"migrations/**" = ["N806", "E501"]
"tests/fixtures/**" = ["S101"]
Разрозненные .flake8 или setup.cfg рядом с pyproject.toml — удалить; ruff читает только pyproject.toml.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
.flake8, .isort.cfg, setup.cfg для lint/format | PY-RUFF-1 | [tool.ruff] в pyproject.toml |
mypy без strict = true в новом сервисе | PY-RUFF-2 | strict = true; ослабление — per-module с justify |
ruff check без mypy в CI | PY-RUFF-3 | оба инструмента в каждом прогоне |
# type: ignore без кода и комментария | PY-RUFF-3, PY-7.X2 | # type: ignore[no-any-return] # SDK без stubs — #issue |
# noqa без кода правила | PY-RUFF-3, PY-7.X2 | # noqa: F401 # justify: ... |
ignore = ["N806"] без обоснования в pyproject.toml | PY-RUFF-X1 | обсуждение в команде + комментарий |
line-length разный в разных сервисах | PY-RUFF-X1 | единый дефолт (88 или 120) через shared config |
Куда дальше
- Форматирование — длина строки, перенос, горизонтальное выравнивание (
PY-5.*). - Именование — что ловит
ruffнаборомN, а что остаётся на review (PY-2.*). - Выражения — guard clause, mutable default, comprehension vs. цикл (
PY-4.*). - Раздел «SAST по коду» —
ruff S-правила как часть security-барьера.