User Guide

Quick Start

Setup

Install Fixit from PyPI:

$ pip install --pre "fixit >1"

By default, Fixit enables all of the lint rules that ship with Fixit, all of which are part of the fixit.rules package.

If you want to customize the list of enabled rules, either to add new rules or disable others, see the Configuration Guide for details and options available.

If you are upgrading from previous versions of Fixit, look at the Upgrade Guide for a list of changes and tools to assist with migrating to the latest version.

Usage

See lints and suggested changes for a set of source files:

$ fixit lint <path>

Apply suggested changes on those same files automatically:

$ fixit fix <path>

If given directories, Fixit will automatically recurse them, finding any files with the .py extension, while obeying your repo’s global .gitignore.

See the Command Reference for more details.

Example

Given the following code:

# custom_object.py

class Foo(object):
    def bar(self, value: str) -> str:
        return "value is {}".format(value)

When running Fixit, we see two separate lint errors:

$ fixit lint custom_object.py
custom_object.py@4:15 UseFstring: Do not use printf style formatting or .format(). Use f-string instead to be more readable and efficient. See https://www.python.org/dev/peps/pep-0498/
custom_object.py@2:0 NoInheritFromObject: Inheriting from object is a no-op.  'class Foo:' is just fine =)

We can also see any suggested changes by passing --diff:

$ fixit lint --diff custom_object.py
custom_object.py@7:0 NoInheritFromObject: Inheriting from object is a no-op.  'class Foo:' is just fine =) (has autofix)
--- a/custom_object.py
+++ b/custom_object.py
@@ -6,3 +6,3 @@
# Triggers built-in lint rules
-class Foo(object):
+class Foo:
    def bar(self, value: str) -> str:
custom_object.py@9:15 UseFstring: Do not use printf style formatting or .format(). Use f-string instead to be more readable and efficient. See https://www.python.org/dev/peps/pep-0498/
🛠️  1 file checked, 1 file with errors, 1 auto-fix available 🛠️

Silencing Errors

For lint rules without autofixes, it may still be useful to silence individual errors. A simple # lint-ignore or # lint-fixme comment, either as a trailing inline comment, or as a dedicated comment line above the code that triggered the lint rule:

class Foo(object):  # lint-fixme: NoInheritFromObject
    ...

# lint-ignore: NoInheritFromObject
class Bar(object):
    ...

By providing one or more lint rule, separated by commas, Fixit can still report issues triggered by other lint rules that haven’t been listed in the comment, but this is not required.

If no rule name is listed, Fixit will silence all rules when reported on code associated with that comment:

class Foo(object):  # lint-ignore
    ...

“ignore” vs “fixme”

Both comment directives achieve the same result — silencing errors for a particular statement of code. The semantics of using either term is left to the user, though they are intended to be used with the following meanings:

  • # lint-fixme for errors that need to be corrected or reviewed at a later date, but where the lint rule should be silenced temporarily for the sake of CI or similar external circumstances.

  • # lint-ignore for errors that are false-positives (please report issues if this occurs with built-in lint rules) or the code is otherwise intentionally written or structured in a way that the lint error cannot be avoided.

Future versions of Fixit may offer reporting or similar tools that treat “fixme” directives differently from “ignore” directives.

Custom Rules

Fixit makes it easy to write and enable new lint rules, directly in your existing codebase alongside the code they will be linting.

Lint rules in Fixit are built on top of LibCST using a LintRule to combine visitors and tests together in a single unit. A (very) simple rule looks like this:

# teambread/rules/hollywood.py

from fixit import LintRule, InvalidTestCase, ValidTestCase
import libcst

class HollywoodNameRule(LintRule):
    # clean code samples
    VALID = [
        ValidTestCase('name = "Susan"'),
    ]
    # code that triggers this rule
    INVALID = [
        InvalidTestCase('name = "Paul"'),
    ]

    def visit_SimpleString(self, node: libcst.SimpleString) -> None:
        if node.value in ('"Paul"', "'Paul'"):
            self.report(node, "It's underproved!")

Rules can suggest auto-fixes for the user by including a replacement CST node when reporting an error:

def visit_SimpleString(self, node: libcst.SimpleString) -> None:
    if node.value in ('"Paul"', "'Paul'"):
        new_node = libcst.SimpleString('"Mary"')
        self.report(node, "It's underproved!", replacement=new_node)

The best lint rules will provide a clear error message, a suggested replacement, and multiple valid and invalid tests cases that exercise as many edge cases for the lint rule as possible.

