Ruff v0.8.0 is available now! Install it from PyPI, or your package manager of choice:

uv pip install --upgrade ruff

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.

Ruff v0.8 is a significant release containing many features and improvements. Highlights include:

  • Ruff's linter and formatter both now assume Python 3.9 by default. (This is configurable; see below for more details.)
  • 13 rules have been stabilized that were previously only available in preview mode. Several behaviors have also been stabilized for pre-existing rules.
  • All the usual bugfixes, behavior improvements and documentation updates that you'd expect from any Ruff release.

Migrating to v0.8

While upgrading to Ruff v0.8 should be a smooth experience for most users, this release contains a number of breaking changes that may impact the way your code is linted or formatted. We'll tackle these one by one.

Target Python version now defaults to 3.9

This release makes some changes to the "target Python version" that Ruff infers by default if it has no other information to go by.

Ruff needs to figure out the minimum version of Python your code supports for several reasons:

  • Many rules in the Ruff linter vary their behavior depending on the inferred target version. For example, the linter has import-sorting rules that categorize your imports depending on whether a module comes from a third-party dependency or the standard library — but the answer to this question depends on the Python version you're using. zoneinfo, for example, was only added to the standard library in Python 3.9, so import zoneinfo must be categorized as a standard-library import if you assume Python 3.9. However, by process of deduction, it must be considered a third-party import on lower versions of Python, since there is no standard-library zoneinfo module on those Python versions.

  • The formatter also sometimes varies its behavior according to the inferred target version. For example, the formatter prefers to parenthesize with statements where possible, but this is only valid syntax on Python >=3.9. If Ruff understands that it should assume an older Python version is being used, a slightly different formatting style is applied.

Ruff uses a three-stage process to determine which Python version it should assume:

  1. If you specify a value for the target-version setting in your Ruff configuration, Ruff will assume that Python version when linting and formatting your code.
  2. If target-version is not specified but you have a pyproject.toml file in your repository, Ruff will next look to see if project.requires-python is specified there, and, if so, will use that.
  3. If Ruff still can't figure out which version to assume after stages (1) and (2), it will fall back to a default target version.

On previous versions of Ruff, the default target version used in stage (3) of the algorithm above was Python 3.8. Ruff v0.8 bumps this default to 3.9, as Python 3.8 is now End Of Life.

If you haven't specified a value for target-version or requires-python, you might see formatting changes that look like this when using the Ruff formatter:

- with open(LONG_FILE_PATH_1) as file_1, open(
-     os.path.join("foo", "bar", "baz", "spam", "eggs", "ham", "bacon)
- ) as file_2:
+ with (
+     open(LONG_FILE_PATH_1) as file_1,
+     os.path.join("foo", "bar", "baz", "spam", "eggs", "ham", "bacon) as file_2,
+ ):
      do_something()

You might see changes like this when using Ruff's isort rules to fix your import sorting:

  import collections
  import difflib
+ import zoneinfo

  import requests
  import sqlalchemy
- import zoneinfo

And Ruff's UP006 rule might start complaining about code that makes use of deprecated typing-module aliases such as typing.List or typing.Dict.

Many more of Ruff's rules take account of the inferred target version in some way; this is not an exhaustive list of rules that might be affected by this change. To revert to Ruff's previous behavior in all cases, consider specifying project.requires-python = ">= 3.8" in your pyproject.toml file, or specifying "py38" for the target-version setting in your Ruff configuration.

New error codes for flake8-type-checking rules

Many of Ruff's rules are reimplementations of lints from other, pre-existing tools. Occasionally this means we need to merge overlapping rules, remove duplicate rules, or rename rules for consistency with other linters.

This release changes the error codes for the rules in our flake8-type-checking category from TCH to TC. This matches changes that were made in the original flake8-type-checking plugin from which these rules were originally adapted.

Selecting a rule using a deprecated TCH code will still work, as Ruff will automatically map the deprecated code to the updated code. However, Ruff will now emit a warning if a deprecated error code is used. Users are encouraged to update their configuration files to use the new error codes where possible.

Removal of six deprecated rules

Six rules have been removed in this release. We realized that the changes they were asking users to make to their code were either unnecessary or outright undesirable:

Changes to calculations of Unicode widths

In some cases, users with code that contains Unicode characters may see their code reformatted, or may see changes in whether E501 is reported on their code. This is because Ruff v0.8 uses a new version of the unicode-width Rust crate to calculate the width of Unicode characters and strings.

Few users are likely to be affected by this change.

XDG (i.e. ~/.local/bin) now used by standalone installer

Previously, Ruff's standalone installer used $CARGO_HOME or ~/.cargo/bin for its target install directory. But this made little sense, as Ruff has no relationship to Cargo.

Ruff will now be installed into one of $XDG_BIN_HOME, $XDG_DATA_HOME/../bin, or ~/.local/bin (tried in that order). This change is only relevant to users of the standalone Ruff installer (using the shell or PowerShell script). If you install Ruff using uv, pip, or any other installer, you should be unaffected.

If you are using the standalone installer in CI, you may need to update your workflow to add ~/.local/bin to the PATH.

If you upgrade with ruff self update, the previous $CARGO_HOME location will continue to be used.

If you want to migrate from the old install location to the new one, simply remove the Ruff binary from the old location (rm ~/.cargo/bin/ruff on Unix), and then install the latest version.

Changed location of pydoclint diagnostics

If a diagnostic is emitted for a rule in our pydoclint category, the line number of the diagnostic will now always point to the first line of the problematic docstring. Previously this was not the case.

If you've opted into these preview rules but have them suppressed using noqa comments in some places, this change may mean that you need to move the noqa suppression comments. Most users should be unaffected by this change.

Rule stabilizations

The following 13 rules have been stabilized and are no longer in preview:

Other behavior changes

This release also improves the behavior of several other rules.

Firstly, invalid-pyproject-toml (RUF200) now understands pyproject.toml files that use features from the latest version of PEP 639. The PEP has been provisionally accepted, and is already enjoying widespread adoption among parts of the community.

Finally, Ruff v0.8 stabilizes several behaviors that were previously only available to users who opted into preview mode:

  • ambiguous-variable-name (E741) now ignores violations in stub files (Python source code files with .pyi file extensions). Authors of stubs typically don't control the names used for variables in their stubs. Rather, the priority is generally for a stub to exactly emulate the interface of the corresponding Python module exposed at runtime.

  • printf-string-formatting (UP031) now reports all printf-like usages in your code. Previously, violations were only reported if an autofix was available.

  • The autofix for zip-instead-of-pairwise (RUF007) has been promoted to stable (though it is still categorized as unsafe).


View the full changelog on GitHub.

Read more about Astral — the company behind Ruff.