$ sudo teach IT
Модуль 4 · Конфигурация

per-file-ignores и noqa

Исключения для конкретных файлов и строк.

⚙️ Конфигурация 🕓 ~20 минут
📁

per-file-ignores — правила для конкретных файлов

Параметр per-file-ignores позволяет отключать определённые правила для файлов, соответствующих glob-паттерну. Это более гибкий аналог глобального ignore, действующий только для указанных файлов.

Синтаксис: ключ — glob-паттерн файлов, значение — список правил для отключения:

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "E501"]
"migrations/*.py" = ["ALL"]
"settings.py" = ["S105", "S106"]

Типичные сценарии использования

Паттерн Правила Почему
"__init__.py" ["F401"] Реэкспорт в __init__.py — частая практика, импорт используется как public API
"tests/**/*.py" ["S101", "E501", "ANN"] В тестах assert обязателен, длинные строки нормальны, аннотации необязательны
"*/migrations/*.py" ["ALL"] Миграции генерируются автоматически, их правка только сломает Alembic
"**/settings*.py" ["S105", "S106"] В конфигах часто используются hardcoded значения для локальной разработки
"**/conftest.py" ["D"] Фикстуры в conftest редко документируются
"scripts/**/*.py" ["ANN", "D"] Одноразовые скрипты не требуют документации и аннотаций
"**/docs/**/*.py" ["D"] Примеры в документации — не основной код

Glob-паттерны: подробности

Ruff использует glob-паттерны для сопоставления файлов. Вот как они работают:

Паттерн Совпадение
"__init__.py" Только файл __init__.py в корне проекта
"src/__init__.py" Только src/__init__.py
"tests/**/*.py" Любой .py файл в tests/ и всех подпапках
"**/migrations/*.py" Любой .py файл в папке migrations на любом уровне вложенности
"*.py" Все .py файлы в корне (не рекурсивно)
⚠️ Важно: Если вы укажете "__init__.py" без пути, паттерн сработает только для файла в корне проекта. Чтобы охватить все __init__.py, используйте "**/__init__.py".

Полный пример конфигурации с per-file-ignores

[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "S", "ANN"]
ignore = ["E501"]

[tool.ruff.lint.per-file-ignores]
# в тестах разрешены assert и длинные строки
"tests/**/*.py" = ["S101", "ANN", "D"]

# в миграциях отключено всё — они автосгенерированы
"*/migrations/*.py" = ["ALL"]

# в __init__.py разрешён реэкспорт
"**/__init__.py" = ["F401"]

# в conftest не нужны аннотации
"**/conftest.py" = ["ANN"]

# в конфигах можно hardcoded значения
"**/settings*.py" = ["S105", "S106"]

# в скриптах не обязательны аннотации
"scripts/**/*.py" = ["ANN", "D"]

# в примерах документации не нужна документрация
"**/docs/**/examples/**/*.py" = ["D", "ANN"]
📄

Синтаксис в разных конфигах

pyproject.toml

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "E501"]

ruff.toml

[lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "E501"]
💡 Отличие: В pyproject.toml нужен префикс tool.ruff.lint, в ruff.toml достаточно lint. Это стандартная разница между TOML-конфигом и PEP 621.
💬

noqa — отключить для строки

Комментарий # noqa на строке кода отключает проверки Ruff для этой строки. Это самый точный способ подавления предупреждений — он действует только на одну строку и не затрагивает остальной код.

Форматы noqa

Формат Пример Что отключает
Без кода # noqa Все правила для этой строки
Одно правило # noqa: F401 Только правило F401 для этой строки
Несколько правил # noqa: F401, E302 Правила F401 и E302 для этой строки
import os  # noqa: F401               # отключить одно правило
import sys  # noqa: F401, E302        # несколько правил
import json  # noqa                   # отключить все правила для строки

x = 1  # noqa: E501                  # длинная строка, но тут коротко

class MyClass:  # noqa: N801          # разрешить имя не в CamelCase
    pass
⚠️ Предупреждение: Используйте # noqa без кода правила — плохая практика. Всегда указывайте конкретный код, чтобы было понятно, что именно отключено и почему. Это облегчает код-ревью и будущее обслуживание.

Правильный способ использования noqa

Хорошая практика — комментировать причину отключения:

# Хорошо: указан код и причина
import os  # noqa: F401 — используется в type: ignore

# Плохо: без кода — непонятно что отключено
import os  # noqa

# Плохо: без причины — придётся гадать на код-ревью
import os  # noqa: F401
💡 Совет: noqa должен стоять в той же строке, что и проверяемый код. Если код перенесён на следующую строку (например, из-за длины), noqa не сработает. В таком случае используйте per-file-ignores или блоковый noqa.
📝

# ruff: noqa — отключить для всего файла

Специальный комментарий # ruff: noqa в первой строке файла (или сразу после shebang) отключает проверки для всего файла. Это удобнее и чище, чем per-file-ignores для отдельных случаев.

# ruff: noqa                    # отключить все правила в файле

# ruff: noqa: E501              # отключить E501 во всём файле

