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

pyproject.toml и ruff.toml

Файлы конфигурации — где живут настройки Ruff.

⚙️ Конфигурация 🕐 ~30 минут
🗺️

Обзор конфигурации Ruff

Ruff поддерживает три формата конфигурационных файлов: pyproject.toml, ruff.toml и .ruff.toml. Все они используют TOML-синтаксис и позволяют централизованно управлять настройками линтинга и форматирования.

Конфигурация Ruff включает в себя несколько логических секций:

  • Общие настройки — line-length, exclude, include, src
  • Линтинг — [tool.ruff.lint]: select, ignore, extend-select
  • Форматирование — [tool.ruff.format]: quote-style, indent-style
  • Per-file ignores — [tool.ruff.lint.per-file-ignores]
  • Наследование — extend для заимствования настроек из другого файла

Все настройки можно переопределить через CLI-флаги, но конфигурационный файл — наиболее удобный и воспроизводимый способ.

📄

pyproject.toml — стандартный подход

Если в проекте уже есть pyproject.toml (стандартный конфигурационный файл для современных Python-проектов), настройки Ruff добавляются в него через секцию [tool.ruff]. Это наиболее рекомендуемый подход, так как он консолидирует всю конфигурацию проекта в одном файле.

pyproject.toml — полная конфигурация
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.9"

# --- Настройки Ruff ---
[tool.ruff]
line-length = 88
target-version = "py311"
exclude = ["migrations/", "build/", "*.generated.py"]
include = ["*.py", "*.pyi"]

