Run: brew install --build-from-source --verbose --debug ruff

File: /opt/homebrew/Library/Taps/homebrew/homebrew-core/Formula/ruff.rb

Docs: https://docs.brew.sh/Formula-Cookbook

Test: brew test ruff
1 file changed
tree: 129c326595abceb1b3bc1411202e780c48dc7cb6
  1. .cargo/
  2. .github/
  3. benches/
  4. flake8_to_ruff/
  5. resources/
  6. ruff/
  7. ruff_dev/
  8. scripts/
  9. src/
  10. tests/
  11. .editorconfig
  12. .gitignore
  13. .pre-commit-config.yaml
  14. Cargo.lock
  15. Cargo.toml
  16. CODE_OF_CONDUCT.md
  17. CONTRIBUTING.md
  18. LICENSE
  19. pyproject.toml
  20. README.md
  21. ruff.rb
  22. rust-toolchain
  23. rustfmt.toml
  24. setup.py
README.md

Ruff

image image image Actions status

An extremely fast Python linter, written in Rust.

  • ⚡️ 10-100x faster than existing linters
  • 🐍 Installable via pip
  • 🤝 Python 3.10 compatibility
  • 🛠️ pyproject.toml support
  • 📦 Built-in caching, to avoid re-analyzing unchanged files
  • 🔧 --fix support, for automatic error correction (e.g., automatically remove unused imports)
  • 👀 --watch support, for continuous file monitoring
  • ⚖️ Near-parity with the built-in Flake8 rule set
  • 🔌 Native re-implementations of popular Flake8 plugins, like flake8-docstrings (pydocstyle)

Ruff aims to be orders of magnitude faster than alternative tools while integrating more functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety of plugins), isort, pydocstyle, yesqa, and even a subset of pyupgrade and autoflake all while executing tens or hundreds of times faster than any individual tool.

Ruff is actively developed and used in major open-source projects like:

Read the launch blog post.

Table of Contents

  1. Installation and Usage
  2. Configuration
  3. Supported Rules
    1. Pyflakes (F)
    2. pycodestyle (E)
    3. isort (I)
    4. pydocstyle (D)
    5. pyupgrade (U)
    6. pep8-naming (N)
    7. flake8-bandit (S)
    8. flake8-comprehensions (C)
    9. flake8-bugbear (B)
    10. flake8-builtins (A)
    11. flake8-tidy-imports (I25)
    12. flake8-print (T)
    13. flake8-quotes (Q)
    14. flake8-annotations (ANN)
    15. flake8-2020 (YTT)
    16. flake8-blind-except (BLE)
    17. flake8-boolean-trap (FBT)
    18. mccabe (C90)
    19. Ruff-specific rules (RUF)
    20. Meta rules (M)
  4. Editor Integrations
  5. FAQ
  6. Development
  7. Releases
  8. Benchmarks
  9. License
  10. Contributing

Installation and Usage

Installation

Available as ruff on PyPI:

pip install ruff

Usage

To run Ruff, try any of the following:

