Essential Lint Rules
Not every lint rule earns its CI time. These rule sets catch real bugs and style drift with minimal false positives for Python 3.14 projects.
Recipe
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "S", "T20"]
ignore = ["E501", "S101"] # line length via formatter; assert ok in testsWhen to reach for this:
- Bootstrapping lint config for a new project
- Upgrading from minimal flake8 to a comprehensive set
- Tightening CI without overwhelming the team
Working Example
# Violations caught by essential rules:
import os, sys # I: unsorted imports
x = 1 # F841: unused variable (F)
eval("1+1") # S307: dangerous eval (S)
print("debug") # T20: print statement (T20)
class unused: ... # F: defined but never useduv run ruff check example.py
# I001 Import block is un-sorted
# F841 Local variable `x` is assigned but never used
# S307 Use of possibly insecure function
# T201 `print` foundWhat this demonstrates:
Icatches import disorder before reviewFcatches dead code and undefined namesS(bandit) flags security anti-patternsT20catches leftover debug prints
Deep Dive
Recommended Tiers
| Tier | Rules | Purpose |
|---|---|---|
| Core | E, F, I, UP | Style, bugs, imports, syntax upgrades |
| Quality | B, SIM, RUF | Bugbear, simplification, ruff-specific |
| Security | S (bandit) | SQL injection, eval, hardcoded secrets |
| Hygiene | T20, PT | No print in prod; pytest best practices |
Per-Directory Overrides
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"] # allow assert in tests
"migrations/**" = ["E", "F"] # skip lint on Django migrationsGotchas
- Enabling everything (
select = ["ALL"]) - hundreds of false positives. Fix: start with the core set; add rules incrementally. - S101 in test files - bandit flags
assert. Fix: per-file ignore fortests/**. - UP rules on unsupported Python - pyupgrade suggests syntax you can't run. Fix: match
target-versiontorequires-python. - T20 in CLI scripts - print is correct for CLI output. Fix: ignore T20 in
scripts/or CLI entry points. - Ignoring too many rules - lint becomes meaningless. Fix: every ignore needs a comment explaining why.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Minimal (E, F only) | Very early prototype | Production code |
| Full ALL | Security-critical systems | Small teams without lint budget |
| pylint | Deep analysis | Speed-critical CI |
FAQs
What rules should every project enable?
At minimum: E, F, I, UP. Add B and SIM for quality, S for security-sensitive code.
How do I find what a rule does?
ruff rule B006 prints the rule description and examples.
Should I enable type-checking rules?
Ruff has flake8-type-checking (TCH) rules. Useful but can be noisy. Prefer mypy/pyright for types.
How do I adopt rules incrementally?
Enable new rules, run ruff check --fix, commit fixes, then add to CI.
What is RUF?
Ruff-native rules for patterns other plugins miss (ambiguous Unicode, invalid pyproject.toml).
Should tests have different rules?
Yes. Allow assert (S101), print for debugging, and magic values (PLR2004) in tests.
How do I suppress a rule project-wide?
Add to ignore = ["RULE"] in [tool.ruff.lint].
Are security rules (S) slow?
No in ruff. They are fast and high-value for web apps and CLIs handling user input.
What about Django-specific rules?
Enable DJ (flake8-django) for Django projects. Skip for FastAPI/Flask.
How often should I update rule sets?
When upgrading ruff. Review the changelog for new high-value rules each quarter.
Related
- Ruff Setup - installation and config
- isort & Import Organization - import rules detail
- mypy & pyright in CI - type checking layer
- Linting & Formatting Best Practices - adoption strategy
Stack versions: This page was written for Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.