Ruff v0.0.283 is out 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, written in Rust. Ruff can be used to replace Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, and more, all while executing tens or hundreds of times faster than any individual tool.

View the full changelog on GitHub, or read on for the highlights.

Ruff supports PEP 695

With the publication of the first release candidate for Python 3.12, the Astral team is preparing for support of new Python features. One of the features, introduced in PEP 695, is new syntax for declaring type aliases and using generic type parameters.

For example, instead of:

import typing

T = typing.TypeVar("T")
MyList: typing.TypeAlias = list[T]

You can write:

type MyList[T] = list[T]

The same type parameter syntax can be used for generic types in functions and classes, e.g.

def my_func[T](x: T) -> list[T]:
  return [x]


class MyClass[U]:
    def method(self) -> U:
        ...

As of Ruff v0.0.283, Ruff's parser supports all valid PEP 695 syntax.

We've also introduced a new rule (UP040) to ease your transition by automatically converting type alias declarations to the new syntax. See #6289 and #6314 for details on implementation of the rule. Note this rule is only enabled if your target Python version is 3.12+.

We're looking for contributors interested in adding rules for more complex type declarations.

As part of this change, we added support for parsing PEP 695 syntax to RustPython's parser. Thank you to the RustPython team for reviewing our contributions.

flake8-pyi rule violations are now raised in non-stub Python files

Previously, the flake8-pyi rules were limited to .pyi type stub files. However, many of these rules are applicable in normal Python (.py) files. Now that most of the rules from flake8-pyi are implemented, we've enabled the subset of rules that are not type stub specific. These rules will help you catch misuse of type annotations in your Python code!

The following rules are now emitted in all Python files:

Thanks to @andersk for contributing! See #6297 for details.

Breaking: Python 3.8 is the default target-python version

Previously, when a target Python version was not specified, Ruff would use a default of Python 3.10. However, it is safer to default to an older Python version to avoid assuming the availability of new features. We now default to the oldest supported Python version which is currently Python 3.8.

This may be a breaking change if you have not specified a target-python or project.requires-python version.

(We still support Python 3.7 but since it has reached EOL we've decided not to make it the default here.)

See #6397 for details.

New rule: custom-type-var-return-type (PYI019)

What does it do?

Checks for methods that define a custom TypeVar for their return type annotation instead of using typing.Self.

Why does it matter?

If certain methods are annotated with a custom TypeVar type, and the class is subclassed, type checkers will not be able to infer the correct return type.

This check currently applies to instance methods that return self, class methods that return an instance of cls, and __new__ methods.

For example, given the following snippet:

class MyClass:
    def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S:
        ...

    def foo(self: _S, arg: bytes) -> _S:
        ...

    @classmethod
    def bar(cls: type[_S], arg: int) -> _S:
        ...

The type variable _S should be replaced with the built-in type Self:

from typing import Self

class MyClass:
    def __new__(cls: type[Self], *args: str, **kwargs: int) -> Self:
        ...

    def foo(self: Self, arg: bytes) -> Self:
        ...

    @classmethod
    def bar(cls: type[Self], arg: int) -> Self:
        ...

This rule is derived from flake8-pyi.

Contributed by @qdegraaf.

New rule: redundant-literal-union (PYI051)

What does it do?

Checks for the presence of redundant Literal types and builtin super types in an union.

Why does it matter?

The use of Literal types in a union with the builtin super type of one of its literal members is redundant, as the super type is strictly more general than the Literal type.

For example, Literal["A"] | str is equivalent to str, and is equivalent to int, as

For example, in the following snippet:

from typing import Literal

A: Literal["x"] | str

B: Literal[1] | int

str and int are the super types of "x" and 1 respectively so performing a union with a literal value has no effect. The literal can be omitted:

from typing import Literal

A: str

B: int

This rule is derived from flake8-pyi.

Contributed by @LaBatata101.

New rule: unecessary-type-union (PYI055)

What does it do?

Checks for the presence of multiple type uses in a union.

Why does it matter?

The type built-in function accepts unions, and it is clearer to explicitly specify them as a single type.

For example, in the following snippet:

field: type[int] | type[float]

It is more succint to move the union into a single type:

field: type[int | float]

This rule is derived from flake8-pyi.

Contributed by @LaBatata101.

New rule: bad-string-format-character (PLE1300)

What does it do?

Checks for unsupported format character codes in formatted strings.

Why does it matter?

An invalid format string character will result in an error at runtime.

For example, in the following snippet, z is not a valid format character and an error would be raised at runtime.

print("%z" % "1")

print("{:z}".format("1"))

This rule is derived from pylint.

Contributed by @silvanocerza.