ruff path/to/code/to/check.py
ruff path/to/code/
ruff path/to/code/*.py

You can run Ruff in --watch mode to automatically re-run on-change:

ruff path/to/code/ --watch

Ruff also works with pre-commit:

repos:
  - repo: https://github.com/charliermarsh/ruff-pre-commit
    rev: v0.0.137
    hooks:
      - id: ruff

Note: prior to v0.0.86, ruff-pre-commit used lint (rather than ruff) as the hook ID.

Configuration

Ruff is configurable both via pyproject.toml and the command line. If left unspecified, the default configuration is equivalent to:

[tool.ruff]
line-length = 88

# Enable Pyflakes `E` and `F` codes by default.
select = ["E", "F"]
ignore = []

# Exclude a variety of commonly ignored directories.
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".hg",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "venv",
]
per-file-ignores = {}

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

# Assume Python 3.10.
target-version = "py310"

[tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10

As an example, the following would configure Ruff to: (1) avoid checking for line-length violations (E501); (2), always autofix, but never remove unused imports (F401); and (3) ignore import-at-top-of-file errors (E402) in __init__.py files:

[tool.ruff]
# Enable Pyflakes and pycodestyle rules.
select = ["E", "F"]

# Never enforce `E501` (line length violations).
ignore = ["E501"]

# Always autofix, but never try to fix `F401` (unused imports).
fix = true
unfixable = ["F401"]

# Ignore `E402` (import violations in any `__init__.py` file, and in `path/to/file.py`.
per-file-ignores = {"__init__.py" = ["E402"], "path/to/file.py" = ["E402"]}

Plugin configurations should be expressed as subsections, e.g.:

[tool.ruff]
# Add "Q" to the list of enabled codes.
select = ["E", "F", "Q"]

[tool.ruff.flake8-quotes]
docstring-quotes = "double"

Alternatively, common configuration settings can be provided via the command-line:

ruff path/to/code/ --select F401 --select F403

See ruff --help for more:

Ruff: An extremely fast Python linter.

Usage: ruff [OPTIONS] <FILES>...

Arguments:
  <FILES>...

Options:
      --config <CONFIG>
          Path to the `pyproject.toml` file to use for configuration
  -v, --verbose
          Enable verbose logging
  -q, --quiet
          Only log errors
  -s, --silent
          Disable all logging (but still exit with status code "1" upon detecting errors)
  -e, --exit-zero
          Exit with status code "0", even upon detecting errors
  -w, --watch
          Run in watch mode by re-running whenever files change
      --fix
          Attempt to automatically fix lint errors
  -n, --no-cache
          Disable cache reads
      --select <SELECT>
          List of error codes to enable
      --extend-select <EXTEND_SELECT>
          Like --select, but adds additional error codes on top of the selected ones
      --ignore <IGNORE>
          List of error codes to ignore
      --extend-ignore <EXTEND_IGNORE>
          Like --ignore, but adds additional error codes on top of the ignored ones
      --exclude <EXCLUDE>
          List of paths, used to exclude files and/or directories from checks
      --extend-exclude <EXTEND_EXCLUDE>
          Like --exclude, but adds additional files and directories on top of the excluded ones
      --fixable <FIXABLE>
          List of error codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
      --unfixable <UNFIXABLE>
          List of error codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
      --per-file-ignores <PER_FILE_IGNORES>
          List of mappings from file pattern to code to exclude
      --format <FORMAT>
          Output serialization format for error messages [default: text] [possible values: text, json]
      --show-source
          Show violations with source code
      --show-files
          See the files Ruff will be run against with the current settings
      --show-settings
          See Ruff's settings
      --add-noqa
          Enable automatic additions of noqa directives to failing lines
      --dummy-variable-rgx <DUMMY_VARIABLE_RGX>
          Regular expression matching the name of dummy variables
      --target-version <TARGET_VERSION>
          The minimum Python version that should be supported
      --line-length <LINE_LENGTH>
          Set the line-length for length-associated checks and automatic formatting
      --max-complexity <MAX_COMPLEXITY>
          Max McCabe complexity allowed for a function
      --stdin-filename <STDIN_FILENAME>
          The name of the file when passing it through stdin
  -h, --help
          Print help information
  -V, --version
          Print version information

Excluding files

Exclusions are based on globs, and can be either:

  • Single-path patterns, like .mypy_cache (to exclude any directory named .mypy_cache in the tree), foo.py (to exclude any file named foo.py), or foo_*.py (to exclude any file matching foo_*.py ).
  • Relative patterns, like directory/foo.py (to exclude that specific file) or directory/*.py (to exclude any Python files in directory). Note that these paths are relative to the project root (e.g., the directory containing your pyproject.toml).

Ignoring errors

To omit a lint check entirely, add it to the “ignore” list via --ignore or --extend-ignore, either on the command-line or in your project.toml file.

To ignore an error in-line, Ruff uses a noqa system similar to Flake8. To ignore an individual error, add # noqa: {code} to the end of the line, like so:

# Ignore F841.
x = 1  # noqa: F841

# Ignore E741 and F841.
i = 1  # noqa: E741, F841

# Ignore _all_ errors.
x = 1  # noqa

Note that, for multi-line strings, the noqa directive should come at the end of the string, and will apply to the entire body, like so:

"""Lorem ipsum dolor sit amet.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
"""  # noqa: E501

Ruff supports several workflows to aid in noqa management.

First, Ruff provides a special error code, M001, to enforce that your noqa directives are “valid”, in that the errors they say they ignore are actually being triggered on that line (and thus suppressed). You can run ruff /path/to/file.py --extend-select M001 to flag unused noqa directives.

Second, Ruff can automatically remove unused noqa directives via its autofix functionality. You can run ruff /path/to/file.py --extend-select M001 --fix to automatically remove unused noqa directives.

Third, Ruff can automatically add noqa directives to all failing lines. This is useful when migrating a new codebase to Ruff. You can run ruff /path/to/file.py --add-noqa to automatically add noqa directives to all failing lines, with the appropriate error codes.

Supported Rules

Regardless of the rule's origin, Ruff re-implements every rule in Rust as a first-party feature.

By default, Ruff enables all E and F error codes, which correspond to those built-in to Flake8.

The 🛠 emoji indicates that a rule is automatically fixable by the --fix command-line option.

Pyflakes

For more, see Pyflakes on PyPI.

CodeNameMessageFix
F401UnusedImport... imported but unused🛠
F402ImportShadowedByLoopVarImport ... from line 1 shadowed by loop variable
F403ImportStarUsedfrom ... import * used; unable to detect undefined names
F404LateFutureImportfrom __future__ imports must occur at the beginning of the file
F405ImportStarUsage... may be undefined, or defined from star imports: ...
F406ImportStarNotPermittedfrom ... import * only allowed at module level
F407FutureFeatureNotDefinedFuture feature ... is not defined
F541FStringMissingPlaceholdersf-string without any placeholders
F601MultiValueRepeatedKeyLiteralDictionary key literal repeated
F602MultiValueRepeatedKeyVariableDictionary key ... repeated
F621ExpressionsInStarAssignmentToo many expressions in star-unpacking assignment
F622TwoStarredExpressionsTwo starred expressions in assignment
F631AssertTupleAssert test is a non-empty tuple, which is always True
F632IsLiteralUse == and != to compare constant literals🛠
F633InvalidPrintSyntaxUse of >> is invalid with print function
F634IfTupleIf test is a tuple, which is always True
F701BreakOutsideLoopbreak outside loop
F702ContinueOutsideLoopcontinue not properly in loop
F704YieldOutsideFunctionyield or yield from statement outside of a function
F706ReturnOutsideFunctionreturn statement outside of a function/method
F707DefaultExceptNotLastAn except block as not the last exception handler
F722ForwardAnnotationSyntaxErrorSyntax error in forward annotation: ...
F821UndefinedNameUndefined name ...
F822UndefinedExportUndefined name ... in __all__
F823UndefinedLocalLocal variable ... referenced before assignment
F831DuplicateArgumentNameDuplicate argument name in function definition
F841UnusedVariableLocal variable ... is assigned to but never used
F901RaiseNotImplementedraise NotImplemented should be raise NotImplementedError🛠

pycodestyle

For more, see pycodestyle on PyPI.

CodeNameMessageFix
E402ModuleImportNotAtTopOfFileModule level import not at top of file
E501LineTooLongLine too long (89 > 88 characters)
E711NoneComparisonComparison to None should be cond is None🛠
E712TrueFalseComparisonComparison to True should be cond is True🛠
E713NotInTestTest for membership should be not in🛠
E714NotIsTestTest for object identity should be is not🛠
E721TypeComparisonDo not compare types, use isinstance()
E722DoNotUseBareExceptDo not use bare except
E731DoNotAssignLambdaDo not assign a lambda expression, use a def🛠
E741AmbiguousVariableNameAmbiguous variable name: ...
E742AmbiguousClassNameAmbiguous class name: ...
E743AmbiguousFunctionNameAmbiguous function name: ...
E902IOErrorIOError: ...
E999SyntaxErrorSyntaxError: ...
W292NoNewLineAtEndOfFileNo newline at end of file
W605InvalidEscapeSequenceInvalid escape sequence: ‘\c’

isort

For more, see isort on PyPI.

CodeNameMessageFix
I001UnsortedImportsImport block is un-sorted or un-formatted🛠

pydocstyle

For more, see pydocstyle on PyPI.

CodeNameMessageFix
D100PublicModuleMissing docstring in public module
D101PublicClassMissing docstring in public class
D102PublicMethodMissing docstring in public method
D103PublicFunctionMissing docstring in public function
D104PublicPackageMissing docstring in public package
D105MagicMethodMissing docstring in magic method
D106PublicNestedClassMissing docstring in public nested class
D107PublicInitMissing docstring in __init__
D200FitsOnOneLineOne-line docstring should fit on one line
D201NoBlankLineBeforeFunctionNo blank lines allowed before function docstring (found 1)🛠
D202NoBlankLineAfterFunctionNo blank lines allowed after function docstring (found 1)🛠
D203OneBlankLineBeforeClass1 blank line required before class docstring🛠
D204OneBlankLineAfterClass1 blank line required after class docstring🛠
D205BlankLineAfterSummary1 blank line required between summary line and description🛠
D206IndentWithSpacesDocstring should be indented with spaces, not tabs
D207NoUnderIndentationDocstring is under-indented🛠
D208NoOverIndentationDocstring is over-indented🛠
D209NewLineAfterLastParagraphMulti-line docstring closing quotes should be on a separate line🛠
D210NoSurroundingWhitespaceNo whitespaces allowed surrounding docstring text🛠
D211NoBlankLineBeforeClassNo blank lines allowed before class docstring🛠
D212MultiLineSummaryFirstLineMulti-line docstring summary should start at the first line
D213MultiLineSummarySecondLineMulti-line docstring summary should start at the second line
D214SectionNotOverIndentedSection is over-indented (“Returns”)🛠
D215SectionUnderlineNotOverIndentedSection underline is over-indented (“Returns”)🛠
D300UsesTripleQuotesUse “““triple double quotes”””
D400EndsInPeriodFirst line should end with a period
D402NoSignatureFirst line should not be the function's signature
D403FirstLineCapitalizedFirst word of the first line should be properly capitalized
D404NoThisPrefixFirst word of the docstring should not be “This”
D405CapitalizeSectionNameSection name should be properly capitalized (“returns”)🛠
D406NewLineAfterSectionNameSection name should end with a newline (“Returns”)🛠
D407DashedUnderlineAfterSectionMissing dashed underline after section (“Returns”)🛠
D408SectionUnderlineAfterNameSection underline should be in the line following the section's name (“Returns”)🛠
D409SectionUnderlineMatchesSectionLengthSection underline should match the length of its name (“Returns”)🛠
D410BlankLineAfterSectionMissing blank line after section (“Returns”)🛠
D411BlankLineBeforeSectionMissing blank line before section (“Returns”)🛠
D412NoBlankLinesBetweenHeaderAndContentNo blank lines allowed between a section header and its content (“Returns”)🛠
D413BlankLineAfterLastSectionMissing blank line after last section (“Returns”)🛠
D414NonEmptySectionSection has no content (“Returns”)
D415EndsInPunctuationFirst line should end with a period, question mark, or exclamation point
D416SectionNameEndsInColonSection name should end with a colon (“Returns”)🛠
D417DocumentAllArgumentsMissing argument descriptions in the docstring: x, y
D418SkipDocstringFunction decorated with @overload shouldn't contain a docstring
D419NonEmptyDocstring is empty

pyupgrade

For more, see pyupgrade on PyPI.

CodeNameMessageFix
U001UselessMetaclassType__metaclass__ = type is implied🛠
U003TypeOfPrimitiveUse str instead of type(...)🛠
U004UselessObjectInheritanceClass ... inherits from object🛠
U005DeprecatedUnittestAliasassertEquals is deprecated, use assertEqual instead🛠
U006UsePEP585AnnotationUse list instead of List for type annotations🛠
U007UsePEP604AnnotationUse X | Y for type annotations🛠
U008SuperCallWithParametersUse super() instead of super(__class__, self)🛠
U009PEP3120UnnecessaryCodingCommentUTF-8 encoding declaration is unnecessary🛠
U010UnnecessaryFutureImportUnnecessary __future__ import ... for target Python version🛠
U011UnnecessaryLRUCacheParamsUnnecessary parameters to functools.lru_cache🛠
U012UnnecessaryEncodeUTF8Unnecessary call to encode as UTF-8🛠
U013ConvertTypedDictFunctionalToClassConvert ... from TypedDict functional to class syntax🛠
U014ConvertNamedTupleFunctionalToClassConvert ... from NamedTuple functional to class syntax🛠
U015RedundantOpenModesUnnecessary open mode parameters🛠

pep8-naming

For more, see pep8-naming on PyPI.

CodeNameMessageFix
N801InvalidClassNameClass name ... should use CapWords convention
N802InvalidFunctionNameFunction name ... should be lowercase
N803InvalidArgumentNameArgument name ... should be lowercase
N804InvalidFirstArgumentNameForClassMethodFirst argument of a class method should be named cls
N805InvalidFirstArgumentNameForMethodFirst argument of a method should be named self
N806NonLowercaseVariableInFunctionVariable ... in function should be lowercase
N807DunderFunctionNameFunction name should not start and end with __
N811ConstantImportedAsNonConstantConstant ... imported as non-constant ...
N812LowercaseImportedAsNonLowercaseLowercase ... imported as non-lowercase ...
N813CamelcaseImportedAsLowercaseCamelcase ... imported as lowercase ...
N814CamelcaseImportedAsConstantCamelcase ... imported as constant ...
N815MixedCaseVariableInClassScopeVariable mixedCase in class scope should not be mixedCase
N816MixedCaseVariableInGlobalScopeVariable mixedCase in global scope should not be mixedCase
N817CamelcaseImportedAsAcronymCamelcase ... imported as acronym ...
N818ErrorSuffixOnExceptionNameException name ... should be named with an Error suffix

flake8-bandit

For more, see flake8-bandit on PyPI.

CodeNameMessageFix
S101AssertUsedUse of assert detected
S102ExecUsedUse of exec detected
S104HardcodedBindAllInterfacesPossible binding to all interfaces
S105HardcodedPasswordStringPossible hardcoded password: "..."
S106HardcodedPasswordFuncArgPossible hardcoded password: "..."
S107HardcodedPasswordDefaultPossible hardcoded password: "..."

flake8-comprehensions

For more, see flake8-comprehensions on PyPI.

CodeNameMessageFix
C400UnnecessaryGeneratorListUnnecessary generator (rewrite as a list comprehension)🛠
C401UnnecessaryGeneratorSetUnnecessary generator (rewrite as a set comprehension)🛠
C402UnnecessaryGeneratorDictUnnecessary generator (rewrite as a dict comprehension)🛠
C403UnnecessaryListComprehensionSetUnnecessary list comprehension (rewrite as a set comprehension)🛠
C404UnnecessaryListComprehensionDictUnnecessary list comprehension (rewrite as a dict comprehension)🛠
C405UnnecessaryLiteralSetUnnecessary (list|tuple) literal (rewrite as a set literal)🛠
C406UnnecessaryLiteralDictUnnecessary (list|tuple) literal (rewrite as a dict literal)🛠
C408UnnecessaryCollectionCallUnnecessary (dict|list|tuple) call (rewrite as a literal)🛠
C409UnnecessaryLiteralWithinTupleCallUnnecessary (list|tuple) literal passed to tuple() (remove the outer call to tuple())🛠
C410UnnecessaryLiteralWithinListCallUnnecessary (list|tuple) literal passed to list() (rewrite as a list literal)🛠
C411UnnecessaryListCallUnnecessary list call (remove the outer call to list())🛠
C413UnnecessaryCallAroundSortedUnnecessary (list|reversed) call around sorted()
C414UnnecessaryDoubleCastOrProcessUnnecessary (list|reversed|set|sorted|tuple) call within (list|set|sorted|tuple)()
C415UnnecessarySubscriptReversalUnnecessary subscript reversal of iterable within (reversed|set|sorted)()
C416UnnecessaryComprehensionUnnecessary (list|set) comprehension (rewrite using (list|set)())🛠
C417UnnecessaryMapUnnecessary map usage (rewrite using a (list|set|dict) comprehension)

flake8-boolean-trap

For more, see flake8-boolean-trap on PyPI.

CodeNameMessageFix
FBT001BooleanPositionalArgInFunctionDefinitionBoolean positional arg in function definition
FBT002BooleanDefaultValueInFunctionDefinitionBoolean default value in function definition
FBT003BooleanPositionalValueInFunctionCallBoolean positional value in function call

flake8-bugbear

For more, see flake8-bugbear on PyPI.

CodeNameMessageFix
B002UnaryPrefixIncrementPython does not support the unary prefix increment
B003AssignmentToOsEnvironAssigning to os.environ doesn't clear the environment
B004UnreliableCallableCheckUsing hasattr(x, '__call__') to test if x is callable is unreliable. Use callable(x) for consistent results.
B005StripWithMultiCharactersUsing .strip() with multi-character strings is misleading the reader
B006MutableArgumentDefaultDo not use mutable data structures for argument defaults
B007UnusedLoopControlVariableLoop control variable i not used within the loop body🛠
B008FunctionCallArgumentDefaultDo not perform function call in argument defaults
B009GetAttrWithConstantDo not call getattr with a constant attribute value. It is not any safer than normal property access.🛠
B010SetAttrWithConstantDo not call setattr with a constant attribute value. It is not any safer than normal property access.🛠
B011DoNotAssertFalseDo not assert False (python -O removes these calls), raise AssertionError()🛠
B012JumpStatementInFinallyreturn/continue/break inside finally blocks cause exceptions to be silenced
B013RedundantTupleInExceptionHandlerA length-one tuple literal is redundant. Write except ValueError instead of except (ValueError,).🛠
B014DuplicateHandlerExceptionException handler with duplicate exception: ValueError🛠
B015UselessComparisonPointless comparison. This comparison does nothing but waste CPU instructions. Either prepend assert or remove it.
B016CannotRaiseLiteralCannot raise a literal. Did you intend to return it or raise an Exception?
B017NoAssertRaisesExceptionassertRaises(Exception) should be considered evil
B018UselessExpressionFound useless expression. Either assign it to a variable or remove it.
B019CachedInstanceMethodUse of functools.lru_cache or functools.cache on methods can lead to memory leaks
B020LoopVariableOverridesIteratorLoop control variable ... overrides iterable it iterates
B021FStringDocstringf-string used as docstring. This will be interpreted by python as a joined string rather than a docstring.
B022UselessContextlibSuppressNo arguments passed to contextlib.suppress. No exceptions will be suppressed and therefore this context manager is redundant
B024AbstractBaseClassWithoutAbstractMethod... is an abstract base class, but it has no abstract methods
B025DuplicateTryBlockExceptiontry-except block with duplicate exception Exception
B026StarArgUnpackingAfterKeywordArgStar-arg unpacking after a keyword argument is strongly discouraged
B027EmptyMethodWithoutAbstractDecorator... is an empty method in an abstract base class, but has no abstract decorator

flake8-builtins

For more, see flake8-builtins on PyPI.

CodeNameMessageFix
A001BuiltinVariableShadowingVariable ... is shadowing a python builtin
A002BuiltinArgumentShadowingArgument ... is shadowing a python builtin
A003BuiltinAttributeShadowingClass attribute ... is shadowing a python builtin

flake8-tidy-imports

For more, see flake8-tidy-imports on PyPI.

CodeNameMessageFix
I252BannedRelativeImportRelative imports are banned

flake8-print

For more, see flake8-print on PyPI.

CodeNameMessageFix
T201PrintFoundprint found🛠
T203PPrintFoundpprint found🛠

flake8-quotes

For more, see flake8-quotes on PyPI.

CodeNameMessageFix
Q000BadQuotesInlineStringSingle quotes found but double quotes preferred
Q001BadQuotesMultilineStringSingle quote multiline found but double quotes preferred
Q002BadQuotesDocstringSingle quote docstring found but double quotes preferred
Q003AvoidQuoteEscapeChange outer quotes to avoid escaping inner quotes

flake8-annotations

For more, see flake8-annotations on PyPI.

CodeNameMessageFix
ANN001MissingTypeFunctionArgumentMissing type annotation for function argument ...
ANN002MissingTypeArgsMissing type annotation for *...
ANN003MissingTypeKwargsMissing type annotation for **...
ANN101MissingTypeSelfMissing type annotation for ... in method
ANN102MissingTypeClsMissing type annotation for ... in classmethod
ANN201MissingReturnTypePublicFunctionMissing return type annotation for public function ...
ANN202MissingReturnTypePrivateFunctionMissing return type annotation for private function ...
ANN204MissingReturnTypeMagicMethodMissing return type annotation for magic method ...
ANN205MissingReturnTypeStaticMethodMissing return type annotation for staticmethod ...
ANN206MissingReturnTypeClassMethodMissing return type annotation for classmethod ...
ANN401DynamicallyTypedExpressionDynamically typed expressions (typing.Any) are disallowed in ...

flake8-2020

For more, see flake8-2020 on PyPI.

CodeNameMessageFix
YTT101SysVersionSlice3Referencedsys.version[:3] referenced (python3.10), use sys.version_info
YTT102SysVersion2Referencedsys.version[2] referenced (python3.10), use sys.version_info
YTT103SysVersionCmpStr3sys.version compared to string (python3.10), use sys.version_info
YTT201SysVersionInfo0Eq3Referencedsys.version_info[0] == 3 referenced (python4), use >=
YTT202SixPY3Referencedsix.PY3 referenced (python4), use not six.PY2
YTT203SysVersionInfo1CmpIntsys.version_info[1] compared to integer (python4), compare sys.version_info to tuple
YTT204SysVersionInfoMinorCmpIntsys.version_info.minor compared to integer (python4), compare sys.version_info to tuple
YTT301SysVersion0Referencedsys.version[0] referenced (python10), use sys.version_info
YTT302SysVersionCmpStr10sys.version compared to string (python10), use sys.version_info
YTT303SysVersionSlice1Referencedsys.version[:1] referenced (python10), use sys.version_info

flake8-blind-except

For more, see flake8-blind-except on PyPI.

CodeNameMessageFix
BLE001BlindExceptBlind except Exception: statement

mccabe

For more, see mccabe on PyPI.

CodeNameMessageFix
C901FunctionIsTooComplex... is too complex (10)

Ruff-specific rules

CodeNameMessageFix
RUF001AmbiguousUnicodeCharacterStringString contains ambiguous unicode character ‘𝐁’ (did you mean ‘B’?)🛠
RUF002AmbiguousUnicodeCharacterDocstringDocstring contains ambiguous unicode character ‘𝐁’ (did you mean ‘B’?)🛠
RUF003AmbiguousUnicodeCharacterCommentComment contains ambiguous unicode character ‘𝐁’ (did you mean ‘B’?)
RUF101ConvertExitToSysExitexit() is only available in the interpreter, use sys.exit() instead🛠

Meta rules

CodeNameMessageFix
M001UnusedNOQAUnused noqa directive🛠

Editor Integrations

VS Code (Official)

Download the Ruff VS Code extension.

PyCharm

Ruff can be installed as an External Tool in PyCharm. Open the Preferences pane, then navigate to “Tools”, then “External Tools”. From there, add a new tool with the following configuration:

Install Ruff as an External Tool

Ruff should then appear as a runnable action:

Ruff as a runnable action

Vim & Neovim (Unofficial)

Ruff is available as part of the coc-pyright extension for coc.nvim.

tools:
  python-ruff: &python-ruff
    lint-command: 'ruff --config ~/myconfigs/linters/ruff.toml --quiet ${INPUT}'
    lint-stdin: true
    lint-formats:
      - '%f:%l:%c: %m'
    format-command: 'ruff --stdin-filename ${INPUT} --config ~/myconfigs/linters/ruff.toml --fix --exit-zero --quiet -'
    format-stdin: true
local null_ls = require("null-ls")
local methods = require("null-ls.methods")
local helpers = require("null-ls.helpers")

local function ruff_fix()
    return helpers.make_builtin({
        name = "ruff",
        meta = {
            url = "https://github.com/charliermarsh/ruff/",
            description = "An extremely fast Python linter, written in Rust.",
        },
        method = methods.internal.FORMATTING,
        filetypes = { "python" },
        generator_opts = {
            command = "ruff",
            args = { "--fix", "-e", "-n", "--stdin-filename", "$FILENAME", "-" },
            to_stdin = true
        },
        factory = helpers.formatter_factory
    })
end

null_ls.setup({
    sources = {
        ruff_fix(),
        null_ls.builtins.diagnostics.ruff,
    }
})

Language Server Protocol (Unofficial)

ruffd is a Rust-based language server for Ruff that implements the Language Server Protocol (LSP).

GitHub Actions

GitHub Actions has everything you need to run Ruff out-of-the-box:

name: CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.10"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff
      - name: Run Ruff
        run: ruff .

FAQ

Is Ruff compatible with Black?

Yes. Ruff is compatible with Black out-of-the-box, as long as the line-length setting is consistent between the two.

As a project, Ruff is designed to be used alongside Black and, as such, will defer implementing stylistic lint rules that are obviated by autoformatting.

How does Ruff compare to Flake8?

(Coming from Flake8? Try flake8-to-ruff to automatically convert your existing configuration.)

Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small number of plugins, (2) alongside Black, and (3) on Python 3 code.

Under those conditions Ruff is missing 14 rules related to string .format calls, 1 rule related to docstring parsing, and 1 rule related to redefined variables.

Ruff re-implements some of the most popular Flake8 plugins and related code quality tools natively, including:

Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:

  1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural pattern matching and parenthesized context managers.
  2. Flake8 has a plugin architecture and supports writing custom lint rules. (To date, popular Flake8 plugins have been re-implemented within Ruff directly.)

Which tools does Ruff replace?

Today, Ruff can be used to replace Flake8 when used with any of the following plugins:

Ruff can also replace isort, yesqa, and a subset of the rules implemented in pyupgrade (16/33).

If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.

Do I need to install Rust to use Ruff?

Nope! Ruff is available as ruff on PyPI:

pip install ruff

Ruff ships with wheels for all major platforms, which enables pip to install Ruff without relying on Rust at all.

Can I write my own plugins for Ruff?

Ruff does not yet support third-party plugins, though a plugin system is within-scope for the project. See #283 for more.

How does Ruff's import sorting compare to isort?

Ruff's import sorting is intended to be nearly equivalent to isort when used profile = "black". (There are some minor differences in how Ruff and isort break ties between similar imports.)

Like isort, Ruff's import sorting is compatible with Black.

Ruff is less configurable than isort, but supports the known-first-party, known-third-party, extra-standard-library, and src settings, like so:

[tool.ruff]
select = [
    # Pyflakes
    "F",
    # Pycodestyle
    "E",
    "W",
    # isort
    "I001"
]
src = ["src", "tests"]

[tool.ruff.isort]
known-first-party = ["my_module1", "my_module2"]

Does Ruff support NumPy- or Google-style docstrings?

Yes! To enable a specific docstring convention, start by enabling all pydocstyle error codes, and then selectively disabling based on your preferred convention.

For example, if you're coming from flake8-docstrings, the following configuration is equivalent to --docstring-convention=numpy:

[tool.ruff]
extend-select = ["D"]
extend-ignore = [
    "D107",
    "D203",
    "D212",
    "D213",
    "D402",
    "D413",
    "D415",
    "D416",
    "D417",
]

Similarly, the following is equivalent to --docstring-convention=google:

[tool.ruff]
extend-select = ["D"]
extend-ignore = [
    "D203",
    "D204",
    "D213",
    "D215",
    "D400",
    "D404",
    "D406",
    "D407",
    "D408",
    "D409",
    "D413",
]

Similarly, the following is equivalent to --docstring-convention=pep8:

[tool.ruff]
extend-select = ["D"]
extend-ignore = [
    "D203",
    "D212",
    "D213",
    "D214",
    "D215",
    "D404",
    "D405",
    "D406",
    "D407",
    "D408",
    "D409",
    "D410",
    "D411",
    "D413",
    "D415",
    "D416",
    "D417",
]

Development

Ruff is written in Rust (1.65.0). You'll need to install the Rust toolchain for development.

Assuming you have cargo installed, you can run:

cargo run resources/test/fixtures

For development, we use nightly Rust:

cargo +nightly fmt
cargo +nightly clippy
cargo +nightly test

Releases

Ruff is distributed on PyPI, and published via maturin.

See: .github/workflows/release.yaml.

Benchmarks

First, clone CPython. It's a large and diverse Python codebase, which makes it a good target for benchmarking.

git clone --branch 3.10 https://github.com/python/cpython.git resources/test/cpython

Add this pyproject.toml to the CPython directory:

[tool.ruff]
line-length = 88
extend-exclude = [
    "Lib/lib2to3/tests/data/bom.py",
    "Lib/lib2to3/tests/data/crlf.py",
    "Lib/lib2to3/tests/data/different_encoding.py",
    "Lib/lib2to3/tests/data/false_encoding.py",
    "Lib/lib2to3/tests/data/py2_test_grammar.py",
    "Lib/test/bad_coding2.py",
    "Lib/test/badsyntax_3131.py",
    "Lib/test/badsyntax_pep3120.py",
    "Lib/test/encoded_modules/module_iso_8859_1.py",
    "Lib/test/encoded_modules/module_koi8_r.py",
    "Lib/test/test_fstring.py",
    "Lib/test/test_grammar.py",
    "Lib/test/test_importlib/test_util.py",
    "Lib/test/test_named_expressions.py",
    "Lib/test/test_patma.py",
    "Lib/test/test_source_encoding.py",
    "Tools/c-analyzer/c_parser/parser/_delim.py",
    "Tools/i18n/pygettext.py",
    "Tools/test2to3/maintest.py",
    "Tools/test2to3/setup.py",
    "Tools/test2to3/test/test_foo.py",
    "Tools/test2to3/test2to3/hello.py",
]

Next, to benchmark the release build:

cargo build --release

hyperfine --ignore-failure --warmup 10 --runs 100 \
  "./target/release/ruff ./resources/test/cpython/ --no-cache" \
  "./target/release/ruff ./resources/test/cpython/"

Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
  Time (mean ± σ):     297.4 ms ±   4.9 ms    [User: 2460.0 ms, System: 67.2 ms]
  Range (min  max):   287.7 ms  312.1 ms    100 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: ./target/release/ruff ./resources/test/cpython/
  Time (mean ± σ):      79.6 ms ±   7.3 ms    [User: 59.7 ms, System: 356.1 ms]
  Range (min  max):    62.4 ms  111.2 ms    100 runs

  Warning: Ignoring non-zero exit code.

To benchmark against the ecosystem's existing tools:

hyperfine --ignore-failure --warmup 5 \
  "./target/release/ruff ./resources/test/cpython/ --no-cache" \
  "pylint --recursive=y resources/test/cpython/" \
  "pyflakes resources/test/cpython" \
  "autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython" \
  "pycodestyle resources/test/cpython" \
  "flake8 resources/test/cpython" \
  "python -m scripts.run_flake8 resources/test/cpython"

In order, these evaluate:

  • Ruff
  • Pylint
  • Pyflakes
  • autoflake
  • pycodestyle
  • Flake8
  • Flake8, with a hack to enable multiprocessing on macOS

(You can poetry install from ./scripts to create a working environment for the above.)

Benchmark 1: ./target/release/ruff ./resources/test/cpython/ --no-cache
  Time (mean ± σ):     297.9 ms ±   7.0 ms    [User: 2436.6 ms, System: 65.9 ms]
  Range (min  max):   289.9 ms  314.6 ms    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 2: pylint --recursive=y resources/test/cpython/
  Time (mean ± σ):     37.634 s ±  0.225 s    [User: 36.728 s, System: 0.853 s]
  Range (min  max):   37.201 s  38.106 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 3: pyflakes resources/test/cpython
  Time (mean ± σ):     40.950 s ±  0.449 s    [User: 40.688 s, System: 0.229 s]
  Range (min  max):   40.348 s  41.671 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 4: autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython
  Time (mean ± σ):     11.562 s ±  0.160 s    [User: 107.022 s, System: 1.143 s]
  Range (min  max):   11.417 s  11.917 s    10 runs

Benchmark 5: pycodestyle resources/test/cpython
  Time (mean ± σ):     67.428 s ±  0.985 s    [User: 67.199 s, System: 0.203 s]
  Range (min  max):   65.313 s  68.496 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 6: flake8 resources/test/cpython
  Time (mean ± σ):     116.099 s ±  1.178 s    [User: 115.217 s, System: 0.845 s]
  Range (min  max):   114.180 s  117.724 s    10 runs

  Warning: Ignoring non-zero exit code.

Benchmark 7: python -m scripts.run_flake8 resources/test/cpython
  Time (mean ± σ):     20.477 s ±  0.349 s    [User: 142.372 s, System: 1.504 s]
  Range (min  max):   20.107 s  21.183 s    10 runs

Summary
  './target/release/ruff ./resources/test/cpython/ --no-cache' ran
   38.81 ± 1.05 times faster than 'autoflake --recursive --expand-star-imports --remove-all-unused-imports --remove-unused-variables --remove-duplicate-keys resources/test/cpython'
   68.74 ± 1.99 times faster than 'python -m scripts.run_flake8 resources/test/cpython'
  126.33 ± 3.05 times faster than 'pylint --recursive=y resources/test/cpython/'
  137.46 ± 3.55 times faster than 'pyflakes resources/test/cpython'
  226.35 ± 6.23 times faster than 'pycodestyle resources/test/cpython'
  389.73 ± 9.92 times faster than 'flake8 resources/test/cpython'

License

MIT

Contributing

Contributions are welcome and hugely appreciated. To get started, check out the contributing guidelines.