Once written, the new lint rule can be enabled by adding it to the list of enabled lint rules in the project’s Configuration file:

# teambread/pyproject.toml

[tool.fixit]
enable = [
    ".rules.hollywood",  # enable just the rules in hollywood.py
    ".rules",  # enable rules from all files in the rules/ directory
]

Note

The leading . (period) is required when using in-repo, or “local”, lint rules, with a module path relative to the directory containing the config file. This allows Fixit to locate and import the lint rule without needing to install a plugin in the user’s environment.

However, be aware that if your custom lint rule needs to import other libraries from the repo, those libraries must be imported using relative imports, and must be contained within the same directory tree as the configuration file.

Once enabled, Fixit can run that new lint rule against the codebase:

# teambread/sourdough/baker.py

def main():
    name = "Paul"
    print(f"hello {name}")
$ fixit lint --diff sourdough/baker.py
sourdough/baker.py@7:11 HollywoodName: It's underproved! (has autofix)
--- a/baker.py
+++ b/baker.py
@@ -6,3 +6,3 @@
def main():
-    name = "Paul"
+    name = "Mary"
    print(f"hello {name}")
🛠️  1 file checked, 1 file with errors, 1 auto-fix available 🛠️
[1]

Note that the lint command only shows lint errors (and suggested changes). The fix command will apply these suggested changes to the codebase:

$ fixit fix --automatic sourdough/baker.py
sourdough/baker.py@7:11 HollywoodName: It's underproved! (has autofix)
🛠️  1 file checked, 1 file with errors, 1 auto-fix available, 1 fix applied 🛠️

By default, the fix command will interactively prompt the user for each suggested change available, which the user can then accept or decline.

Now that the suggested changes have been applied, the codebase is clean:

$ fixit lint sourdough/baker.py
🧼 1 file clean 🧼

Commands

$ fixit [OPTIONS] COMMAND ...

The following options are available for all commands:

--debug / --quiet

Raise or lower the level of output and logging.

--config-file PATH

Override the normal hierarchical configuration and use the configuration from the specified path, ignoring all other configuration files entirely.

--tags TAGS

Select or filter the set of lint rules to apply based on their tags.

Takes a comma-separated list of tag names, optionally prefixed with !, ^, or -. Tags without one of those prefixes will be considered “include” tags, while tags with one of those prefixes will be considered “exclude” tags.

Lint rules will be enabled if and only if they have at least one tag that in the “include” list, and no tags in the “exclude” list.

For example:

$ fixit --tags "hello, world, ^cats" ...

The command above will filter the set of enabled lint rules to ones that have either the “hello” or “world” tags, and exclude any rules with the “cats” tag, even if they would have otherwise been selected by the other two tags.

lint

Lint one or more paths, and print a list of lint errors. If “-” is given as the first path, then the second given path will be used for configuration lookup and error messages, and the input read from STDIN.

$ fixit lint [--diff] [PATH ...]
--diff / -d

Show suggested fixes, in unified diff format, when available.

fix

Lint one or more paths, and apply suggested fixes. If “-” is given as the first path, then the second given path will be used for configuration lookup, the input read from STDIN, and the fixed output printed to STDOUT (ignoring --interactive).

$ fixit fix [--interactive | --automatic [--diff]] [PATH ...]
--interactive / -i

Interactively prompt the user to apply or decline suggested fixes for each auto-fix available. default

--automatic / -a

Automatically apply suggested fixes for all lint errors when available.

--diff / -d

Show applied fixes in unified diff format when applied automatically.

test

Test one or more lint rules using their VALID and INVALID test cases.

Expects qualified lint rule packages or names, with the same form as when configuring enable and disable.

$ fixit test [RULES ...]

Example:

$ fixit test .examples.teambread.rules
test_INVALID_0 (fixit.testing.HollywoodNameRule) ... ok
test_INVALID_1 (fixit.testing.HollywoodNameRule) ... ok
test_VALID_0 (fixit.testing.HollywoodNameRule) ... ok
test_VALID_1 (fixit.testing.HollywoodNameRule) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.024s

OK

upgrade

Upgrade lint rules or client code to the latest version of Fixit. Automatically applies fixes from all upgrade rules in fixit.upgrade.

Shortcut for fixit --rules fixit.upgrade fix --automatic <path>

$ fixit upgrade [PATH ...]

debug

Debug options for validating Fixit configuration.

$ fixit debug [PATH ...]

Configuration

