Chapter 03

配置文件 pyproject.toml

把规则、长度、排除、目标 Python 等所有配置集中在 pyproject.toml 里,团队跨机器一致。

3.1 配置文件的三种位置

Ruff 按以下优先级查找配置(命中第一个即停止向上查找):

  1. pyproject.toml 里的 [tool.ruff] 段(推荐,与 PEP 518 生态统一)
  2. ruff.toml(独立文件,语法同 pyproject 的 tool.ruff 内容)
  3. .ruff.toml(以点开头的隐藏版,等价)

Ruff 会从当前目录沿父目录向上查找,直到找到上述任一文件或到达文件系统根。

什么是 pyproject.toml?

pyproject.toml 由 PEP 518 / PEP 621 定义,是 Python 项目的"统一配置总入口"。里面 [project] 放元数据,[build-system] 放构建后端,[tool.xxx] 放各工具的配置。Ruff、Black、pytest、mypy、coverage 都已支持从这里读配置。

3.2 最小可用配置

# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]   # 允许长行(因为已有 formatter 管)

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

3.3 顶层配置项

line-length(默认 88)
最大行宽。Black 默认 88,Google 风格用 80,大型数据科学项目常设 100-120。
target-version(默认从 project.requires-python 推断)
目标 Python 版本,取值 "py38".."py313"。影响 UP 族规则、formatter 的合法语法。
src(默认 ["src"]
本地源码目录列表,用于 isort 分组时判定"哪些是本地模块"。
extend
继承另一个 ruff 配置文件(例如团队共享的 /configs/ruff-base.toml)。
exclude / extend-exclude
忽略的路径或 glob 模式。exclude 会覆盖默认值(.git/.venv/dist 等),extend-exclude 则追加。
respect-gitignore(默认 true)
自动跳过 .gitignore 中的文件。

3.4 [tool.ruff.lint]:规则选择

select / extend-select / ignore / extend-ignore 四个是最核心的:

[tool.ruff.lint]
# 启用这些规则族(覆盖默认)
select = ["E", "F", "I", "B", "UP", "SIM", "N"]

# 在 select 基础上再追加
extend-select = ["RUF", "S"]

# 关掉具体规则
ignore = ["E501", "S101"]   # 允许 assert(S101)和长行

# 视为可自动修复(safe)——用于收紧/放宽 --fix 行为
fixable = ["ALL"]
unfixable = ["F841"]   # 不让 --fix 自动删未使用变量

# 允许 flake8 风格的 # noqa: XXX
external = ["WPS"]
select 还是 extend-select?

团队从 0 搭配置用 select(精确、易审计)。如果基于上游共享配置再加规则,用 extend-select(不破坏继承)。

3.5 per-file-ignores:文件级例外

测试文件里允许 assert、__init__.py 里允许未使用 import 做重新导出——这类需求用 per-file-ignores

[tool.ruff.lint.per-file-ignores]
"__init__.py"     = ["F401", "F403"]   # 允许未使用的 import / *
"tests/**/*.py"   = ["S101", "D", "ANN"] # 测试里放宽 assert/docstring/注解
"scripts/*.py"    = ["T20"]               # 脚本允许 print

3.6 [tool.ruff.format]:格式化配置

[tool.ruff.format]
quote-style = "double"         # "single" / "double" / "preserve"
indent-style = "space"         # "space" / "tab"
skip-magic-trailing-comma = false
line-ending = "auto"           # auto/lf/crlf/native
docstring-code-format = true   # 对 docstring 里的 ``` 代码块也格式化
docstring-code-line-length = "dynamic"

3.7 [tool.ruff.lint.isort]:import 分组

[tool.ruff.lint.isort]
known-first-party = ["myapp", "mylib"]   # 指定哪些包算本地
known-third-party = ["fastapi"]
combine-as-imports = true
force-single-line = false
split-on-trailing-comma = true
section-order = [
    "future",
    "standard-library",
    "third-party",
    "first-party",
    "local-folder",
]

3.8 [tool.ruff.lint.pydocstyle]

[tool.ruff.lint.pydocstyle]
convention = "google"   # "google" / "numpy" / "pep257"
# 选定 convention 后 D 族自动套用合适的子集

3.9 完整示例:FastAPI 项目

[tool.ruff]
line-length = 100
target-version = "py312"
extend-exclude = ["alembic/versions", "generated"]

[tool.ruff.lint]
select = [
    "E", "F", "W",          # pycodestyle + pyflakes
    "I",                     # isort
    "N",                     # 命名
    "UP",                    # pyupgrade
    "B",                     # bugbear
    "C4",                    # comprehensions
    "SIM",                   # simplify
    "PTH",                   # pathlib 优先于 os.path
    "RUF",                   # Ruff 原创
    "S",                     # 安全
]
ignore = [
    "E501",   # formatter 管行长
    "B008",   # FastAPI Depends() 在默认参数是合法用法
]

[tool.ruff.lint.per-file-ignores]
"__init__.py"     = ["F401"]
"tests/**/*.py"   = ["S101", "S105", "S106"]
"alembic/env.py"  = ["E402"]

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

3.10 动态查看"生效配置"

配置跨文件继承时容易搞不清"到底用了哪些"。Ruff 提供调试命令:

$ ruff check --show-settings .
# 打印出解析后全部生效配置(JSON 格式)

$ ruff check --verbose .
# 显示每个文件用的是哪份配置

3.11 小结