[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B"]
ignore = ["E501", "D100"]
extend-select = ["SIM"]

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

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

Преимущества использования pyproject.toml:

  • Единый файл конфигурации для всего проекта
  • Поддерживается всеми современными Python-инструментами
  • Легко обнаруживается (стандартный путь поиска)
  • Меньше файлов в корне проекта
📋

ruff.toml — отдельный файл

Если вы предпочитаете не смешивать конфигурацию разных инструментов, используйте отдельный ruff.toml (или скрытую версию .ruff.toml). Синтаксис такой же, но без префикса [tool.ruff] — настройки идут напрямую:

ruff.toml
line-length = 88
target-version = "py311"
exclude = ["migrations/", "build/"]
include = ["*.py", "*.pyi"]

[lint]
select = ["E", "F", "I", "N", "UP", "B"]
ignore = ["E501"]
extend-select = ["SIM"]

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

[format]
quote-style = "double"
indent-style = "space"
magic-trailing-comma = true

Преимущества отдельного ruff.toml:

  • Не загрязняет pyproject.toml
  • Легко найти и редактировать
  • Может быть расположен в поддиректории (если нужно)
  • Меньше вложенности в TOML (.tool.ruff.lint vs [lint])
💡 ruff.toml и .ruff.toml — это одно и то же. Ruff ищет оба. Выбор зависит от ваших предпочтений: некоторые предпочитают скрытые файлы (начинающиеся с точки), чтобы не загромождать корень проекта.
🏗️

Структура секций в pyproject.toml

Секция в pyproject.toml Секция в ruff.toml Описание
[tool.ruff] (root) Общие настройки: line-length, exclude, include, src
[tool.ruff.lint] [lint] Настройки линтинга: select, ignore, extend-select
[tool.ruff.lint.per-file-ignores] [lint.per-file-ignores] Игнорирование правил для конкретных файлов/папок
[tool.ruff.format] [format] Настройки форматтера: quote-style, indent-style
[tool.ruff.lint.extend-safe-fixes] [lint.extend-safe-fixes] Расширение списка safe-fixes (продвинутая настройка)
⚡

Полная конфигурация по умолчанию

Вот как выглядит полная конфигурация Ruff со всеми значениями по умолчанию. Это полезно как справочник и отправная точка для настройки:

# ruff.toml — полный default
line-length = 88
indent-width = 4
target-version = "py38"  # минимальная версия Python проекта
preview = false
required-version = "0.6.0"  # минимальная версия Ruff

# Какие файлы включать/исключать
include = ["*.py", "*.pyi", "*.ipynb"]  # с preview
exclude = [
    ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite",
    ".hg", ".ipynb_checkpoints", ".mypy_cache", ".nox",
    ".pants.d", ".pyenv", ".pytest_cache", ".ruff_cache",
    ".svn", ".tox", ".venv", ".vscode", "__pypackages__",
    "_build", "buck-out", "build", "dist", "node_modules",
    "site-packages", "venv"
]
src = ["."]  # корневые директории для разрешения импортов

# Настройки линтинга
[lint]
select = ["E", "F", "I"]  # по умолчанию
ignore = []
extend-select = []
fixable = ["ALL"]
unfixable = []

[lint.per-file-ignores]

[lint.extend-safe-fixes]

[lint.extend-unsafe-fixes]

# Настройки форматтера
[format]
indent-style = "space"
quote-style = "double"
magic-trailing-comma = true
docstring-code-format = false
docstring-code-line-length = "dynamic"
line-ending = "auto"
🔍

Поиск конфигурационного файла (дисковери)

Ruff использует иерархический поиск конфигурационного файла. Процесс выглядит так:

  1. Начинает с директории проверяемого файла
  2. Ищет ruff.toml, затем .ruff.toml, затем pyproject.toml
  3. Если не нашёл — поднимается на уровень выше (в родительскую директорию)
  4. Повторяет до корня файловой системы
  5. Если ничего не нашёл — использует значения по умолчанию

Если Ruff находит конфигурационный файл, он прекращает поиск и использует найденный. Первый найденный файл побеждает (не объединяет с найденными выше).

Пример иерархического поиска:

# Структура проекта:
/project/
  pyproject.toml         # <- конфиг для всего проекта
  src/
    main.py              # Ruff ищет: .ruff.toml -> ruff.toml -> pyproject.toml
    subpackage/
      ruff.toml          # <- переопределяет настройки для подпакета
      module.py           # Ruff ищет: ruff.toml (находит сразу)
  tests/
    .ruff.toml            # <- отдельные настройки для тестов
    test_main.py          # Ruff ищет: .ruff.toml (находит сразу)
    test_subpackage/
      test_module.py      # наследует .ruff.toml из tests/
💡 Чтобы увидеть, какой конфиг используется, запустите ruff check --show-settings . — в начале вывода будет путь к найденному конфигурационному файлу.
🥇

Приоритет конфигурации

Ruff ищет конфигурационный файл в следующем порядке (первый найденный побеждает):

  1. ruff.toml (наивысший приоритет)
  2. .ruff.toml
  3. pyproject.toml (секция [tool.ruff])

А вот полная иерархия приоритетов всех настроек (от наивысшего к низшему):

Приоритет Источник Пример
1 (наивысший) CLI аргументы ruff check --select ALL --ignore E501
2 Локальный ruff.toml (рядом с файлом) src/subpackage/ruff.toml
3 Локальный .ruff.toml src/subpackage/.ruff.toml
4 Локальный pyproject.toml src/subpackage/pyproject.toml
5 Родительский ruff.toml project/ruff.toml
6 Родительский .ruff.toml project/.ruff.toml
7 Родительский pyproject.toml project/pyproject.toml
8 (низший) Default значения line-length=88, select=E,F,I и т.д.

Важно: Если в одной директории найдены и ruff.toml, и pyproject.toml — ruff.toml имеет приоритет. pyproject.toml будет проигнорирован (даже если в нём есть настройки Ruff).

⚠️ Будьте осторожны с иерархическими конфигами. Если у вас есть ruff.toml в корне проекта и pyproject.toml в поддиректории с настройками Ruff, pyproject.toml будет найден первым и переопределит корневой конфиг.
🔗

extend — наследование конфигурации

Ключ extend позволяет наследовать настройки из другого конфигурационного файла. Это полезно, когда у вас есть базовая конфигурация для всего проекта и специализированные для подпроектов или окружений.

# ruff.toml (корень проекта) — базовый конфиг
line-length = 88
target-version = "py311"

[lint]
select = ["E", "F", "I", "N", "UP", "B"]
ignore = ["E501"]

[format]
quote-style = "double"


# src/api/ruff.toml — переопределяет некоторые настройки
extend = "../../ruff.toml"  # наследуем базовый конфиг

[lint]
# добавляем правила для API
select = ["E", "F", "I", "N", "UP", "B", "S"]  # S — безопасность
ignore = ["E501", "D"]


# tests/ruff.toml — другие настройки для тестов
extend = "../ruff.toml"

[lint]
select = ["E", "F", "I", "N"]
ignore = ["E501", "D", "S101"]  # S101 — assert used (отключаем для тестов)

Правила наследования через extend:

  • Наследуются все настройки из указанного файла
  • Настройки в текущем файле переопределяют унаследованные
  • Путь к файлу может быть относительным или абсолютным
  • extend не поддерживает цепочки длиннее одного уровня (нельзя A -> B -> C)
💡 extend — отличный способ организовать конфигурацию для монорепозиториев, где разные пакеты требуют разных правил линтинга.
⌨️

--config — указание конфига через CLI

Флаг --config позволяет указать путь к конфигурационному файлу явно. Это полезно в CI/CD, pre-commit хуках или при использовании временной конфигурации.

# использовать конкретный конфиг
ruff check --config /path/to/ruff.toml .
ruff format --config /path/to/ruff.toml .

# использовать pyproject.toml из другой директории
ruff check --config ../shared/pyproject.toml .

# можно использовать для разных окружений
ruff check --config ruff.prod.toml --select ALL .
ruff check --config ruff.dev.toml --select E,F,I .

# --config переопределяет поиск конфига (используется только указанный файл)
# полезно в CI:
ruff check --config .github/linters/ruff.toml .

Когда вы используете --config, Ruff не выполняет автоматический поиск. Используется только указанный файл. Если в нём нет каких-то настроек — применяются значения по умолчанию.

# Использование --config в GitHub Actions:
- name: Lint with custom config
  run: |
    ruff check --config .github/ruff-ci.toml --output-format github .

# Использование в pre-commit:
- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.6.0
  hooks:
    - id: ruff
      args: [--config, pyproject.lint.toml]
💡 Если у вас несколько веток с разными версиями Ruff, --config позволяет держать разные конфиги для каждой ветки без merge conflicts.
📦

Установка Ruff

Ruff можно установить разными способами, в зависимости от вашей платформы и предпочтений:

Метод Команда Примечание
pip pip install ruff Стандартный способ, pre-built wheels
pipx pipx install ruff Изолированная установка
conda conda install ruff -c conda-forge Anaconda/Miniconda
Homebrew (macOS) brew install ruff macOS
Cargo cargo install ruff Из исходников (требуется Rust)
GitHub Releases Скачать бинарник с GitHub Для CI: curl -LsSf ... | sh
# установка через pip
pip install ruff

# проверка установки
ruff --version

# установка конкретной версии
pip install ruff==0.6.0

# для разработки (из репозитория)
git clone https://github.com/astral-sh/ruff
cd ruff
cargo build --release

# установка через Astral's install script (для CI)
curl -LsSf https://astral.sh/ruff/install.sh | sh

# GitHub Actions — готовый action
- uses: astral-sh/ruff-action@v2
💡 Для максимальной производительности используйте pre-built wheels (pip автоматически их скачивает). На macOS можно через Homebrew. Для CI используйте официальный GitHub Action: astral-sh/ruff-action.
✅

Проверка конфигурации

После настройки конфигурации полезно проверить, что Ruff её правильно читает:

# показать активные настройки
ruff check --show-settings .

# показать какие файлы будут проверяться
ruff check --show-files .

# проверить, что конфиг валидный (любая команда с --help)
ruff check --help | grep -A 5 "Config"

# проверить версию Ruff
ruff --version

Если Ruff не находит конфиг или использует не те настройки, проверьте:

  1. Правильно ли указан путь к конфигу (--config)
  2. Не переопределяется ли конфиг вложенным ruff.toml
  3. Правильно ли написаны секции (tool.ruff vs tool.ruff.lint)
  4. Нет ли синтаксических ошибок в TOML (можно проверить через python -m toml)
⭐

Best Practices

  1. Используйте pyproject.toml для новых проектов — это современный стандарт
  2. Зафиксируйте версию Ruff в зависимостях: ruff = ">=0.6.0,<0.7.0"
  3. Не храните конфиги в нескольких местах — выберите один файл для всего проекта
  4. Используйте extend для монорепозиториев — базовая конфигурация в корне, переопределения в подпакетах
  5. Проверяйте конфиг через --show-settings после изменений
  6. Документируйте неочевидные решения — если отключаете правило, оставьте комментарий почему
  7. Начинайте с минимального набора — добавляйте правила постепенно
# Пример хорошо документированного конфига:
[tool.ruff]
line-length = 100  # разрешаем 100 из-за длинных имён в Django

[tool.ruff.lint]
select = [
    "E", "F", "I",     # обязательные: стиль, ошибки, сортировка
    "N",               # именование
    "UP",              # современный синтаксис
    "B",               # баги
    "S",               # безопасность
]
ignore = [
    "E501",  # отключаем длину строк — используем ruff format
    "D",     # документация — пока не готовы
]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]  # в __init__ часто реэкспорты
"migrations/**" = ["ALL"]  # миграции генерируются, не проверяем
📋