Fixit uses TOML format for configuration, and supports hierarchical, cascading configuration. Fixit will read values from both the standardized pyproject.toml file as well as a separate .fixit.toml or fixit.toml file, with values from the latter taking precendence over the former, and values from files “nearer” to those being linted taking precedence over values from files “further” away.

When determining the configuration to use for a given path, Fixit will continue looking upward in the filesystem until it reaches either the root of the filesystem, or a configuration file is found with root set to True. Fixit will then read configuration values from each file, from further to nearest, and merge or override values as appropriate.

This behavior enables a monorepo to provide a baseline configuration, while individual projects can choose to either add to that baseline, or define their own root of configuration to ignore any other baseline configs. This also allows the inclusion of external or third-party projects to maintain consistent linting results, regardless of whether they are being linted externally or within the containing monorepo.

[tool.fixit]

The main configuration table.

root: bool = false

Marks this file as a root of the configuration hierarchy.

If set to true, Fixit will not visit any configuration files further up the filesystem hierarchy.

enable: list[str] = []

List of modules or individual rules to enable when linting files covered by this configuration.

Rules bundled with Fixit, or available in the environment’s site-packages, can be referenced as a group by their fully-qualified package name, or individually by adding a colon and the rule name:

enable = [
    "fixit.rules",  # all lint rules in this package (non-recursive)
    "fixit.rules:UseFstringRule",  # single lint rule by name
]

Local rules, available only in the repo being linted, can be referenced by their locally-qualified package names, as if they were being imported from a Python module relative to the configuration file specifying the rule:

# teambread/fixit.toml
enable = [
    ".rules",  # all rules in teambread/rules/ (non-recursive)
    ".rules.hollywood",  # all rules in teambread/rules/hollywood.py
    ".rules:HollywoodNameRule",  # single lint rule by name
]

Overrides disabled rules from any configuration further up the hierarchy.

Fixit will enable the built-in fixit.rules lint rules by default.

disable: list[str] = []

List of modules or individual rules to disable when linting files covered by this configuration.

Overrides enabled rules from this file, as well any configuration files further up the hierarchy.

See enable for details on referencing lint rules.

enable-root-import: bool | str

Allow importing local rules using absolute imports, relative to the root of the project. This provides an alternative to using dotted rule names for enabling and importing local rules (see enable) from either the directory containing the root config (when set to true), or a single, optional path relative to the root config.

For example, project orange using a src/orange/ project hierarchy could use the following config:

root = True
enable-root-import = "src"
enable = ["orange.rules"]

Assuming that the namespace orange is not already in site-packages, then orange.rules would be imported from src/orange/rules/, while also allowing these local rules to import from other components in the orange namespace.

This option may only be specified in the root config file. Specifying the option in any other config file is treated as a configuration error. Absolute paths, or paths containing .. parent-relative components, are not allowed.

This option is roughly equivalent to adding the configured path, relative to the root configuration, to sys.path when attempting to import and materialize any enabled lint rules.

python-version: str

Python version to target when selecting lint rules. Rules with PYTHON_VERSION specifiers that don’t match this target version will be automatically disabled during linting.

To target a minimum Python version of 3.10:

python-version = "3.10"

Defaults to the currently active version of Python. Set to empty string "" to disable target version checking.

formatter: str

Code formatting style to apply after fixing source files.

Supported code styles:

  • (unset): No style is applied (default).

  • "black": Black code formatter.

  • "ufmt": µfmt code style — µsort import sorting with Black code formatting.

Alternative formatting styles can be added by implementing the Formatter interface.

[tool.fixit.options]

The options table allows setting options for individual lint rules, by mapping the fully-qualified rule name to a dictionary of key/value pairs:

[tool.fixit.options]
"fixit.rules:ExampleRule" = {greeting = "hello world"}

Alternatively, for rules with a large number of options, the rule name can be included in the table name for easier usage. Note that the quotes in the table name are required for correctly specifying options:

[tool.fixit.options."fixit.rules:ExampleRule"]
greeting = "hello world"
answer = 42

[[tool.fixit.overrides]]

Overrides provide a mechanism for hierarchical configuration within a single configuration file. They are defined as an array of tables, with each table defining the subpath it applies to, along with any values from the tables above:

[[tool.fixit.overrides]]
path = "foo/bar"
disable = ["fixit.rules:ExampleRule"]

[[tool.fixit.overrides.options]]
# applies to the above override path only
"fixit.rules:Story" = {closing = "goodnight moon"}

[[tool.fixit.overrides]]
path = "fizz/buzz"
enable = ["plugin:SomethingNeat"]