# ruff: noqa: E501, F401        # несколько правил для всего файла

# ruff: noqa: E501, F401, N801  # комбинированный

Когда использовать # ruff: noqa вместо per-file-ignores:

Сценарий Что лучше Почему
Один файл с исключением # ruff: noqa Локальное решение, не требует правки конфига
Много файлов по паттерну per-file-ignores Одна запись в конфиге вместо кучи комментариев
Временное отключение # ruff: noqa Легко найти и убрать позже
⚠️ Важно: # ruff: noqa должен быть в самой первой строке файла. Если первой строкой идёт shebang (#!/usr/bin/env python), то # ruff: noqa должен быть второй строкой.
🔄

isort action comments

Ruff поддерживает комментарии isort для управления группировкой импортов. Это позволяет контролировать, как Ruff (в режиме isort) обрабатывает импорты в конкретных файлах.

Комментарий Описание
# isort: skip_file Пропустить весь файл при сортировке импортов
# isort: skip Пропустить следующую строку импорта
# isort: split Принудительно разделить группы импортов
# isort: dont-add-to-section Не добавлять импорт в секцию
# isort: skip_file
import os
import sys
# Этот файл не будет сортироваться isort
import os
import sys

# isort: split — принудительное разделение

import django  # эта группа не смешается с stdlib
import requests
# isort: skip — пропустить один импорт
import os
import sys  # isort: skip  # этот импорт не будет переставлен
import json
💡 Совет: # isort: split полезен, когда нужно явно отделить, например, внутренние импорты от библиотечных, если Ruff неверно определяет их тип.
🔎

RUF100 — обнаружение лишних noqa

Правило RUF100 (Ruff-specific) находит # noqa комментарии, которые стали не нужны — то есть правила, которые они отключают, больше не срабатывают на этой строке. Это помогает поддерживать код в чистоте и не накапливать мёртвые подавления.

Как это работает

Когда правило перестаёт срабатывать на строке (например, вы исправили код, обновили конфиг или правило было удалено), # noqa для этого правила становится избыточным. RUF100 сообщит об этом:

import os  # noqa: F401

# Если F401 больше не активен (правило отключено в конфиге),
# Ruff выдаст: RUF100 Unused noqa directive 'F401'

Автоматическое удаление лишних noqa

Ruff может автоматически удалять ненужные noqa-комментарии:

# Запустить проверку + автоудаление лишних noqa
ruff check --fix --unsafe-fixes

# Только RUF100
ruff check --select RUF100 --fix
⚠️ Важно: Исправление RUF100 считается небезопасным (unsafe), так как удаление комментария может изменить поведение проверки. Всегда используйте --unsafe-fixes для автоисправления RUF100.

Типичные причины появления лишних noqa

  1. Правило было отключено в конфиге (через ignore), но noqa остался.
  2. Правило было удалено из select, но noqa остался.
  3. Код был изменён и больше не нарушает правило.
  4. Обновление Ruff — правило перестало существовать или было переименовано.
# Пример: правило отключено через ignore
# [tool.ruff.lint]
# ignore = ["F401"]

import os  # noqa: F401   # RUF100: это больше не нужно — F401 и так отключён
➕

--add-noqa — автоматическое добавление noqa

Флаг --add-noqa автоматически добавляет # noqa: {rule} ко всем строкам, где Ruff находит нарушения. Это полезно, когда вы внедряете Ruff в существующий проект и хотите сначала заглушить все ошибки, а потом исправлять их постепенно.

# Добавить noqa ко всем текущим нарушениям
ruff check --add-noqa

# Добавить noqa только для конкретного правила
ruff check --add-noqa --select F401

# Добавить noqa для конкретной директории
ruff check src/ --add-noqa

После добавления noqa, код будет чист с точки зрения Ruff, и вы сможете постепенно удалять noqa по мере исправления кода:

# До --add-noqa:
import os
import sys

# После --add-noqa:
import os  # noqa: F401
import sys  # noqa: F401
⚠️ Важно: --add-noqa добавляет noqa только для правил, которые активны в вашей конфигурации на момент запуска. Если вы потом добавите новые правила, старые noqa могут стать неполными.

Стратегия внедрения Ruff в legacy проект

  1. Установите Ruff и настройте select минимально (например, E, F, I).
  2. Запустите ruff check --add-noqa — все текущие ошибки будут заглушены noqa.
  3. Настройте pre-commit с Ruff.
  4. Теперь Ruff будет проверять только новый код (старые ошибки заглушены).
  5. Постепенно исправляйте старые noqa (RUF100 подсветит неактуальные).
  6. Добавляйте новые категории правил по одной.
# Шаг 1: минимальная конфигурация
[tool.ruff.lint]
extend-select = ["E", "F", "I"]

# Шаг 2: заглушить все ошибки
# ruff check --add-noqa

# Шаг 3: починить код и удалить noqa
# ruff check --fix

# Шаг 4: добавить больше правил
[tool.ruff.lint]
extend-select = ["E", "F", "I", "N", "UP"]

# Шаг 5: снова заглушить новые ошибки и повторять
📋

