Skip to content

[CVAT] [Recording Oracle] Add ruff#2396

Merged
zhiltsov-max merged 44 commits into
developfrom
ba/recording-oracle/add-ruff
Aug 27, 2024
Merged

[CVAT] [Recording Oracle] Add ruff#2396
zhiltsov-max merged 44 commits into
developfrom
ba/recording-oracle/add-ruff

Conversation

@Bobronium
Copy link
Copy Markdown
Contributor

@Bobronium Bobronium commented Aug 18, 2024

Description

Adds ruff and integrates linting into the CI pipeline.

Ruff combines features from flake8 (with plugins), pylint, isort, black, autopep8, flynt, etc., into one extremely fast and well-maintained package.

One of the main features is availability of automatic fixes for some violations.

Relying on a tool like Ruff helps to reduce cognitive load when writing/reading the code by enforcing consistency.

Summary of changes

I've adopted the following strategy for integrating Ruff:

  • Reformatted the codebase.
  • Configured Ruff to report all violations.
  • Applied safe fixes each in its own commit.
  • Applied unsafe fixes each in its own commit.
  • Handled the most trivial violations manually.
  • Configured Ruff to ignore the noisiest/questionable violations.
  • Used # noqa for remaining violations where necessary.

Each commit with autofix has a comment explaining the reasoning behind the changes.

How to test the changes

The code should be semantically equivalent, with only stylistic and formatting changes. I've ensured that all tests pass.

References

After this PR and #2395 will get merged, I'll introduce a PR adding .git-blame-ignore-revs with revisions from merge commits, so these changes will be ignored by git blame for better history overview.

@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
escrow-dashboard ⬜️ Ignored (Inspect) Visit Preview Aug 26, 2024 9:08am
faucet-server ⬜️ Ignored (Inspect) Visit Preview Aug 26, 2024 9:08am

@Bobronium Bobronium changed the base branch from main to develop August 18, 2024 12:03
## What it does
Checks for unused imports.

## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. They also increase the cognitive load of reading the code.

If an import statement is used to check for the availability or existence
of a module, consider using `importlib.util.find_spec` instead.

If an import statement is used to re-export a symbol as part of a module's
public interface, consider using a "redundant" import alias, which
instructs Ruff (and other tools) to respect the re-export, and avoid
marking it as unused, as in:

```python
from module import member as member
```

Alternatively, you can use `__all__` to declare a symbol as part of the module's
interface, as in:

```python
# __init__.py
import some_module

__all__ = ["some_module"]
```

## Fix safety

Fixes to remove unused imports are safe, except in `__init__.py` files.

Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the
type of the unused import. Ruff will suggest a safe fix to export first-party imports with
either a redundant alias or, if already present in the file, an `__all__` entry. If multiple
`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix
to remove third-party and standard library imports -- the fix is unsafe because the module's
interface changes.

## Example

```python
import numpy as np  # unused import

def area(radius):
    return 3.14 * radius**2
```

Use instead:

```python
def area(radius):
    return 3.14 * radius**2
```

To check the availability of a module, use `importlib.util.find_spec`:

```python
from importlib.util import find_spec

if find_spec("numpy") is not None:
    print("numpy is installed")
else:
    print("numpy is not installed")
```

## Options
- `lint.ignore-init-module-imports`

## References
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
## What it does
Checks for unused imports.

## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. They also increase the cognitive load of reading the code.

If an import statement is used to check for the availability or existence
of a module, consider using `importlib.util.find_spec` instead.

If an import statement is used to re-export a symbol as part of a module's
public interface, consider using a "redundant" import alias, which
instructs Ruff (and other tools) to respect the re-export, and avoid
marking it as unused, as in:

```python
from module import member as member
```

Alternatively, you can use `__all__` to declare a symbol as part of the module's
interface, as in:

```python
# __init__.py
import some_module

__all__ = ["some_module"]
```

## Fix safety

Fixes to remove unused imports are safe, except in `__init__.py` files.

Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the
type of the unused import. Ruff will suggest a safe fix to export first-party imports with
either a redundant alias or, if already present in the file, an `__all__` entry. If multiple
`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix
to remove third-party and standard library imports -- the fix is unsafe because the module's
interface changes.