Резюме

Аспект Детали
Форматы конфигов pyproject.toml, ruff.toml, .ruff.toml
Поиск конфига Иерархический: от директории файла до корня
Приоритет CLI > локальный ruff.toml > родительский pyproject.toml > default
--config Явное указание конфига, отключает поиск
extend Наследование из другого конфига (1 уровень)
📖 Документация: docs.astral.sh/ruff/configuration/
✅

Валидация конфигурации

Ruff автоматически проверяет конфигурационный файл на корректность при запуске. Если в TOML есть синтаксическая ошибка или неизвестная опция, Ruff сообщит об ошибке и завершится с ненулевым кодом возврата.

Примеры ошибок в конфиге:

# Ошибка: неизвестная опция
[tool.ruff]
line-lenght = 88  # опечатка: должно быть line-length

# Ошибка: неверное значение
[tool.ruff.lint]
select = ["E", "F", "ZZZZ"]  # ZZZZ — неизвестное правило

# Ошибка: неверный тип
[tool.ruff]
line-length = "long"  # должно быть число

# Ошибка: неверная секция
[tool.ruff.lint]
select = ["E", "F"]
severity = ["E:error", "W:warning"]  # неверная опция

Ruff выдаст понятное сообщение об ошибке с указанием строки и проблемы в конфигурационном файле.

