Ruff v0.12.0 is available now! Install it from PyPI, or with your package manager of choice:
uv tool install ruff@latest
As a reminder: Ruff is an extremely fast Python linter and formatter, written in Rust. Ruff can be used to replace Black, Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, and more, all while executing tens or hundreds of times faster than any individual tool.
Migrating to v0.12 #
Like most Ruff minor releases, v0.12 only contains a few breaking changes. Most users should expect a smooth upgrade with no changes to their code or configuration. Still, there are a few larger changes worth covering in detail.
Improved syntax error detection #
Ruff now detects two additional classes of syntax errors reported by CPython, in addition to the parsing errors reported by earlier versions.
Ruff has always been able to detect invalid Python syntax that prevents parsing, such as mismatched quotes or a missing colon. However, CPython reports two additional categories of syntax errors: version-related syntax errors, and syntax errors emitted during a different stage of CPython's compilation of Python. We'll cover each of these in turn.
Version-related syntax errors #
As the name suggests, these syntax errors relate to changes in Python syntax
between versions. Common examples of these errors are the use of
the match
statement before
Python 3.10 or the use of assignment expressions (:=
, the "walrus operator") before
3.8. Ruff's parser is now version-aware and will emit diagnostics if you use any
syntax that is not available on your current Python version. (See
our documentation
for details on how Ruff tries to figure out what your current Python version is.)
You can find a full list of these syntax errors in the tracking issue.
If different parts of your project support different Python versions, you can
override the global target version with the
per-file-target-version
setting. For example, in the case of a developer scripts/
directory in the root
of your project, you could add
[per-file-target-version]
"scripts/*.py" = "py312"
to your ruff.toml
or the equivalent tool.ruff.per-file-target-version
in
your pyproject.toml
.
Syntax errors emitted by the Python compiler #
The next class of syntax error reported by CPython is a bit different. Rather than being emitted by the Python (or Ruff) parser, these errors are detected and reported by CPython's bytecode compiler. They cover a wide array of errors including:
- Irrefutable
match
patternsmatch 42: case x: ... # SyntaxError: name capture 'x' makes remaining patterns unreachable case y: ...
- Duplicate function parameter names
def foo(x, x): ... # SyntaxError: duplicate argument 'x' in function definition
yield
andreturn
outside of functionsyield 1 # SyntaxError: 'yield' outside function return 1 # SyntaxError: 'return' outside function
and many more, which you can again find in the tracking issue.
Better default Python version handling #
Ruff will default to the latest supported Python version when checking the syntax errors from the previous section, in order to make the new errors less disruptive for projects without a Python version configured.
By default, Ruff will try to
infer
a project's target Python version from an available configuration file, as
described in the config file
discovery
section of the documentation. If this fails to uncover a Python version, and no
version is provided by other means such as the --target-version
CLI option,
Ruff generally falls back to its oldest supported Python version,
currently Python 3.9. This avoids false positives on rules like pyupgrade
(UP), which are only
valid on more recent Python versions. However, defaulting to 3.9 when parsing Python would mean
emitting syntax errors for common pieces of newer syntax like match
statements or PEP-695 generics. In v0.12,
Ruff has two separate defaults for these scenarios, when no Python version is
configured: "latest" (3.13) when detecting syntax errors, and "oldest" (3.9) for
everything else.
Like the pre-existing default behavior on earlier versions, this new default for syntax errors is intended to err on the side of minimizing false positives over catching all issues. If you want to take advantage of the new syntax error detection, ensure that your target Python version is configured correctly.
rust-toolchain.toml
is no longer included in source distributions #
Ruff uses a
rust-toolchain.toml
file to specify the latest stable Rust version (currently 1.87) for use in
development and for producing release binaries. However, Ruff also supports
building with at least the previous two stable Rust versions (1.85 and 1.86 in
this case), in line with our minimum supported Rust
version
(MSRV) policy. In previous releases, the rust-toolchain.toml
file was included
in the source archives. This caused consumers of the source distribution to
build with, and possibly download and install, the latest stable Rust version,
even if their available toolchain was compatible with Ruff's MSRV.
This change should not affect most end users of Ruff but should make it easier
to build Ruff from source, especially in situations with security or other
constraints on the available Rust toolchain. The one thing to note, if you are
not in such a constrained environment, is that this also means that cargo
will
not automatically install a compatible Rust toolchain. You may have to run a
command like
rustup install 1.85
to obtain an MSRV-compatible toolchain, in that case.
Updated f-string formatting #
Ruff now formats multi-line f-strings with format specifiers differently because of a change to CPython's grammar. Before, Ruff could format the interpolation expression like so:
f"This is some long string {
x:d
} that is formatted across multiple lines"
However, the line break after the :d
format specifier is now a syntax error in
Python 3.13.4 and newer. Ruff therefore now formats the f-string like so:
f"This is some long string {
x:d} that is formatted across multiple lines"
Rule stabilizations #
The following rules have been stabilized and are no longer in preview:
for-loop-writes
(FURB122
)check-and-remove-from-set
(FURB132
)verbose-decimal-constructor
(FURB157
)fromisoformat-replace-z
(FURB162
)int-on-sliced-str
(FURB166
)exc-info-outside-except-handler
(LOG014
)import-outside-top-level
(PLC0415
)unnecessary-dict-index-lookup
(PLR1733
)nan-comparison
(PLW0177
)eq-without-hash
(PLW1641
)pytest-parameter-with-default-argument
(PT028
)pytest-warns-too-broad
(PT030
)pytest-warns-with-multiple-statements
(PT031
)invalid-formatter-suppression-comment
(RUF028
)dataclass-enum
(RUF049
)class-with-mixed-type-vars
(RUF053
)unnecessary-round
(RUF057
)starmap-zip
(RUF058
)non-pep604-annotation-optional
(UP045
)non-pep695-generic-class
(UP046
)non-pep695-generic-function
(UP047
)private-type-parameter
(UP049
)
Other behavior stabilizations #
This release also stabilizes some additional behavior, previously only available in preview mode:
collection-literal-concatenation
(RUF005
) now recognizes slices, in addition to list literals and variables.- The fix for
readlines-in-for
(FURB129
) is now marked as always safe. if-else-block-instead-of-if-exp
(SIM108
) will now further simplify expressions to useor
instead of anif
expression, where possible.unused-noqa
(RUF100
) now checks for file-levelnoqa
comments as well as inline comments.subprocess-without-shell-equals-true
(S603
) now accepts literal strings, as well as lists and tuples of literal strings, as trusted input.boolean-type-hint-positional-argument
(FBT001
) now applies to types that includebool
, likebool | int
ortyping.Optional[bool]
, in addition to plainbool
annotations.non-pep604-annotation-union
(UP007
) has now been split into two rules.UP007
now applies only totyping.Union
, whilenon-pep604-annotation-optional
(UP045
) checks for use oftyping.Optional
.UP045
has also been stabilized in this release, but you may need to update existinginclude
,ignore
, ornoqa
settings to accommodate this change.
Rule deprecations #
The following rules have been deprecated and will be removed in a future release:
pandas-df-variable-name
has been deprecated because it's highly opinionated and not always desirable when enabling the otherPD
rules.
Rule removals #
suspicious-xmle-tree-usage
(S320
) has been removed. It was deprecated in v0.10 because the problems it checked for in thelxml
package have been resolved.
See the full changelog on GitHub for other, smaller changes.
Thank you! #
Thank you to everyone who provided feedback regarding the changes included in Ruff's preview mode, and especially, to our contributors. It's an honor building Ruff with you!
View the full changelog on GitHub.
Read more about Astral — the company behind Ruff.
Thanks to Micha Reiser and Alex Waygood who contributed to this blog post.