Блоковые noqa (не поддерживается)

⚠️ Важно: В отличие от Flake8, Ruff не поддерживает блоковые noqa-комментарии (например, # noqa: disable / # noqa: enable). Используйте # ruff: noqa для файла целиком или # noqa построчно.

Если вам нужно отключить проверку для нескольких строк подряд без # noqa на каждой строке, есть два варианта:

  1. Добавить # ruff: noqa в начало файла — отключит всё в файле.
  2. Вынести проблемный код в отдельный файл и настроить per-file-ignores.
🔖

Приоритет подавления правил

Когда вы используете несколько механизмов подавления, работает следующий приоритет (от высшего к низшему):

  1. # noqa на конкретной строке — имеет наивысший приоритет.
  2. # ruff: noqa в начале файла — отключает для всего файла.
  3. per-file-ignores — настройка в конфиге для паттерна файлов.
  4. ignore — глобальное отключение правил.
  5. select / extend-select — какие правила вообще включены.

Это означает, что:

  • # noqa: F401 переопределит select = ["F401"] (правило включено, но noqa отключает).
  • per-file-ignores переопределит глобальный select для конкретных файлов.
  • Если правило не входит в select, никакой noqa для него не нужен.
📈

Сводная таблица методов подавления

Метод Уровень Приоритет Пример
noqa строковый Строка Высший import os # noqa: F401
ruff: noqa файловый Файл Высокий # ruff: noqa: E501
per-file-ignores Паттерн файлов Средний "tests/*.py" = ["S101"]
ignore Глобальный Низкий ignore = ["E501"]
isort action Файл/строка Средний # isort: skip
⚠

Частые ошибки и их решения

❌ Ошибка: Я написал # noqa:F401 (без пробела), но Ruff не игнорирует правило.
✅ Решение: Ruff ожидает пробел после # noqa:. Правильный формат: # noqa: F401.
❌ Ошибка: Я добавил # ruff: noqa: F401 в середине файла, но это не работает.
✅ Решение: # ruff: noqa работает только в первой строке файла (или второй, если первая — shebang). В середине файла этот комментарий игнорируется.
❌ Ошибка: Ruff не видит мой per-file-ignores для __init__.py в подпапке.
✅ Решение: Паттерн "__init__.py" (без пути) совпадает только с файлом в корне. Используйте "**/__init__.py" для всех вложенных.
❌ Ошибка: Я запустил --add-noqa, но noqa не добавились.
✅ Решение: Убедитесь, что правила активны (входят в select) и действительно находят нарушения. Попробуйте сначала ruff check . без --add-noqa — если нет нарушений, то и добавлять нечего.
❌ Ошибка: У меня много лишних noqa в проекте, как найти их все?
✅ Решение: Запустите ruff check --select RUF100 — он покажет все неиспользуемые noqa. Затем ruff check --fix --unsafe-fixes --select RUF100 — удалит их.
🛠

Практические примеры

Пример 1: Django проект

[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "S", "DJ"]

[tool.ruff.lint.per-file-ignores]
"*/migrations/*.py" = ["ALL"]
"*/tests/**/*.py" = ["S101", "DJ"]
"**/settings.py" = ["S105", "S106"]
"**/admin.py" = ["F401", "D"]
"**/models.py" = ["D"]

Пример 2: FastAPI проект

[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "ANN"]

[tool.ruff.lint.per-file-ignores]
"**/schemas.py" = ["B008"]       # Pydantic использует Field(...) в дефолтах
"**/dependencies.py" = ["B008"]  # FastAPI зависимости
"**/routers/**/*.py" = ["ANN"]   # В роутерах аннотации необязательны
"tests/**/*.py" = ["ANN", "S101"]
"**/config.py" = ["S105"]

Пример 3: Пакет с __init__.py реэкспортами

# my_package/__init__.py
from my_package.core import MyClass  # noqa: F401 — реэкспорт для внешнего API
from my_package.utils import helper  # noqa: F401

__all__ = ["MyClass", "helper"]

Или через per-file-ignores:

[tool.ruff.lint.per-file-ignores]
"**/__init__.py" = ["F401"]

Пример 4: Проект с Jupyter ноутбуками

[tool.ruff.lint]
select = ["E", "F", "I"]

[tool.ruff.lint.per-file-ignores]
"**/notebooks/**/*.py" = ["ALL"]   # экспортированные ноутбуки
"**/experiments/**/*.py" = ["ALL"] # экспериментальный код
📝

Итоги

💡 Главные выводы:
  1. per-file-ignores — для целых категорий файлов (тесты, миграции, __init__.py).
  2. # noqa — для конкретных строк, всегда указывайте код правила.
  3. # ruff: noqa — для целого файла (в первой строке).
  4. RUF100 находит устаревшие noqa — регулярно чистите их.
  5. --add-noqa помогает быстро заглушить ошибки в legacy-коде.
  6. isort action comments управляют сортировкой импортов.
  7. Блоковые noqa (# noqa: disable) в Ruff не поддерживаются.

Урок 4.3: per-file-ignores и noqa

5 вопросов