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

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.

Back in October 2023, we announced Ruff v0.1.0, with a new versioning policy and a preview mode to gate new features. Since then, we've added over 100 new rules, which have only been available to users opting-in to preview features. Thank you to everyone that's provided us with feedback regarding the behavior of these new rules — it's been invaluable in determining our stabilizations. We're excited to bring these improvements to everyone in Ruff v0.2.0.

Read on for discussion of the major changes, or take a look at the changelog for an exhaustive list.

Migrating to v0.2.0

This release includes very few breaking changes. In most cases, users will only encounter warnings suggesting migration away from deprecated features. The notable breaking changes are related to nursery rule selection (which has been deprecated since v0.1.0) and rule remappings (which may disable rules that were previously enabled).

If you're using preview mode, you'll see a few more breaking changes, as some behaviors that are treated as warnings in stable will instead error in preview. We strongly recommend reading the deprecated rules and output format sections.

You can see some example migration pull requests at Dagster, FastAPI, LangChain, Zulip, and Sphinx.

Deprecated rules

The following rules are now deprecated:

We found these rules were often disabled by users and their suggestions are not relevant for type checkers.

Deprecated rules will warn on explicit selection (e.g., --select ANN001) without preview, but still be enabled. They will not warn if selected by prefix (e.g., ANN). When preview mode is enabled, explicit selection will throw an error and the rules will be disabled when selected by prefix.

Rule remappings

Since Ruff implements rules from many lint tools, we occasionally need to handle overlapping and duplicated rules. The following rules have been remapped to new codes:

Selection using the deprecated code will warn and automatically map to the updated code. If you're selecting the rule by prefix (e.g., TRY), the new rule will not be enabled automatically. To continue using the rule, select it using its updated code (e.g., B904).

These deprecated rule codes will continue to appear in the documentation with a note regarding the remapping.

Nursery rule selection

The "nursery" originated as a home for new rules, but has since been replaced by a global preview mode. Since the introduction of preview mode, nursery rule selection has remained for backwards compatibility. Now, use of the nursery rule selector will result in an error.

Unstable rules that were in the nursery cannot be selected without the preview flag anymore:

❯ ruff check --select RUF912
ruff failed
  Cause: Selection of unstable rule `RUF912` without the `--preview` flag is not allowed.

Similarly, the NURSERY selector is no longer available:

❯ ruff check --select NURSERY
ruff failed
  Cause: The `NURSERY` selector was removed. Use the `--preview` flag instead.

Removed rules

The preview rule and-or-ternary (PLR1706) was removed. This rule did not meet our standards for safety, targets Python 2.x, and has limited validity in the absence of stronger type inference.

Configuration changes

Now that Ruff includes a formatter, we've been moving settings specific to the linter out of the top-level Ruff settings (tool.ruff) and into a dedicated tool.ruff.lint section.

For example, ruff.allowed-confusables has moved to ruff.lint.allowed-confusables. When upgrading, you'll see detailed warnings enumerating any relevant settings, and where they should move. You can view the full list of renamed settings in the v0.2.0 changelog.

The following top-level settings are considered global, are still available in the ruff top-level namespace, and should not be moved:

  • ruff.builtins
  • ruff.cache-dir
  • ruff.exclude
  • ruff.extend
  • ruff.extend-exclude
  • ruff.extend-include
  • ruff.fix
  • ruff.fix-only
  • ruff.force-exclude
  • ruff.include
  • ruff.namespace-packages
  • ruff.preview
  • ruff.respect-gitignore
  • ruff.src
  • ruff.unsafe-fixes

Output format

Currently, when Ruff reports lint violations, only the error code and message are shown. Users can opt in to including the relevant source code snippet via the --show-source flag. Ruff is moving towards making this the default behavior — we think that context about the violation is important, and including the source will enable us to show suggested fixes in the future.

In support of this behavior, we are adding two new output format options:

  • concise (replaces text)
  • full (replaces --show-source)

The --output-format=text, --show-source, and --no-show-source flags are deprecated. You may continue to use them, but a warning will be displayed.

In preview, the default output format has switched to full. To recover the previous output, you may set the output format to concise.

The default output is unchanged in stable:

❯ ruff check --select RUF900 RUF900 Hey this is a stable test rule.
Found 1 error.

The new default output format in preview:

❯ ruff check --select RUF900 --preview RUF900 Hey this is a stable test rule.
1 | # I'm just an example source file
  |  RUF900: Hey this is a stable test rule
2 | ruff = "v0.2.0"

Found 1 error.

Using the old output format:

❯ ruff check --select RUF900 --preview --output-format concise RUF900 Hey this is a stable test rule.
Found 1 error.

Stabilized behaviors

module-import-not-at-top-of-file (E402)

It's common to interleave a sys.path modification between imports at the top of a file — this is a frequent cause of # noqa: E402 false positives. E402 has been updated to omit such modifications when determining the "import boundary".

For example, given:

from foo import Foo

sys.path.append(str(Path(__file__).parent / "extensions"))

from bar import Bar

Ruff will no longer raise an E402 violation for the second import.

reimplemented-container-builtin (PIE807)

Previously, PIE807 only checked for lambdas that can be replaced with the list builtin. Now, PIE807 also flags lambdas that can be replaced with the dict builtin.

For example, given:

class Foo:
    bar: dict[str, int] = field(default_factory=lambda: {})

Ruff will suggest:

class Foo:
    bar: dict[str, int] = field(default_factory=dict)

unnecessary-placeholder (PIE790)

Previously, PIE790 only checked for unnecessary pass statements. Now, it also checks for unnecessary ellipsis (...) literals.

For example, given:

def func():
    """Placeholder docstring."""

Ruff will suggest:

def func():
    """Placeholder docstring."""

if-else-block-instead-of-dict-get (SIM401)

Previously, SIM401 only checked for if expressions that could be replaced by dict.get(). Now, SIM401 will also suggest replacing if-else expressions with dict.get() calls.

For example, given:

value = foo["bar"] if "bar" in foo else 0

Ruff will suggest:

value = foo.get("bar", 0)

Stabilized rules

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

There are many more new rules still in preview. We'd highly recommend checking them out! You can turn on preview and opt-in to rules one-at-a-time.

Stabilized fixes

Fixes for the following rules have been stabilized and are now available without preview:

Fixes for the following rules have been promoted from unsafe to safe:

Thank you!

Thank you to everyone who provided us with feedback regarding the behavior of new rules and other changes in preview mode and to our 80 new contributors (for a total of 362!) since the v0.1.0 release, it's an honor building Ruff with you!

View the full changelog on GitHub.

We're hiring! Read more about Astral — the company behind Ruff.