## Example

```python
import numpy as np  # unused import

def area(radius):
    return 3.14 * radius**2
```

Use instead:

```python
def area(radius):
    return 3.14 * radius**2
```

To check the availability of a module, use `importlib.util.find_spec`:

```python
from importlib.util import find_spec

if find_spec("numpy") is not None:
    print("numpy is installed")
else:
    print("numpy is not installed")
```

## Options
- `lint.ignore-init-module-imports`

## References
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
- [Typing documentation: interface conventions](https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols)
## What it does
Checks for `zip` calls without an explicit `strict` parameter.

## Why is this bad?
By default, if the iterables passed to `zip` are of different lengths, the
resulting iterator will be silently truncated to the length of the shortest
iterable. This can lead to subtle bugs.

Use the `strict` parameter to raise a `ValueError` if the iterables are of
non-uniform length. If the iterables are intentionally different lengths, the
parameter should be explicitly set to `False`.

## Example
```python
zip(a, b)
```

Use instead:
```python
zip(a, b, strict=True)
```

## Fix safety
This rule's fix is marked as unsafe for `zip` calls that contain
`**kwargs`, as adding a `strict` keyword argument to such a call may lead
to a duplicate keyword argument error.

## References
- [Python documentation: `zip`](https://docs.python.org/3/library/functions.html#zip)
## What it does
Checks for membership tests using `not {element} in {collection}`.

## Why is this bad?
Testing membership with `{element} not in {collection}` is more readable.

## Example
```python
Z = not X in Y
if not X.B in Y:
    pass
```

Use instead:
```python
Z = X not in Y
if X.B not in Y:
    pass
```
## What it does
Checks for f-strings that do not contain any placeholder expressions.

## Why is this bad?
f-strings are a convenient way to format strings, but they are not
necessary if there are no placeholder expressions to format. In this
case, a regular string should be used instead, as an f-string without
placeholders can be confusing for readers, who may expect such a
placeholder to be present.

An f-string without any placeholders could also indicate that the
author forgot to add a placeholder expression.

## Example
```python
f"Hello, world!"
```

Use instead:
```python
"Hello, world!"
```

**Note:** to maintain compatibility with PyFlakes, this rule only flags
f-strings that are part of an implicit concatenation if _none_ of the
f-string segments contain placeholder expressions.

For example:

```python
# Will not be flagged.
(
    f"Hello,"
    f" {name}!"
)

# Will be flagged.
(
    f"Hello,"
    f" World!"
)
```

See [#10885](astral-sh/ruff#10885) for more.

## References
- [PEP 498](https://www.python.org/dev/peps/pep-0498/)
## What it does
Checks for submodule imports that are aliased to the submodule name.

## Why is this bad?
Using the `from` keyword to import the submodule is more concise and
readable.

## Example
```python
import concurrent.futures as futures
```

Use instead:
```python
from concurrent import futures
```

## References
- [Python documentation: Submodules](https://docs.python.org/3/reference/import.html#submodules)
## What it does
Checks for conditions that position a constant on the left-hand side of the
comparison operator, rather than the right-hand side.

## Why is this bad?
These conditions (sometimes referred to as "Yoda conditions") are less
readable than conditions that place the variable on the left-hand side of
the comparison operator.

In some languages, Yoda conditions are used to prevent accidental
assignment in conditions (i.e., accidental uses of the `=` operator,
instead of the `==` operator). However, Python does not allow assignments
in conditions unless using the `:=` operator, so Yoda conditions provide
no benefit in this regard.

## Example
```python
if "Foo" == foo:
    ...
```

Use instead:
```python
if foo == "Foo":
    ...
```

## References
- [Python documentation: Comparisons](https://docs.python.org/3/reference/expressions.html#comparisons)
- [Python documentation: Assignment statements](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements)
## What it does
Checks for `dict.get()` calls that pass `None` as the default value.

## Why is this bad?
`None` is the default value for `dict.get()`, so it is redundant to pass it
explicitly.

## Example
```python
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
age = ages.get("Cat", None)
```

Use instead:
```python
ages = {"Tom": 23, "Maria": 23, "Dog": 11}
age = ages.get("Cat")
```

## References
- [Python documentation: `dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get)
## What it does
Checks for uses of deprecated methods from the `unittest` module.

## Why is this bad?
The `unittest` module has deprecated aliases for some of its methods.
The aliases may be removed in future versions of Python. Instead,
use their non-deprecated counterparts.

## Example
```python
from unittest import TestCase

class SomeTest(TestCase):
    def test_something(self):
        self.assertEquals(1, 1)
```

Use instead:
```python
from unittest import TestCase

class SomeTest(TestCase):
    def test_something(self):
        self.assertEqual(1, 1)
```

## References
- [Python documentation: Deprecated aliases](https://docs.python.org/3/library/unittest.html#deprecated-aliases)
## What it does
Checks for the use of generics that can be replaced with standard library
variants based on [PEP 585].

## Why is this bad?
[PEP 585] enabled collections in the Python standard library (like `list`)
to be used as generics directly, instead of importing analogous members
from the `typing` module (like `typing.List`).

When available, the [PEP 585] syntax should be used instead of importing
members from the `typing` module, as it's more concise and readable.
Importing those members from `typing` is considered deprecated as of [PEP
585].

This rule is enabled when targeting Python 3.9 or later (see:
[`target-version`]). By default, it's _also_ enabled for earlier Python
versions if `from __future__ import annotations` is present, as
`__future__` annotations are not evaluated at runtime. If your code relies
on runtime type annotations (either directly or via a library like
Pydantic), you can disable this behavior for Python versions prior to 3.9
by setting [`lint.pyupgrade.keep-runtime-typing`] to `true`.

## Example
```python
from typing import List

foo: List[int] = [1, 2, 3]
```

Use instead:
```python
foo: list[int] = [1, 2, 3]
```

## Fix safety
This rule's fix is marked as unsafe, as it may lead to runtime errors when
alongside libraries that rely on runtime type annotations, like Pydantic,
on Python versions prior to Python 3.9.

## Options
- `target-version`
- `lint.pyupgrade.keep-runtime-typing`

[PEP 585]: https://peps.python.org/pep-0585/
## What it does
Check for type annotations that can be rewritten based on [PEP 604] syntax.

## Why is this bad?
[PEP 604] introduced a new syntax for union type annotations based on the
`|` operator. This syntax is more concise and readable than the previous
`typing.Union` and `typing.Optional` syntaxes.

This rule is enabled when targeting Python 3.10 or later (see:
[`target-version`]). By default, it's _also_ enabled for earlier Python
versions if `from __future__ import annotations` is present, as
`__future__` annotations are not evaluated at runtime. If your code relies
on runtime type annotations (either directly or via a library like
Pydantic), you can disable this behavior for Python versions prior to 3.10
by setting [`lint.pyupgrade.keep-runtime-typing`] to `true`.

## Example
```python
from typing import Union

foo: Union[int, str] = 1
```

Use instead:
```python
foo: int | str = 1
```

## Fix safety
This rule's fix is marked as unsafe, as it may lead to runtime errors when
alongside libraries that rely on runtime type annotations, like Pydantic,
on Python versions prior to Python 3.10. It may also lead to runtime errors
in unusual and likely incorrect type annotations where the type does not
support the `|` operator.

## Options
- `target-version`
- `lint.pyupgrade.keep-runtime-typing`

[PEP 604]: https://peps.python.org/pep-0604/
## What it does
Checks for unnecessary calls to `encode` as UTF-8.

## Why is this bad?
UTF-8 is the default encoding in Python, so there is no need to call
`encode` when UTF-8 is the desired encoding. Instead, use a bytes literal.

## Example
```python
"foo".encode("utf-8")
```

Use instead:
```python
b"foo"
```

## References
- [Python documentation: `str.encode`](https://docs.python.org/3/library/stdtypes.html#str.encode)
## What it does
Checks for `str.format` calls that can be replaced with f-strings.

## Why is this bad?
f-strings are more readable and generally preferred over `str.format`
calls.

## Example
```python
"{}".format(foo)
```

Use instead:
```python
f"{foo}"
```

## References
- [Python documentation: f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
## What it does
Checks for uses of deprecated imports based on the minimum supported
Python version.

## Why is this bad?
Deprecated imports may be removed in future versions of Python, and
should be replaced with their new equivalents.

Note that, in some cases, it may be preferable to continue importing
members from `typing_extensions` even after they're added to the Python
standard library, as `typing_extensions` can backport bugfixes and
optimizations from later Python versions. This rule thus avoids flagging
imports from `typing_extensions` in such cases.

## Example
```python
from collections import Sequence
```

Use instead:
```python
from collections.abc import Sequence
```
## What it does
Checks for the presence of unnecessary quotes in type annotations.

## Why is this bad?
In Python, type annotations can be quoted to avoid forward references.

However, if `from __future__ import annotations` is present, Python
will always evaluate type annotations in a deferred manner, making
the quotes unnecessary.

Similarly, if the annotation is located in a typing-only context and
won't be evaluated by Python at runtime, the quotes will also be
considered unnecessary. For example, Python does not evaluate type
annotations on assignments in function bodies.

## Example
Given:
```python
from __future__ import annotations

def foo(bar: "Bar") -> "Bar":
    ...
```

Use instead:
```python
from __future__ import annotations

def foo(bar: Bar) -> Bar:
    ...
```

Given:
```python
def foo() -> None:
    bar: "Bar"
```

Use instead:
```python
def foo() -> None:
    bar: Bar
```

## References
- [PEP 563](https://peps.python.org/pep-0563/)
- [Python documentation: `__future__`](https://docs.python.org/3/library/__future__.html#module-__future__)
…-method

## What it does
Checks that "special" methods, like `__init__`, `__new__`, and `__call__`, have
return type annotations.

## Why is this bad?
Type annotations are a good way to document the return types of functions. They also
help catch bugs, when used alongside a type checker, by ensuring that the types of
any returned values, and the types expected by callers, match expectation.

Note that type checkers often allow you to omit the return type annotation for
`__init__` methods, as long as at least one argument has a type annotation. To
opt in to this behavior, use the `mypy-init-return` setting in your `pyproject.toml`
or `ruff.toml` file:

```toml
[tool.ruff.lint.flake8-annotations]
mypy-init-return = true
```

## Example
```python
class Foo:
    def __init__(self, x: int):
        self.x = x
```

Use instead:
```python
class Foo:
    def __init__(self, x: int) -> None:
        self.x = x
```
…ethod

## What it does
Checks that class methods have return type annotations.

## Why is this bad?
Type annotations are a good way to document the return types of functions. They also
help catch bugs, when used alongside a type checker, by ensuring that the types of
any returned values, and the types expected by callers, match expectation.

## Example
```python
class Foo:
    @classmethod
    def bar(cls):
        return 1
```

Use instead:
```python
class Foo:
    @classmethod
    def bar(cls) -> int:
        return 1
```
## What it does
Checks for uses of mutable objects as function argument defaults.

## Why is this bad?
Function defaults are evaluated once, when the function is defined.

The same mutable object is then shared across all calls to the function.
If the object is modified, those modifications will persist across calls,
which can lead to unexpected behavior.

Instead, prefer to use immutable data structures, or take `None` as a
default, and initialize a new mutable object inside the function body
for each call.

Arguments with immutable type annotations will be ignored by this rule.
Types outside of the standard library can be marked as immutable with the
[`lint.flake8-bugbear.extend-immutable-calls`] configuration option.

## Known problems
Mutable argument defaults can be used intentionally to cache computation
results. Replacing the default with `None` or an immutable data structure
does not work for such usages. Instead, prefer the `@functools.lru_cache`
decorator from the standard library.

## Example
```python
def add_to_list(item, some_list=[]):
    some_list.append(item)
    return some_list

l1 = add_to_list(0)  # [0]
l2 = add_to_list(1)  # [0, 1]
```

Use instead:
```python
def add_to_list(item, some_list=None):
    if some_list is None:
        some_list = []
    some_list.append(item)
    return some_list

l1 = add_to_list(0)  # [0]
l2 = add_to_list(1)  # [1]
```

## Options
- `lint.flake8-bugbear.extend-immutable-calls`

## References
- [Python documentation: Default Argument Values](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values)
## What it does
Checks for unused variables in loops (e.g., `for` and `while` statements).

## Why is this bad?
Defining a variable in a loop statement that is never used can confuse
readers.

If the variable is intended to be unused (e.g., to facilitate
destructuring of a tuple or other object), prefix it with an underscore
to indicate the intent. Otherwise, remove the variable entirely.

## Example
```python
for i, j in foo:
    bar(i)
```

Use instead:
```python
for i, _j in foo:
    bar(i)
```

## References
- [PEP 8: Naming Conventions](https://peps.python.org/pep-0008/#naming-conventions)
## What it does
Checks for uses of `assert False`.

## Why is this bad?
Python removes `assert` statements when running in optimized mode
(`python -O`), making `assert False` an unreliable means of
raising an `AssertionError`.

Instead, raise an `AssertionError` directly.

## Example
```python
assert False
```

Use instead:
```python
raise AssertionError
```

## Fix safety
This rule's fix is marked as unsafe, as changing an `assert` to a
`raise` will change the behavior of your program when running in
optimized mode (`python -O`).

## References
- [Python documentation: `assert`](https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement)
## What it does
Checks for unnecessary generators that can be rewritten as `list`
comprehensions (or with `list` directly).

## Why is this bad?
It is unnecessary to use `list` around a generator expression, since
there are equivalent comprehensions for these types. Using a
comprehension is clearer and more idiomatic.

Further, if the comprehension can be removed entirely, as in the case of
`list(x for x in foo)`, it's better to use `list(foo)` directly, since it's
even more direct.

## Examples
```python
list(f(x) for x in foo)
list(x for x in foo)
list((x for x in foo))
```

Use instead:
```python
[f(x) for x in foo]
list(foo)
list(foo)
```

## Fix safety
This rule's fix is marked as unsafe, as it may occasionally drop comments
when rewriting the call. In most cases, though, comments will be preserved.
## What it does
Checks for unnecessary `dict`, `list` or `tuple` calls that can be
rewritten as empty literals.

## Why is this bad?
It's unnecessary to call, e.g., `dict()` as opposed to using an empty
literal (`{}`). The former is slower because the name `dict` must be
looked up in the global scope in case it has been rebound.

## Examples
```python
dict()
dict(a=1, b=2)
list()
tuple()
```

Use instead:
```python
{}
{"a": 1, "b": 2}
[]
()
```

## Fix safety
This rule's fix is marked as unsafe, as it may occasionally drop comments
when rewriting the call. In most cases, though, comments will be preserved.

## Options
- `lint.flake8-comprehensions.allow-dict-calls-with-keyword-arguments`
## What it does
Checks for the presence of unused variables in function scopes.

## Why is this bad?
A variable that is defined but not used is likely a mistake, and should
be removed to avoid confusion.

If a variable is intentionally defined-but-not-used, it should be
prefixed with an underscore, or some other value that adheres to the
[`lint.dummy-variable-rgx`] pattern.

Under [preview mode](https://docs.astral.sh/ruff/preview), this rule also
triggers on unused unpacked assignments (for example, `x, y = foo()`).

## Example
```python
def foo():
    x = 1
    y = 2
    return x
```

Use instead:
```python
def foo():
    x = 1
    return x
```

## Options
- `lint.dummy-variable-rgx`
## What it does
Checks for `str.join` calls that can be replaced with f-strings.

## Why is this bad?
f-strings are more readable and generally preferred over `str.join` calls.

## Example
```python
" ".join((foo, bar))
```

Use instead:
```python
f"{foo} {bar}"
```

## References
- [Python documentation: f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
## What it does
Checks for uses of assertion methods from the `unittest` module.

## Why is this bad?
To make use of `pytest`'s assertion rewriting, a regular `assert` statement
is preferred over `unittest`'s assertion methods.

## Example
```python
import unittest

class TestFoo(unittest.TestCase):
    def test_foo(self):
        self.assertEqual(a, b)
```

Use instead:
```python
import unittest

class TestFoo(unittest.TestCase):
    def test_foo(self):
        assert a == b
```

## References
- [`pytest` documentation: Assertion introspection details](https://docs.pytest.org/en/7.1.x/how-to/assert.html#assertion-introspection-details)
…tion

## What it does
Checks for uses of exception-related assertion methods from the `unittest`
module.

## Why is this bad?
To enforce the assertion style recommended by `pytest`, `pytest.raises` is
preferred over the exception-related assertion methods in `unittest`, like
`assertRaises`.

## Example
```python
import unittest

class TestFoo(unittest.TestCase):
    def test_foo(self):
        with self.assertRaises(ValueError):
            raise ValueError("foo")
```

Use instead:
```python
import unittest
import pytest

class TestFoo(unittest.TestCase):
    def test_foo(self):
        with pytest.raises(ValueError):
            raise ValueError("foo")
```

## References
- [`pytest` documentation: Assertions about expected exceptions](https://docs.pytest.org/en/latest/how-to/assert.html#assertions-about-expected-exceptions)
## What it does
Checks for variable assignments that immediately precede a `return` of the
assigned variable.

## Why is this bad?
The variable assignment is not necessary, as the value can be returned
directly.

## Example
```python
def foo():
    bar = 1
    return bar
```

Use instead:
```python
def foo():
    return 1
```
…exception

## What it does
Checks for unnecessary parentheses on raised exceptions.

## Why is this bad?
If an exception is raised without any arguments, parentheses are not
required, as the `raise` statement accepts either an exception instance
or an exception class (which is then implicitly instantiated).

Removing the parentheses makes the code more concise.

## Known problems
Parentheses can only be omitted if the exception is a class, as opposed to
a function call. This rule isn't always capable of distinguishing between
the two.

For example, if you import a function `module.get_exception` from another
module, and `module.get_exception` returns an exception object, this rule will
incorrectly mark the parentheses in `raise module.get_exception()` as
unnecessary.

## Example
```python
raise TypeError()
```

Use instead:
```python
raise TypeError
```

## References
- [Python documentation: The `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)
## What it does
Checks for multiple `isinstance` calls on the same target.

## Why is this bad?
To check if an object is an instance of any one of multiple types
or classes, it is unnecessary to use multiple `isinstance` calls, as
the second argument of the `isinstance` built-in function accepts a
tuple of types and classes.

Using a single `isinstance` call implements the same behavior with more
concise code and clearer intent.

## Example
```python
if isinstance(obj, int) or isinstance(obj, float):
    pass
```

Use instead:
```python
if isinstance(obj, (int, float)):
    pass
```

## References
- [Python documentation: `isinstance`](https://docs.python.org/3/library/functions.html#isinstance)
## What it does
Checks for nested `if` statements that can be collapsed into a single `if`
statement.

## Why is this bad?
Nesting `if` statements leads to deeper indentation and makes code harder to
read. Instead, combine the conditions into a single `if` statement with an
`and` operator.

## Example
```python
if foo:
    if bar:
        ...
```

Use instead:
```python
if foo and bar:
    ...
```

## References
- [Python documentation: The `if` statement](https://docs.python.org/3/reference/compound_stmts.html#the-if-statement)
- [Python documentation: Boolean operations](https://docs.python.org/3/reference/expressions.html#boolean-operations)
…-exp

## What it does
Check for `if`-`else`-blocks that can be replaced with a ternary operator.

## Why is this bad?
`if`-`else`-blocks that assign a value to a variable in both branches can
be expressed more concisely by using a ternary operator.

## Example
```python
if foo:
    bar = x
else:
    bar = y
```

Use instead:
```python
bar = x if foo else y
```

## References
- [Python documentation: Conditional expressions](https://docs.python.org/3/reference/expressions.html#conditional-expressions)
## What it does
Checks for first-party imports that are only used for type annotations, but
aren't defined in a type-checking block.

## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. If an import is _only_ used in typing-only contexts, it can
instead be imported conditionally under an `if TYPE_CHECKING:` block to
minimize runtime overhead.

If [`lint.flake8-type-checking.quote-annotations`] is set to `true`,
annotations will be wrapped in quotes if doing so would enable the
corresponding import to be moved into an `if TYPE_CHECKING:` block.

If a class _requires_ that type annotations be available at runtime (as is
the case for Pydantic, SQLAlchemy, and other libraries), consider using
the [`lint.flake8-type-checking.runtime-evaluated-base-classes`] and
[`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
as such.

## Example
```python
from __future__ import annotations

import local_module

def func(sized: local_module.Container) -> int:
    return len(sized)
```

Use instead:
```python
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import local_module

def func(sized: local_module.Container) -> int:
    return len(sized)
```

## Options
- `lint.flake8-type-checking.quote-annotations`
- `lint.flake8-type-checking.runtime-evaluated-base-classes`
- `lint.flake8-type-checking.runtime-evaluated-decorators`
- `lint.typing-modules`

## References
- [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
## What it does
Checks for third-party imports that are only used for type annotations, but
aren't defined in a type-checking block.

## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. If an import is _only_ used in typing-only contexts, it can
instead be imported conditionally under an `if TYPE_CHECKING:` block to
minimize runtime overhead.

If [`lint.flake8-type-checking.quote-annotations`] is set to `true`,
annotations will be wrapped in quotes if doing so would enable the
corresponding import to be moved into an `if TYPE_CHECKING:` block.

If a class _requires_ that type annotations be available at runtime (as is
the case for Pydantic, SQLAlchemy, and other libraries), consider using
the [`lint.flake8-type-checking.runtime-evaluated-base-classes`] and
[`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
as such.

## Example
```python
from __future__ import annotations

import pandas as pd

def func(df: pd.DataFrame) -> int:
    return len(df)
```

Use instead:
```python
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import pandas as pd

def func(df: pd.DataFrame) -> int:
    return len(df)
```

## Options
- `lint.flake8-type-checking.quote-annotations`
- `lint.flake8-type-checking.runtime-evaluated-base-classes`
- `lint.flake8-type-checking.runtime-evaluated-decorators`
- `lint.typing-modules`

## References
- [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
…y-import

## What it does
Checks for standard library imports that are only used for type
annotations, but aren't defined in a type-checking block.

## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. If an import is _only_ used in typing-only contexts, it can
instead be imported conditionally under an `if TYPE_CHECKING:` block to
minimize runtime overhead.

If [`lint.flake8-type-checking.quote-annotations`] is set to `true`,
annotations will be wrapped in quotes if doing so would enable the
corresponding import to be moved into an `if TYPE_CHECKING:` block.

If a class _requires_ that type annotations be available at runtime (as is
the case for Pydantic, SQLAlchemy, and other libraries), consider using
the [`lint.flake8-type-checking.runtime-evaluated-base-classes`] and
[`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
as such.

## Example
```python
from __future__ import annotations

from pathlib import Path

def func(path: Path) -> str:
    return str(path)
```

Use instead:
```python
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pathlib import Path

def func(path: Path) -> str:
    return str(path)
```

## Options
- `lint.flake8-type-checking.quote-annotations`
- `lint.flake8-type-checking.runtime-evaluated-base-classes`
- `lint.flake8-type-checking.runtime-evaluated-decorators`
- `lint.typing-modules`

## References
- [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
## What it does
Checks for conditions that position a constant on the left-hand side of the
comparison operator, rather than the right-hand side.

## Why is this bad?
These conditions (sometimes referred to as "Yoda conditions") are less
readable than conditions that place the variable on the left-hand side of
the comparison operator.

In some languages, Yoda conditions are used to prevent accidental
assignment in conditions (i.e., accidental uses of the `=` operator,
instead of the `==` operator). However, Python does not allow assignments
in conditions unless using the `:=` operator, so Yoda conditions provide
no benefit in this regard.

## Example
```python
if "Foo" == foo:
    ...
```

Use instead:
```python
if foo == "Foo":
    ...
```

## References
- [Python documentation: Comparisons](https://docs.python.org/3/reference/expressions.html#comparisons)
- [Python documentation: Assignment statements](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements)
## What it does
Checks for the use of implicit `Optional` in type annotations when the
default parameter value is `None`.

## Why is this bad?
Implicit `Optional` is prohibited by [PEP 484]. It is confusing and
inconsistent with the rest of the type system.

It's recommended to use `Optional[T]` instead. For Python 3.10 and later,
you can also use `T | None`.

## Example
```python
def foo(arg: int = None):
    pass
```

Use instead:
```python
from typing import Optional

def foo(arg: Optional[int] = None):
    pass
```

Or, for Python 3.10 and later:
```python
def foo(arg: int | None = None):
    pass
```

If you want to use the `|` operator in Python 3.9 and earlier, you can
use future imports:
```python
from __future__ import annotations

def foo(arg: int | None = None):
    pass
```

## Limitations

Type aliases are not supported and could result in false negatives.
For example, the following code will not be flagged:
```python
Text = str | bytes

def foo(arg: Text = None):
    pass
```

## Options
- `target-version`

[PEP 484]: https://peps.python.org/pep-0484/#union-types
## What it does
Checks for uses of `isinstance` and `issubclass` that take a tuple
of types for comparison.

## Why is this bad?
Since Python 3.10, `isinstance` and `issubclass` can be passed a
`|`-separated union of types, which is consistent
with the union operator introduced in [PEP 604].

Note that this results in slower code. Ignore this rule if the
performance of an `isinstance` or `issubclass` check is a
concern, e.g., in a hot loop.

## Example
```python
isinstance(x, (int, float))
```

Use instead:
```python
isinstance(x, int | float)
```

## Options
- `target-version`

## References
- [Python documentation: `isinstance`](https://docs.python.org/3/library/functions.html#isinstance)
- [Python documentation: `issubclass`](https://docs.python.org/3/library/functions.html#issubclass)

[PEP 604]: https://peps.python.org/pep-0604/
## What it does
Checks for relative imports.

## Why is this bad?
Absolute imports, or relative imports from siblings, are recommended by [PEP 8]:

> Absolute imports are recommended, as they are usually more readable and tend to be better behaved...
> ```python
> import mypkg.sibling
> from mypkg import sibling
> from mypkg.sibling import example
> ```
> However, explicit relative imports are an acceptable alternative to absolute imports,
> especially when dealing with complex package layouts where using absolute imports would be
> unnecessarily verbose:
> ```python
> from . import sibling
> from .sibling import example
> ```

## Example
```python
from .. import foo
```

Use instead:
```python
from mypkg import foo
```

## Options
- `lint.flake8-tidy-imports.ban-relative-imports`

[PEP 8]: https://peps.python.org/pep-0008/#imports
@Bobronium Bobronium force-pushed the ba/recording-oracle/add-ruff branch from 01e308c to e4b92ee Compare August 18, 2024 12:06
@Bobronium Bobronium marked this pull request as ready for review August 19, 2024 07:34
@Bobronium Bobronium requested a review from zhiltsov-max August 19, 2024 07:34
@Bobronium Bobronium force-pushed the ba/recording-oracle/add-ruff branch from e4b92ee to 6edcfaf Compare August 19, 2024 10:56
@Bobronium Bobronium force-pushed the ba/recording-oracle/add-ruff branch from 6edcfaf to 5514647 Compare August 19, 2024 11:10
@zhiltsov-max zhiltsov-max merged commit 2736ceb into develop Aug 27, 2024
@zhiltsov-max zhiltsov-max changed the title [Recording Oracle] Add ruff [CVAT] [Recording Oracle] Add ruff Aug 27, 2024
@flopez7 flopez7 deleted the ba/recording-oracle/add-ruff branch November 5, 2024 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants