You add Ruff's security rules to your project and run ruff check. Most of
the findings are S101, which flags every assert statement. Nearly all of
them are in your tests, where assert is how pytest checks a result. One is in
your application code:
def apply_discount(price: float, percent: float) -> float:
assert 0 <= percent <= 100, "percent out of range"
return price * (1 - percent / 100)
That one is a real bug. Python skips every assert when you run it with the
-O flag, so apply_discount(10.0, 150) returns -5.0 instead of failing.
Raise an exception for a check that must always run:
def apply_discount(price: float, percent: float) -> float:
if not 0 <= percent <= 100:
raise ValueError(f"percent out of range: {percent}")
return price * (1 - percent / 100)
So the same rule, S101, flags noise in your tests and a bug in your
application code. This site grades exercises with their tests and mypy, and
never runs Ruff on them. The advice here is for your own projects. The
earlier Ruff configuration article covers
the setup.
Fail CI on rules you would always fix
A rule belongs in CI when you would make the change for nearly every finding.
F and E722 meet that standard. F flags undefined names and unused
imports. E722, part of E7, flags a bare except:, which also catches
KeyboardInterrupt. A select list replaces Ruff's default rules, so name
every family you want. Add other rules by the same standard:
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "B", "G004", "S101", "T20"]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101"]
"src/myapp/cli/**/*.py" = ["T20"]
Bflags bug patterns such as a mutable default argument, which every call to the function shares.G004flags an f-string in a logging call. Withlogger.debug("rows: %s", rows), logging formats the message only if debug logging is on. An f-string formats it on every call.T20flagsprintcalls.T20andS101apply in some files and not in others. Theper-file-ignorestable turns each one off for the paths where it does not apply, and keeps it everywhere else.
Run judgment rules as advice
Some rules flag code that is often fine. If they fail CI, your team learns to
add # noqa comments without reading the finding.
PLR0913flags a function with more than five arguments. Sometimes the fix is to group related values in a dataclass. Sometimes the function needs them all.- The
Srules forsubprocessflag calls that start another program. Each finding asks you to check where the arguments come from. It does not mean the call is unsafe. C901flags a function whose complexity score is over a limit you set. The next article explains what that score measures.
Ruff has no warning level, so any finding from a selected rule fails
ruff check. Leave
judgment rules out of select, and run them in a separate step that always
passes:
ruff check --select PLR0913,C901 --exit-zero
Check your understanding
Five files in src/myapp/cli/ each have # noqa: T201 comments on their print calls. What should you change?
Show answer
Hide answer
Move the exception into the configuration with a per-file-ignores entry for
src/myapp/cli/**/*.py. Printing is the job of a command-line tool, so the
rule never applies there. One configuration entry now says so, instead of a
comment on every call. Keep a
# noqa: CODE comment for one line that breaks a rule the rest of its file
follows, and add a short comment that says why.
Upgrade Ruff on purpose
Pin the Ruff version, and use the same version locally and in CI. When you
select a family such as B, you also get the new rules Ruff adds to it in
later releases. So read the release notes when you upgrade, and run the checks
even if your configuration did not change.
Going further
Ruff marks some automatic fixes as unsafe, because they can change what the
code does. ruff check --fix applies only the safe ones unless you opt in.
Review the diff either way. Read more about
fix safety and the
security rules.
Before version 0.16, Ruff's default rules were E4, E7, E9 and F.
Since 0.16, about 400 rules are on by default. Run ruff check --show-settings
to list the rules your configuration turns on. See
the default rules.