Ruff v0.12.0 is available now! Install it from PyPI, or with your package manager of choice:
uv tool install ruff@latestAs 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
matchpatternsmatch 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 yieldandreturnoutside 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.85to 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 useorinstead of anifexpression, where possible.unused-noqa(RUF100) now checks for file-levelnoqacomments 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 | intortyping.Optional[bool], in addition to plainboolannotations.non-pep604-annotation-union(UP007) has now been split into two rules.UP007now applies only totyping.Union, whilenon-pep604-annotation-optional(UP045) checks for use oftyping.Optional.UP045has also been stabilized in this release, but you may need to update existinginclude,ignore, ornoqasettings to accommodate this change.
Rule deprecations #
The following rules have been deprecated and will be removed in a future release:
pandas-df-variable-namehas been deprecated because it's highly opinionated and not always desirable when enabling the otherPDrules.
Rule removals #
suspicious-xmle-tree-usage(S320) has been removed. It was deprecated in v0.10 because the problems it checked for in thelxmlpackage 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.
