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:
missing-type-self
(ANN101
)missing-type-cls
(ANN102
)
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:
raise-without-from-inside-except
:TRY200
toB904
suspicious-eval-usage
:PGH001
toS307
logging-warn
:PGH002
toG010
static-key-dict-comprehension
:RUF011
toB035
runtime-string-union
:TCH006
toTCH010
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 example.py --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 example.py --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.show-fixes
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
(replacestext
)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 example.py --select RUF900
example.py:1:1: RUF900 Hey this is a stable test rule.
Found 1 error.
The new default output format in preview:
❯ ruff check example.py --select RUF900 --preview
example.py:1:1: 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 example.py --select RUF900 --preview --output-format concise
example.py:1:1: 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:
@dataclass
class Foo:
bar: dict[str, int] = field(default_factory=lambda: {})
Ruff will suggest:
@dataclass
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:
trio-timeout-without-await
(TRIO100
)trio-sync-call
(TRIO105
)trio-async-function-with-timeout
(TRIO109
)trio-unneeded-sleep
(TRIO110
)trio-zero-sleep-call
(TRIO115
)unnecessary-escaped-quote
(Q004
)enumerate-for-loop
(SIM113
)zip-dict-keys-and-values
(SIM911
)timeout-error-alias
(UP041
)flask-debug-true
(S201
)tarfile-unsafe-members
(S202
)ssl-insecure-version
(S502
)ssl-with-bad-defaults
(S503
)ssl-with-no-version
(S504
)weak-cryptographic-key
(S505
)ssh-no-host-key-verification
(S507
)django-raw-sql
(S611
)mako-templates
(S702
)generator-return-from-iter-method
(PYI058
)runtime-string-union
(TCH006
)numpy2-deprecation
(NPY201
)quadratic-list-summation
(RUF017
)assignment-in-assert
(RUF018
)unnecessary-key-check
(RUF019
)never-union
(RUF020
)direct-logger-instantiation
(LOG001
)invalid-get-logger-argument
(LOG002
)exception-without-exc-info
(LOG007
)undocumented-warn
(LOG009
)
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:
triple-single-quotes
(D300
)non-pep604-annotation
(UP007
)dict-get-with-none-default
(SIM910
)in-dict-keys
(SIM118
)collapsible-else-if
(PLR5501
)if-with-same-arms
(SIM114
)useless-else-on-loop
(PLW0120
)unnecessary-literal-union
(PYI030
)unnecessary-spread
(PIE800
)error-instead-of-exception
(TRY400
)redefined-while-unused
(F811
)duplicate-value
(B033
)multiple-imports-on-one-line
(E401
)non-pep585-annotation
(UP006
)
Fixes for the following rules have been promoted from unsafe to safe:
unaliased-collections-abc-set-import
(PYI025
)
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.