Integrations

pre-commit

Fixit can be included as a hook for pre-commit.

Once you install it, you can add Fixit’s pre-commit hook to the .pre-commit-config.yaml file in your repository.

  • To run lint rules on commit, add:

repos:
  - repo: https://github.com/Instagram/Fixit
    rev: 0.0.0  # replace with the Fixit version to use
    hooks:
      - id: fixit-lint
  • To run lint rules and apply autofixes, add:

repos:
  - repo: https://github.com/Instagram/Fixit
    rev: 0.0.0  # replace with the Fixit version to use
    hooks:
      - id: fixit-fix

To read more about how you can customize your pre-commit configuration, see the pre-commit docs.

Built-in Rules

fixit.rules

These rules are all part of the fixit.rules package, and are enabled by default unless explicitly listed in the disable configuration option.

class fixit.rules.AvoidOrInExcept

Discourages use of or in except clauses. If an except clause needs to catch multiple exceptions, they must be expressed as a parenthesized tuple, for example: except (ValueError, TypeError) (https://docs.python.org/3/tutorial/errors.html#handling-exceptions)

When or is used, only the first operand exception type of the conditional statement will be caught. For example:

In [1]: class Exc1(Exception):
    ...:     pass
    ...:

In [2]: class Exc2(Exception):
    ...:     pass
    ...:

In [3]: try:
    ...:     raise Exception()
    ...: except Exc1 or Exc2:
    ...:     print("caught!")
    ...:
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-3-3340d66a006c> in <module>
    1 try:
----> 2     raise Exception()
    3 except Exc1 or Exc2:
    4     print("caught!")
    5

Exception:

In [4]: try:
    ...:     raise Exc1()
    ...: except Exc1 or Exc2:
    ...:     print("caught!")
    ...:
    caught!

In [5]: try:
    ...:     raise Exc2()
    ...: except Exc1 or Exc2:
    ...:     print("caught!")
    ...:
---------------------------------------------------------------------------
Exc2                                      Traceback (most recent call last)
<ipython-input-5-5d29c1589cc0> in <module>
    1 try:
----> 2     raise Exc2()
    3 except Exc1 or Exc2:
    4     print("caught!")
    5

Exc2:
MESSAGE

Avoid using ‘or’ in an except block. For example:’except ValueError or TypeError’ only catches ‘ValueError’. Instead, use parentheses, ‘except (ValueError, TypeError)’

VALID
try:
    print()
except (ValueError, TypeError) as err:
    pass
INVALID
try:
    print()
except ValueError or TypeError:
    pass
class fixit.rules.CollapseIsinstanceChecks

The built-in isinstance function, instead of a single type, can take a tuple of types and check whether given target suits any of them. Rather than chaining multiple isinstance calls with a boolean-or operation, a single isinstance call where the second argument is a tuple of all types can be used.

MESSAGE

Multiple isinstance calls with the same target but different types can be collapsed into a single call with a tuple of types.

AUTOFIX: Yes
VALID
foo() or foo()
foo(x, y) or foo(x, z)
INVALID
isinstance(x, y) or isinstance(x, z)

# suggested fix
isinstance(x, (y, z))
isinstance(x, y) or isinstance(x, z) or isinstance(x, q)

# suggested fix
isinstance(x, (y, z, q))
class fixit.rules.ComparePrimitivesByEqual

Enforces the use of == and != in comparisons to primitives rather than is and is not. The == operator checks equality (https://docs.python.org/3/reference/datamodel.html#object.__eq__), while is checks identity (https://docs.python.org/3/reference/expressions.html#is).

MESSAGE

Don’t use is or is not to compare primitives, as they compare references. Use == or != instead.

AUTOFIX: Yes
VALID
a == 1
a == '1'
INVALID
a is 1

# suggested fix
a == 1
a is '1'

# suggested fix
a == '1'
class fixit.rules.CompareSingletonPrimitivesByIs

Enforces the use of is and is not in comparisons to singleton primitives (None, True, False) rather than == and !=. The == operator checks equality, when in this scenario, we want to check identity. See Flake8 rules E711 (https://www.flake8rules.com/rules/E711.html) and E712 (https://www.flake8rules.com/rules/E712.html).

MESSAGE

Comparisons to singleton primitives should not be done with == or !=, as they check equality rather than identity. Use is or is not instead.

AUTOFIX: Yes
VALID
if x: pass
if not x: pass
INVALID
x != True

# suggested fix
x is not True
x != False

# suggested fix
x is not False
class fixit.rules.DeprecatedUnittestAsserts

Discourages the use of various deprecated unittest.TestCase functions

See https://docs.python.org/3/library/unittest.html#deprecated-aliases

MESSAGE

{deprecated} is deprecated, use {replacement} instead

AUTOFIX: Yes
VALID
self.assertEqual(a, b)
self.assertNotEqual(a, b)
INVALID
self.assertEquals(a, b)

# suggested fix
self.assertEqual(a, b)
self.assertNotEquals(a, b)

# suggested fix
self.assertNotEqual(a, b)
class fixit.rules.NoAssertTrueForComparisons

Finds incorrect use of assertTrue when the intention is to compare two values. These calls are replaced with assertEqual. Comparisons with True, False and None are replaced with one-argument calls to assertTrue, assertFalse and assertIsNone.

MESSAGE

“assertTrue” does not compare its arguments, use “assertEqual” or other appropriate functions.

AUTOFIX: Yes
VALID
self.assertTrue(a == b)
self.assertTrue(data.is_valid(), "is_valid() method")
INVALID
self.assertTrue(a, 3)

# suggested fix
self.assertEqual(a, 3)
self.assertTrue(hash(s[:4]), 0x1234)

# suggested fix
self.assertEqual(hash(s[:4]), 0x1234)
class fixit.rules.NoInheritFromObject

In Python 3, a class is inherited from object by default. Explicitly inheriting from object is redundant, so removing it keeps the code simpler.

MESSAGE

Inheriting from object is a no-op. ‘class Foo:’ is just fine =)

AUTOFIX: Yes
VALID
class A(something):    pass
class A:
    pass
INVALID
class B(object):
    pass

# suggested fix
class B:
    pass
class B(object, A):
    pass

# suggested fix
class B(A):
    pass
class fixit.rules.NoNamedTuple

Enforce the use of dataclasses.dataclass decorator instead of NamedTuple for cleaner customization and inheritance. It supports default value, combining fields for inheritance, and omitting optional fields at instantiation. See PEP 557. @dataclass is faster at reading an object’s nested properties and executing its methods. (benchmark)

MESSAGE

Instead of NamedTuple, consider using the @dataclass decorator from dataclasses instead for simplicity, efficiency and consistency.

AUTOFIX: Yes
VALID
@dataclass(frozen=True)
class Foo:
    pass
@dataclass(frozen=False)
class Foo:
    pass
INVALID
from typing import NamedTuple

class Foo(NamedTuple):
    pass

# suggested fix
from typing import NamedTuple

@dataclass(frozen=True)
class Foo:
    pass
from typing import NamedTuple as NT

class Foo(NT):
    pass

# suggested fix
from typing import NamedTuple as NT

@dataclass(frozen=True)
class Foo:
    pass
class fixit.rules.NoRedundantArgumentsSuper

Remove redundant arguments when using super for readability.

MESSAGE

Do not use arguments when calling super for the parent class. See https://www.python.org/dev/peps/pep-3135/

AUTOFIX: Yes
VALID
class Foo(Bar):
    def foo(self, bar):
        super().foo(bar)
class Foo(Bar):
    def foo(self, bar):
        super(Bar, self).foo(bar)
INVALID
class Foo(Bar):
    def foo(self, bar):
        super(Foo, self).foo(bar)

# suggested fix
class Foo(Bar):
    def foo(self, bar):
        super().foo(bar)
class Foo(Bar):
    @classmethod
    def foo(cls, bar):
        super(Foo, cls).foo(bar)

# suggested fix
class Foo(Bar):
    @classmethod
    def foo(cls, bar):
        super().foo(bar)
class fixit.rules.NoRedundantFString

Remove redundant f-string without placeholders.

MESSAGE

f-string doesn’t have placeholders, remove redundant f-string.

AUTOFIX: Yes
VALID
good: str = "good"
good: str = f"with_arg{arg}"
INVALID
bad: str = f"bad" + "bad"

# suggested fix
bad: str = "bad" + "bad"
bad: str = f'bad'

# suggested fix
bad: str = 'bad'
class fixit.rules.NoRedundantLambda

A lamba function which has a single objective of passing all it is arguments to another callable can be safely replaced by that callable.

AUTOFIX: Yes
VALID
lambda x: foo(y)
lambda x: foo(x, y)
INVALID
lambda: self.func()

# suggested fix
self.func
lambda x: foo(x)

# suggested fix
foo
class fixit.rules.NoRedundantListComprehension

A derivative of flake8-comprehensions’s C407 rule.

AUTOFIX: Yes
VALID
any(val for val in iterable)
all(val for val in iterable)
INVALID
any([val for val in iterable])

# suggested fix
any(val for val in iterable)
all([val for val in iterable])

# suggested fix
all(val for val in iterable)
class fixit.rules.NoStaticIfCondition

Discourages if conditions which evaluate to a static value (e.g. or True, and False, etc).

MESSAGE

Your if condition appears to evaluate to a static value (e.g. or True, and False). Please double check this logic and if it is actually temporary debug code.

VALID
if my_func() or not else_func():
    pass
if function_call(True):
    pass
INVALID
if True:
    do_something()
if crazy_expression or True:
    do_something()
class fixit.rules.NoStringTypeAnnotation

Enforce the use of type identifier instead of using string type hints for simplicity and better syntax highlighting. Starting in Python 3.7, from __future__ import annotations can postpone evaluation of type annotations PEP 563 and thus forward references no longer need to use string annotation style.

MESSAGE

String type hints are no longer necessary in Python, use the type identifier directly.

AUTOFIX: Yes
VALID
from a.b import Class

def foo() -> Class:
    return Class()
import typing
from a.b import Class

def foo() -> typing.Type[Class]:
    return Class
INVALID
from __future__ import annotations

from a.b import Class

def foo() -> "Class":
    return Class()

# suggested fix
from __future__ import annotations

from a.b import Class

def foo() -> Class:
    return Class()
from __future__ import annotations

from a.b import Class

async def foo() -> "Class":
    return await Class()

# suggested fix
from __future__ import annotations

from a.b import Class

async def foo() -> Class:
    return await Class()
class fixit.rules.ReplaceUnionWithOptional

Enforces the use of Optional[T] over Union[T, None] and Union[None, T]. See https://docs.python.org/3/library/typing.html#typing.Optional to learn more about Optionals.

MESSAGE

Optional[T] is preferred over Union[T, None] or Union[None, T]. Learn more: https://docs.python.org/3/library/typing.html#typing.Optional

AUTOFIX: Yes
VALID
def func() -> Optional[str]:
    pass
def func() -> Optional[Dict]:
    pass
INVALID
def func() -> Union[str, None]:
    pass
from typing import Optional
def func() -> Union[Dict[str, int], None]:
    pass

# suggested fix
from typing import Optional
def func() -> Optional[Dict[str, int]]:
    pass
class fixit.rules.RewriteToComprehension

A derivative of flake8-comprehensions’s C400-C402 and C403-C404. Comprehensions are more efficient than functions calls. This C400-C402 suggest to use dict/set/list comprehensions rather than respective function calls whenever possible. C403-C404 suggest to remove unnecessary list comprehension in a set/dict call, and replace it with set/dict comprehension.

AUTOFIX: Yes
VALID
[val for val in iterable]
{val for val in iterable}
INVALID
list(val for val in iterable)

# suggested fix
[val for val in iterable]
list(val for row in matrix for val in row)

# suggested fix
[val for row in matrix for val in row]
class fixit.rules.RewriteToLiteral

A derivative of flake8-comprehensions’ C405-C406 and C409-C410. It’s unnecessary to use a list or tuple literal within a call to tuple, list, set, or dict since there is literal syntax for these types.

AUTOFIX: Yes
VALID
(1, 2)
()
INVALID
tuple([1, 2])

# suggested fix
(1, 2)
tuple((1, 2))

# suggested fix
(1, 2)
class fixit.rules.SortedAttributes

Ever wanted to sort a bunch of class attributes alphabetically? Well now it’s easy! Just add “@sorted-attributes” in the doc string of a class definition and lint will automatically sort all attributes alphabetically.

Feel free to add other methods and such – it should only affect class attributes.

MESSAGE

It appears you are using the @sorted-attributes directive and the class variables are unsorted. See the lint autofix suggestion.

AUTOFIX: Yes
VALID
class MyConstants:
    """
    @sorted-attributes
    """
    A = 'zzz123'
    B = 'aaa234'

class MyUnsortedConstants:
    B = 'aaa234'
    A = 'zzz123'
INVALID
class MyUnsortedConstants:
    """
    @sorted-attributes
    """
    z = "hehehe"
    B = 'aaa234'
    A = 'zzz123'
    cab = "foo bar"
    Daaa = "banana"

    @classmethod
    def get_foo(cls) -> str:
        return "some random thing"

# suggested fix
class MyUnsortedConstants:
    """
    @sorted-attributes
    """
    A = 'zzz123'
    B = 'aaa234'
    Daaa = "banana"
    cab = "foo bar"
    z = "hehehe"

    @classmethod
    def get_foo(cls) -> str:
        return "some random thing"
class fixit.rules.UseAssertIn

Discourages use of assertTrue(x in y) and assertFalse(x in y) as it is deprecated (https://docs.python.org/3.8/library/unittest.html#deprecated-aliases). Use assertIn(x, y) and assertNotIn(x, y)) instead.

MESSAGE

Use assertIn/assertNotIn instead of assertTrue/assertFalse for inclusion check. See https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn)

AUTOFIX: Yes
VALID
self.assertIn(a, b)
self.assertIn(f(), b)
INVALID
self.assertTrue(a in b)

# suggested fix
self.assertIn(a, b)
self.assertTrue(f() in b)

# suggested fix
self.assertIn(f(), b)
class fixit.rules.UseAssertIsNotNone

Discourages use of assertTrue(x is not None) and assertFalse(x is not None) as it is deprecated (https://docs.python.org/3.8/library/unittest.html#deprecated-aliases). Use assertIsNotNone(x) and assertIsNone(x)) instead.

MESSAGE

“assertTrue” and “assertFalse” are deprecated. Use “assertIsNotNone” and “assertIsNone” instead. See https://docs.python.org/3.8/library/unittest.html#deprecated-aliases

AUTOFIX: Yes
VALID
self.assertIsNotNone(x)
self.assertIsNone(x)
INVALID
self.assertTrue(a is not None)

# suggested fix
self.assertIsNotNone(a)
self.assertTrue(not x is None)

# suggested fix
self.assertIsNotNone(x)
class fixit.rules.UseAsyncSleepInAsyncDef

Detect if asyncio.sleep is used in an async function

MESSAGE

Use asyncio.sleep in async function

VALID
import time
def func():
    time.sleep(1)
from time import sleep
def func():
    sleep(1)
INVALID
import time
async def func():
    time.sleep(1)
from time import sleep
async def func():
    sleep(1)
class fixit.rules.UseClsInClassmethod

Enforces using cls as the first argument in a @classmethod.

MESSAGE

When using @classmethod, the first argument must be cls.

AUTOFIX: Yes
VALID
class foo:
    # classmethod with cls first arg.
    @classmethod
    def cm(cls, a, b, c):
        pass
class foo:
    # non-classmethod with non-cls first arg.
    def nm(self, a, b, c):
        pass
INVALID
class foo:
    # No args at all.
    @classmethod
    def cm():
        pass

# suggested fix
class foo:
    # No args at all.
    @classmethod
    def cm(cls):
        pass
class foo:
    # Single arg + reference.
    @classmethod
    def cm(a):
        return a

# suggested fix
class foo:
    # Single arg + reference.
    @classmethod
    def cm(cls):
        return cls
class fixit.rules.UseFstring

Encourages the use of f-string instead of %-formatting or .format() for high code quality and efficiency.

Following two cases not covered:

  1. arguments length greater than 30 characters: for better readibility reason

    For example:

    1: this is the answer: %d” % (a_long_function_call() + b_another_long_function_call()) 2: f”this is the answer: {a_long_function_call() + b_another_long_function_call()}” 3: result = a_long_function_call() + b_another_long_function_call() f”this is the answer: {result}”

    Line 1 is more readable than line 2. Ideally, we’d like developers to manually fix this case to line 3

  2. only %s placeholders are linted against for now. We leave it as future work to support other placeholders.

    For example, %d raises TypeError for non-numeric objects, whereas f”{x:d}” raises ValueError. This discrepancy in the type of exception raised could potentially break the logic in the code where the exception is handled

MESSAGE

Do not use printf style formatting or .format(). Use f-string instead to be more readable and efficient. See https://www.python.org/dev/peps/pep-0498/

AUTOFIX: Yes
VALID
somebody='you'; f"Hey, {somebody}."
"hey"
INVALID
"Hey, {somebody}.".format(somebody="you")
"%s" % "hi"

# suggested fix
f"{'hi'}"
class fixit.rules.UseTypesFromTyping

Enforces the use of types from the typing module in type annotations in place of builtins.{builtin_type} since the type system doesn’t recognize the latter as a valid type before Python 3.10.

AUTOFIX: Yes
PYTHON_VERSION: '< 3.10'
VALID
def fuction(list: List[str]) -> None:
    pass
def function() -> None:
    thing: Dict[str, str] = {}
INVALID
from typing import List
def whatever(list: list[str]) -> None:
    pass

# suggested fix
from typing import List
def whatever(list: List[str]) -> None:
    pass
def function(list: list[str]) -> None:
    pass

fixit.rules.extra

These are highly opinionated rules, and are not enabled by default. They can be enabled in your project’s enable configuration option.

class fixit.rules.extra.ExplicitFrozenDataclass

Encourages the use of frozen dataclass objects by telling users to specify the kwarg.

Without this lint rule, most users of dataclass won’t know to use the kwarg, and may unintentionally end up with mutable objects.

MESSAGE

When using dataclasses, explicitly specify a frozen keyword argument. Example: @dataclass(frozen=True) or @dataclass(frozen=False). Docs: https://docs.python.org/3/library/dataclasses.html

AUTOFIX: Yes
VALID
@some_other_decorator
class Cls: pass
from dataclasses import dataclass
@dataclass(frozen=False)
class Cls: pass
INVALID
from dataclasses import dataclass
@some_unrelated_decorator
@dataclass  # not called as a function
@another_unrelated_decorator
class Cls: pass

# suggested fix
from dataclasses import dataclass
@some_unrelated_decorator
@dataclass(frozen=True)  # not called as a function
@another_unrelated_decorator
class Cls: pass
from dataclasses import dataclass
@dataclass()  # called as a function, no kwargs
class Cls: pass

# suggested fix
from dataclasses import dataclass
@dataclass(frozen=True)  # called as a function, no kwargs
class Cls: pass
class fixit.rules.extra.UseLintFixmeComment

To silence a lint warning, use lint-fixme (when plans to fix the issue later) or lint-ignore (when the lint warning is not valid) comments. The comment requires to be in a standalone comment line and follows the format lint-fixme: RULE_NAMES EXTRA_COMMENTS. It suppresses the lint warning with the RULE_NAMES in the next line. RULE_NAMES can be one or more lint rule names separated by comma. noqa is deprecated and not supported because explicitly providing lint rule names to be suppressed in lint-fixme comment is preferred over implicit noqa comments. Implicit noqa suppression comments sometimes accidentally silence warnings unexpectedly.

MESSAGE

noqa is deprecated. Use lint-fixme or lint-ignore instead.

VALID
# lint-fixme: UseFstringRule
"%s" % "hi"
# lint-ignore: UsePlusForStringConcatRule
'ab' 'cd'
INVALID
fn() # noqa
(
 1,
 2,  # noqa
)

fixit.upgrade

These rules can be used to automatically upgrade lint rules to the latest version of Fixit, including deprecated names or aliases when possible.

These can be more easily applied by running fixit upgrade <path>. See Commands for details.

class fixit.upgrade.FixitDeprecatedImport

Upgrade lint rules to replace deprecated imports with their replacements.

MESSAGE

Fixit deprecated import {old_name}, use {new_name} instead

AUTOFIX: Yes
VALID
from fixit import LintRule
from fixit import Invalid
INVALID
from fixit import CstLintRule

# suggested fix
from fixit import LintRule
from fixit import CSTLintRule

# suggested fix
from fixit import LintRule
class fixit.upgrade.FixitDeprecatedTestCaseKeywords

Modify lint rule test cases from Fixit 1 to remove deprecated keyword arguments and convert the line and column values into a CodeRange.

MESSAGE

Fix deprecated Valid/Invalid keyword arguments

AUTOFIX: Yes
VALID
from fixit import InvalidTestCase

InvalidTestCase(
    "print('hello')",
    message="oops",
)
INVALID
from fixit import InvalidTestCase
InvalidTestCase(
    "print('hello')",
    line=3,
    column=10,
    config=None,
    filename="hello.py",
    kind="X123",
)

# suggested fix
from fixit import InvalidTestCase
InvalidTestCase(
    "print('hello')",
    range = CodeRange(start=CodePosition(3, 10), end=CodePosition(1 + 3, 0)))
class fixit.upgrade.FixitRemoveRuleSuffix

Remove the “Rule” suffix from lint rule class names

MESSAGE

Do not end lint rule subclasses with ‘Rule’

VALID
import fixit
class DontTryThisAtHome(fixit.LintRule): ...
from fixit import LintRule
class CatsRuleDogsDrool(LintRule): ...
INVALID
import fixit
class DontTryThisAtHomeRule(fixit.LintRule): ...
from fixit import LintRule
class CatsRuleDogsDroolRule(LintRule): ...