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.
Fixit offers multiple integrations, including pre-commit actions and LSP support for VSCode and other editors. See the Integrations Guide for details.
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.
- --output-format / -o FORMAT_TYPE
Override how Fixit prints violations to the terminal.
See
output-format
for available formats.
- --output-template TEMPLATE
Override the python formatting template to use with
output-format = 'custom'
.
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.
lsp
¶
Start the language server providing IDE features over
LSP.
This command is only available if installed with the lsp
extras (e.g.
pip install "fixit[lsp]"
). See IDE Integrations
for more details.
$ fixit lsp [--stdio | --tcp PORT | --ws PORT]
- --stdio
Serve LSP over stdio. default
- --tcp
Serve LSP over TCP on PORT.
- --ws
Serve LSP over WebSocket on PORT.
- --debounce-interval
Delay in seconds for server-side debounce. default: 0.5
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
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 totrue
), or a single, optional path relative to the root config.For example, project
orange
using asrc/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, thenorange.rules
would be imported fromsrc/orange/rules/
, while also allowing these local rules to import from other components in theorange
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
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.
- output-format
Choose one of the presets for terminal output formatting. This option is inferred based on the current working directory or from an explicity specified config file – subpath overrides will be ignored.
Can be one of:
custom
: Specify your own format using theoutput-template
option below.fixit
: Fixit’s default output format.vscode
: A format that provides clickable paths for Visual Studio Code.
Note
The default output format is planned to change to
vscode
in the next feature release, expected as part ofv2.3
orv3.0
. If you are sensitive to output formats changing, specify your preferred format in your project configs accordingly.
- output-template
Sets the format of output printed to terminal. Python formatting is used in the background to fill in data. Only active with
output-format
set tocustom
.This option is inferred based on the current working directory or from an explicity specified config file – subpath overrides will be ignored.
Supported variables:
message
: Message emitted by the applied rule.path
: Path to affected file.result
: RawResult
object.rule_name
: Name of the applied rule.start_col
: Start column of affected code.start_line
: Start line of affected code.
[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¶
IDE¶
Fixit can be used to lint as you type as well as to format files.
To get this functionality, install the lsp
extras (e.g.
pip install "fixit[lsp]"
) then set up an LSP client to launch and connect to
the Fixit LSP server. See the lsp command for command
usage details.
Examples of client setup:
VSCode:
Generic LSP Client (via GitHub; requires configuration)
Fixit (Unofficial) (via VSCode Marketplace; compiled from Generic LSP Client with preset configuration for Fixit)
Neovim: nvim-lspconfig:
require("lspconfig.configs").fixit = {
default_config = {
cmd = { "fixit", "lsp" },
filetypes = { "python" },
root_dir = require("lspconfig").util.root_pattern(
"pyproject.toml", "setup.py", "requirements.txt", ".git",
),
single_file_support = true,
},
}
lspconfig.fixit.setup({})
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.
VSCode¶
For better integration with Visual Studio Code setting output-format
can be set to vscode
.
That way VSCode opens the editor at the right position when clicking on code locations in Fixit’s terminal output.
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 multipleisinstance
calls with a boolean-or operation, a singleisinstance
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 thanis
andis not
. The==
operator checks equality (https://docs.python.org/3/reference/datamodel.html#object.__eq__), whileis
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.DeprecatedABCImport¶
Checks for the use of the deprecated collections ABC import. Since python 3.3, the Collections Abstract Base Classes (ABC) have been moved to collections.abc. These ABCs are import errors starting in Python 3.10.
- MESSAGE¶
ABCs must be imported from collections.abc
- AUTOFIX: Yes¶
- PYTHON_VERSION: '>= 3.3'¶
- VALID¶
from collections.abc import Container
from collections.abc import Container, Hashable
- INVALID¶
from collections import Container # suggested fix from collections.abc import Container
from collections import Container, Hashable # suggested fix from collections.abc import Container, Hashable
- 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 withassertEqual
. Comparisons with True, False and None are replaced with one-argument calls toassertTrue
,assertFalse
andassertIsNone
.- 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 fromobject
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 ofNamedTuple
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]
overUnion[T, None]
andUnion[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)
andassertFalse(x in y)
as it is deprecated (https://docs.python.org/3.8/library/unittest.html#deprecated-aliases). UseassertIn(x, y)
andassertNotIn(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)
andassertFalse(x is not None)
as it is deprecated (https://docs.python.org/3.8/library/unittest.html#deprecated-aliases). UseassertIsNotNone(x)
andassertIsNone(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:
- 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
- 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 ofbuiltins.{builtin_type}
since the type system doesn’t recognize the latter as a valid type before Python3.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) orlint-ignore
(when the lint warning is not valid) comments. The comment requires to be in a standalone comment line and follows the formatlint-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): ...