Ruff v0.10.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.
Migrating to v0.10 #
As usual for Ruff minor releases, v0.10 contains few breaking changes, and most users should be able to upgrade without significant modifications to their code or configuration. However, there are a few changes worth calling out individually.
Improved Python version detection #
This release makes Python version detection more intuitive for projects with a
pyproject.toml
file.
Ruff uses your Python version to offer more accurate suggestions for
lint rules, import sorting, and other formatting decisions — just to name a
few.1 If you've been following recent
preview changes, we're also working on
better detection of version-related syntax errors (such as the use of match
before Python 3.10). These rely on your specified Python version to
avoid false positives.
In previous versions of Ruff, you could specify your Python version with:
- The
target-version
option in aruff.toml
file or the[tool.ruff]
section of apyproject.toml
file. - The
project.requires-python
field in apyproject.toml
file with a[tool.ruff]
section.
These options worked well in most cases, and are still recommended for fine
control of the Python version. However, because of the way Ruff discovers
config files,
pyproject.toml
files without a [tool.ruff]
section would be ignored,
including the requires-python
setting. Ruff would then use the default Python
version (3.9 as of this writing) instead, which is surprising when you've
attempted to request another version.
In v0.10, config discovery has been updated to address this issue:
- If Ruff finds a
ruff.toml
file without atarget-version
, it will check for apyproject.toml
file in the same directory and respect itsrequires-python
version, even if it does not contain a[tool.ruff]
section. - If there is no config file (
ruff.toml
orpyproject.toml
with a[tool.ruff]
section) in the directory of the file being checked, Ruff will search for the closestpyproject.toml
in the parent directories and use itsrequires-python
setting.2
These changes will have no effect if you're already using the target-version
option or requires-python
in a pyproject.toml
with a [tool.ruff]
section.3
See the new documentation for more details.
More robust suppression comments #
The inline (# noqa
) and file-level (# ruff: noqa
)
suppression comment parsing have been unified and overall made more robust.
In the following situations, a comment will suppress more rules than it used to:
- Leading comments are now allowed for file-level suppression comments, e.g.
# Some context # ruff: noqa
. Previously, thisnoqa
was silently ignored. - Trailing comments are now allowed for file-level suppression comments, e.g.
# ruff: noqa This file is rough!
, whereas previously this resulted in an error and the suppression did not take effect. - To support the previous change, invalid syntax like
ruff: noqa UP035
(missing colon afternoqa
) will now suppress all rules. This is consistent with the existing inline behavior and is guarded against byblanket-noqa
(PGH004
). - Missing items in lists of rules are now accepted but emit a warning. For
example,
#noqa: F401,,F841
will suppressF401
andF841
but display a warning. - Similarly, missing delimiters in lists of rules are now accepted in both the
inline and file-level comments but emit a warning. For example,
# ruff: noqa: F401F841
will suppress bothF401
andF841
but display a warning.
In the following situations, a comment may suppress fewer rules than it used to:
- It is now disallowed to have an invalid rule suffix in inline suppression
comments, e.g.
# noqa: UP035abc
. This will log an error and suppress no rules. This matches the current behavior for file-level suppression comments. - Whitespace is now allowed between the colon and rule list, e.g.
#noqa : F401
. This previously suppressed all rules but will now only suppressF401
, as was likely intended.
Updated TYPE_CHECKING
behavior #
This release brings Ruff's treatment of TYPE_CHECKING
symbols into alignment
with other tools like mypy
and pyright
.
typing.TYPE_CHECKING
is a special constant in the standard library typing
module that can be used
to avoid expensive imports at runtime but include them for use by type checkers
(and linters). This often looks something like this example adapted from the
typing
documentation:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import expensive_mod
def fun(arg: expensive_mod.SomeType) -> None: ...
However, in some cases the typing
import itself may be undesirable. In the
past, Ruff and many type checkers have allowed if
statements like if 0: ...
or if False: ...
in place of if TYPE_CHECKING: ...
to avoid the typing
import. That has fallen out of favor and been generally replaced by using a
local symbol named TYPE_CHECKING
instead, for example:
from __future__ import annotations
TYPE_CHECKING = False
if TYPE_CHECKING:
import expensive_mod
def fun(arg: expensive_mod.SomeType) -> None: ...
This release stabilizes both of these changes in Ruff:
- Support for
if 0
andif False
instead ofif TYPE_CHECKING
has been removed. - Support for any symbol named
TYPE_CHECKING
rather than preciselytyping.TYPE_CHECKING
has been added.
As an added benefit, this change strengthens rules like
if-else-block-instead-of-if-exp
(SIM108
), which previously could not collapse if 0
or if False
blocks in
case they were actually for TYPE_CHECKING
.
Rule stabilizations #
The following rules have been stabilized and are no longer in preview:
batched-without-explicit-strict
(B911
)unnecessary-dict-comprehension-for-iterable
(C420
)datetime-min-max
(DTZ901
)fast-api-unused-path-parameter
(FAST003
)root-logger-call
(LOG015
)len-test
(PLC1802
)shallow-copy-environ
(PLW1507
)os-listdir
(PTH208
)invalid-pathlib-with-suffix
(PTH210
)invalid-assert-message-literal-argument
(RUF040
)unnecessary-nested-literal
(RUF041
)unnecessary-cast-to-int
(RUF046
)map-int-version-parsing
(RUF048
)if-key-in-dict-del
(RUF051
)unsafe-markup-use
(S704
). This rule has also been recoded fromRUF035
.split-static-string
(SIM905
)runtime-cast-value
(TC006
)unquoted-type-alias
(TC007
)non-pep646-unpack
(UP044
)
Other behavior stabilizations #
This release also stabilizes some additional behavior, previously only available in preview mode:
- Options for the
flake8-builtins
rules with redundantbuiltins-
prefixes have been deprecated. For example, thelint.flake8-builtins.builtins-allowed-modules
option is nowlint.flake8-builtins.allowed-modules
. - The
flake8-builtins
rulestdlib-module-shadowing
(A005
) now defaults to non-strict module name checking (e.g. autils/logging.py
module will not be flagged as conflicting with thelogging
builtin module). See thelint.flake8-builtins.strict-checking
option to restore the old default. custom-type-var-for-self
(PYI019
) now uses a more accurate approach for replacing customTypeVar
s withSelf
, can apply to methods without a return annotation, and offers an automatic fix.blanket-noqa
(PGH004
) now detects file-level suppression comments in addition to inline comments.invalid-envvar-default
(PLW1508
) has been extended to recognizeos.environ.get
as well asos.getenv
.invalid-first-argument-name-for-class-method
(N804
) no longer flags__new__
methods, which are technically static methods rather than class methods.bad-staticmethod-argument
(PLW0211
) now applies to__new__
methods instead.
See the full changelog on GitHub for other, smaller changes.
Rule deprecations #
suspicious-xmle-tree-usage
(S320
) has been deprecated because the issues it checks for in thelxml
package have been resolved.non-pep604-isinstance
(UP038
) has been deprecated because using PEP 604 union syntax inisinstance
andissubclass
calls is not actually a recommended pattern and may also hurt performance.
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 Dylan Wilson, Micha Reiser, Dhruv Manilawala, and Zanie Blue who contributed to this blog post.
Footnotes #
-
See the v0.8 release notes for a more specific list if you're curious! ↩
-
Ruff only performs this search if the
--isolated
flag is not used and no target version is passed via the--config
or--target-version
flags. ↩ -
These changes also won't affect you if you don't have a
project.requires-python
setting or apyproject.toml
at all. In those cases, Ruff will continue using its internal default version of Python 3.9 (as of this writing). ↩