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 patterns
    match 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 and return outside of functions
    yield 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:

Other behavior stabilizations

This release also stabilizes some additional behavior, previously only available in preview mode:

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 other PD rules.

Rule removals

  • suspicious-xmle-tree-usage (S320) has been removed. It was deprecated in v0.10 because the problems it checked for in the lxml 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.