🌍

Конфигурация для разных окружений

Вы можете иметь разные конфигурации для разработки, CI и продакшена:

# ruff.dev.toml — для локальной разработки (максимум правил)
line-length = 88

[lint]
select = ["ALL"]  # все правила
ignore = ["E501", "D"]

[format]
quote-style = "double"

# ruff.ci.toml — для CI (строгие проверки)
line-length = 88

[lint]
select = ["E", "F", "I", "N", "UP", "B", "S"]
ignore = ["E501"]
fixable = []  # в CI не применяем исправления

[format]
quote-style = "double"

# Запуск:
ruff check --config ruff.dev.toml .   # локально
ruff check --config ruff.ci.toml .    # в CI

Этот подход позволяет иметь разные стандарты для разных окружений без дублирования кода.

🏢

Конфигурация для монорепозитория

Монорепозитории часто содержат несколько Python-пакетов с разными требованиями к стилю. Вот как организовать конфигурацию:

# Структура:
# monorepo/
#   ruff.toml              # базовый конфиг
#   packages/
#     core/
#       ruff.toml          # extends ../ruff.toml + свои правила
#       src/
#     api/
#       ruff.toml          # extends ../ruff.toml + безопасность
#       src/
#     scripts/
#       ruff.toml          # extends ../ruff.toml + более строгие

# monorepo/ruff.toml (базовый):
line-length = 100

[lint]
select = ["E", "F", "I", "N"]
ignore = ["E501"]

[format]
quote-style = "double"

# packages/api/ruff.toml:
extend = "../../ruff.toml"

[lint]
select = ["E", "F", "I", "N", "S", "B"]  # добавляем безопасность
ignore = ["E501"]

# packages/scripts/ruff.toml:
extend = "../../ruff.toml"

[lint]
select = ["E", "F"]  # минимум для скриптов
ignore = ["E501", "N"]
fixable = ["ALL"]

Теперь при запуске ruff check packages/api/src/ Ruff найдёт packages/api/ruff.toml и использует его с наследованием от корневого конфига.

💡

Советы и хитрости

  • required-version — добавьте required-version = "0.6.0" в конфиг, чтобы предотвратить запуск с неподходящей версией Ruff. Если версия не совпадает, Ruff завершится с ошибкой.
  • src настройка — если ваш проект использует src/ layout, укажите src = ["src"]. Это поможет Ruff правильно разрешать импорты.
  • target-version — укажите минимальную версию Python: target-version = "py311". Ruff будет предлагать только те фичи, которые доступны в этой версии.
  • exclude кэша — Ruff по умолчанию исключает .ruff_cache. При использовании ruff clean кэш очищается.
  • Проверка синтаксиса TOML — используйте python -m toml для проверки синтаксиса конфига.
# pyproject.toml с продвинутыми настройками
[tool.ruff]
line-length = 88
target-version = "py311"
required-version = "0.6.0"
src = ["src"]  # src-layout

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

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

Устранение проблем

Проблема: Ruff игнорирует мой конфиг

Решение: Проверьте, что файл находится в ожидаемой директории. Используйте ruff check --show-settings . чтобы увидеть, какой конфиг был найден. Проверьте --show-files чтобы убедиться, что файлы включены в проверку.

Проблема: Ruff вылетает с ошибкой парсинга TOML

Решение: Проверьте синтаксис TOML. Обратите внимание на кавычки: строки должны быть в двойных или одинарных кавычках. Массивы — в квадратных скобках. Ключи и значения разделяются знаком =.

Проблема: Ruff не использует правила из моего конфига

Решение: Убедитесь, что select или extend-select указан, а не только ignore. Если указан только ignore, Ruff использует значения по умолчанию для select (E, F, I) и применяет к ним ignore.

Проблема: Ruff не видит файлы .pyi или .ipynb

Решение: Настройте include в конфиге: include = ["*.py", "*.pyi", "*.ipynb"]. По умолчанию Ruff включает только *.py.

📝

Шпаргалка

Команда Описание
ruff check --show-settings . Показать активные настройки
ruff check --show-files . Показать проверяемые файлы
ruff --version Версия Ruff
ruff clean Очистить кэш Ruff
ruff check --config PATH . Использовать указанный конфиг
📖 Документация: docs.astral.sh/ruff/configuration/
⚠️

Обработка ошибок в конфигурации

Ruff сообщает об ошибках в конфигурации понятными сообщениями. Вот типичные ошибки и их решения:

Ошибка Причина Решение
TOML parse error Синтаксическая ошибка в TOML Проверьте кавычки, скобки, запятые
Unknown field Неизвестная опция Проверьте название опции, возможно опечатка
Expected version Неверная версия в required-version Обновите версию Ruff
Config file not found Файл из --config не существует Проверьте путь к файлу
Extend cycle detected Циклическое наследование через extend Убедитесь, что extend не ведёт к самому себе
# Пример ошибки:
# Error: Failed to parse "/project/pyproject.toml"
# Caused by:
#   TOML parse error at line 3, column 1
#   unexpected character
#   expected newline, `#`

# Решение: проверьте строку 3 в файле конфигурации
💻

Использование в CI/CD с разными конфигами

В CI/CD можно использовать разные конфиги для разных этапов pipeline:

# Этап 1: Линтинг
ruff check --select E,F,I,N,UP,B .

# Этап 2: Проверка форматирования
ruff format --check .

# Этап 3: Проверка безопасности (с другим конфигом)
ruff check --config ruff.security.toml .

# Этап 4: Проверка документации
ruff check --select D --config ruff.docs.toml .

# GitHub Actions:
name: CI
on: [push, pull_request]
jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v2
        with:
          args: check --select E,F,I,N,UP,B
      - uses: astral-sh/ruff-action@v2
        with:
          args: format --check .

Makefile для локальной разработки:

# Makefile
.PHONY: lint format check

lint:
	ruff check --select E,F,I,N,UP,B .

format:
	ruff format .

check: lint format-check

format-check:
	ruff format --check .

fix:
	ruff check --fix .
	ruff format .

clean:
	ruff clean
🏠

Пример полного проекта с конфигурацией

# pyproject.toml — полный файл проекта
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "awesome-project"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = [
    "ruff>=0.6.0,<0.7.0",
]

[tool.ruff]
line-length = 88
target-version = "py310"
src = ["src"]

[tool.ruff.lint]
select = [
    "E",    # pycodestyle errors
    "F",    # pyflakes
    "I",    # isort
    "N",    # pep8-naming
    "UP",   # pyupgrade
    "B",    # bugbear
    "S",    # bandit (security)
    "SIM",  # flake8-simplify
]
ignore = [
    "E501",   # line too long — форматтер сам справится
    "D100",   # missing docstring — пока не готовы
]

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

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
magic-trailing-comma = true
docstring-code-format = true
docstring-code-line-length = 72

Урок 4.1: Конфигурационный файл

5 вопросов