mrkeyoor.com_
Thu 06 Aug 10:54 UTC
PyPICLI & Toolingupdated 06 Aug 2026

flake8

flake8 is a command that runs three other tools over your Python files and merges their output into one list of problems. pyflakes finds real errors such as unused imports, undefined names, and shadowed variables. pycodestyle checks formatting against PEP 8, which is where the E and W codes come from. mccabe measures cyclomatic complexity if you ask it to. On top of that flake8 adds the parts people actually think of as flake8: the noqa comment system for suppressing a single line, per-file ignore rules, config file discovery, parallel execution across files, and a plugin interface that hundreds of third-party checkers register against. It does not reformat anything and it does not do type checking. It reads your code, prints filename, line, column, code, and message, and exits non-zero if it found anything.

Verdict

flake8 still works, is still maintained, and if your project already runs it there is no emergency. For anything new, Ruff covers the same rules with autofix and pyproject.toml config, and the main reason left to stay on flake8 is a plugin that has no Ruff equivalent.

API stability5/5The command line, the noqa syntax, and the config keys have been stable since flake8 3; major bumps mostly drop old Python versions and bump the pinned pycodestyle and pyflakes ranges, and the last real break was the plugin interface change in version 5.
Docs4/5flake8.pycqa.org documents every option, the full error code list, config file handling, plugin development, and an FAQ that answers the pyproject.toml question directly; what is thin is guidance on choosing a rule set, and the README is mostly links.
Maintenance4/5Pushed 14 July 2026 with 23 open issues and no open pull requests, which is unusually tidy, but 7.3.0 dates from June 2025 and releases now come roughly twice a year because the project is finished rather than growing.
Ecosystem4/5Around 12.8M weekly downloads and hundreds of plugins built on the flake8.extension entry point, plus first-class pre-commit and editor support; the ecosystem is large but no longer growing, with new plugin work going to Ruff instead.

Use it if

  • You have an existing project already wired to flake8, with a setup.cfg full of tuned ignores and a plugin set your team relies on, and switching linters is not worth the churn this quarter
  • You depend on a plugin with no equivalent elsewhere: flake8-bandit, flake8-django, flake8-pytest-style variants, or an internal checker your company wrote against the flake8.extension entry point
  • You want a small, predictable tool with a rule set that has barely moved in years, so a CI pipeline that passes today still passes after a patch bump
  • You need to run on Python 3.9, which flake8 still supports
  • You want error detection separate from formatting, so black or another formatter owns layout and flake8 only reports genuine mistakes
Skip it if

Setup reality

pip install flake8 pulls pycodestyle, pyflakes, and mccabe at pinned version ranges, and running flake8 with no arguments lints the current directory. Then you hit the defaults. max-line-length is 79, which nothing in modern Python actually uses, and the default ignore list is E121, E123, E126, E226, E24, E704, W503, and W504, so the tool is already lying slightly about being pure PEP 8. If you use black, you have to reconcile them by hand: set max-line-length to 88 and add E203 to extend-ignore, because black's slice spacing trips pycodestyle. Config discovery is the other surprise. flake8 looks for setup.cfg, tox.ini, or .flake8 in the directory tree and deliberately does not read pyproject.toml, so a project that has consolidated everything else into pyproject keeps one extra file just for this. Use extend-ignore rather than ignore unless you mean to throw away the defaults, and the same for extend-select. Plugins install as ordinary packages and register through the flake8.extension entry point, which means they are active the moment they are installed, in every project sharing that environment; --require-plugins is how you make a missing one an error rather than a silent gap. Finally, note that flake8 caches nothing between runs and --jobs auto uses multiprocessing, which is unavailable on some Windows and container setups and quietly falls back to serial.

Patterns

Lint a project from the command lineinstall-and-run

pip install flake8

flake8                 # current directory
flake8 src/ tests/     # specific paths
flake8 --count src/    # print a total at the end

# src/app.py:12:1: F401 'os' imported but unused
# src/app.py:44:80: E501 line too long (92 > 79 characters)

Exit code is 1 when anything was reported and 0 when clean, which is all CI needs. The default line length of 79 is why a fresh run on a normal codebase floods with E501 before you have configured anything.

Put settings in setup.cfg, tox.ini, or .flake8config-file

# setup.cfg
[flake8]
max-line-length = 88
extend-ignore = E203, E704
extend-exclude = .venv,build,migrations
max-complexity = 10
per-file-ignores =
    __init__.py:F401
    tests/*:S101

flake8 does not read pyproject.toml, so this file exists purely for it. Use extend-ignore, not ignore: setting ignore replaces the built-in default list (E121, E123, E126, E226, E24, E704, W503, W504) and turns those checks back on.

Stop flake8 arguing with blackblack-compatibility

[flake8]
max-line-length = 88
extend-ignore = E203, E501, W503
# E203: whitespace before ':' (black formats slices this way)
# W503: line break before binary operator (black prefers it)
# E501: only if you let black own line length entirely

black and pycodestyle genuinely disagree about slice spacing and operator line breaks, so this is not optional if you run both. Dropping E501 is a choice: black will not split a long string or comment, so those lines stay long and unflagged.

Silence one line or one fileinline-suppression

import os  # noqa
import sys  # noqa: F401
from x import y, z  # noqa: F401,E501

# first line of a generated file, at column 0:
# flake8: noqa

Always give the code. A bare # noqa hides everything on that line, including a real error introduced later. A file-level # flake8: noqa must start the line; put it after code and flake8 prints a warning and ignores it. Codes on the file-level form are not supported.

Choose which checks runselect-and-ignore

flake8 --select=E9,F63,F7,F82 .        # syntax and undefined names only
flake8 --extend-select=C901 .          # add complexity on top of defaults
flake8 --extend-ignore=E501,W291 .     # keep defaults, drop two more
flake8 --disable-noqa .                # audit what noqa is hiding

--select replaces the entire active set, --extend-select adds to it. The four-code select above is the common CI smoke test: it catches syntax errors and undefined names without any style opinions. --disable-noqa is worth running once a quarter.

Relax rules for specific pathsper-file-ignores

[flake8]
per-file-ignores =
    __init__.py:F401,F403
    tests/**:D103
    scripts/one_off_*.py:T201

Patterns match against the path as given on the command line, so a rule written for tests/** does not fire when someone runs flake8 from inside the tests directory. This replaces the old trick of littering __init__.py with noqa comments.

Run it through pre-commitpre-commit-hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pycqa/flake8
    rev: 7.3.0
    hooks:
      - id: flake8
        additional_dependencies:
          - flake8-bugbear==24.12.12

pre-commit builds an isolated environment, so plugins must go in additional_dependencies or they are simply not there and their codes silently never fire. Pin the plugin version too, otherwise a new plugin release breaks CI on a day you changed nothing.

Add and enforce pluginsplugins

pip install flake8-bugbear flake8-comprehensions

flake8 --version
# 7.3.0 (flake8-bugbear: 24.12.12, mccabe: 0.7.0,
#  pycodestyle: 2.14.0, pyflakes: 3.4.0) CPython 3.12.3

# make a missing plugin fail loudly
flake8 --require-plugins=flake8-bugbear,flake8-comprehensions .

Installing a plugin activates it everywhere in that environment with no opt-in, which is how one developer's virtualenv ends up reporting codes CI never sees. --require-plugins in your config is the fix. flake8 --version is the fastest way to see what is actually loaded.

Flag over-complicated functionscomplexity-check

flake8 --max-complexity=10 src/
# src/handler.py:88:1: C901 'process' is too complex (17)

The mccabe check is installed but off by default; it only runs once you set a threshold. C901 counts branches, not lines, so a long but flat function passes while a short one with nested conditionals fails. Ten is the common starting value.

Get useful output in CIci-reporting

flake8 --count --statistics --show-source .

# machine readable, for annotations
flake8 --format='%(path)s:%(row)d:%(col)d: %(code)s %(text)s' .

# report but never fail the build (migration mode)
flake8 --exit-zero --output-file=flake8.txt .

--statistics prints a count per error code, which tells you which single rule is responsible for four hundred of your five hundred findings. --exit-zero is the honest way to introduce flake8 to a legacy codebase without blocking every pull request on day one.

Lint a buffer instead of a filelint-stdin

cat src/app.py | flake8 - --stdin-display-name=src/app.py

git diff --name-only --diff-filter=ACM 'HEAD' -- '*.py' \
  | xargs --no-run-if-empty flake8

The dash reads stdin, and --stdin-display-name is what makes the reported path match the real file so editors can jump to it. flake8 7 removed --diff, so linting only changed lines now means filtering the file list yourself as above.

Work out why a rule is or is not firingdebug-config

flake8 --bug-report            # JSON of versions, plugins, platform
flake8 -vv src/app.py          # very verbose: config files found, plugins run
flake8 --isolated src/app.py   # ignore every config file on disk

--isolated is the quickest way to prove that a surprising result comes from a config file somewhere up the tree rather than from the code. --bug-report is what maintainers ask for on issues and it also shows the exact plugin versions in play.

Alternatives

PackageRegistryPick it when
ruffPyPIYou want the same rules plus most plugin rules, autofix, pyproject.toml config, and a run that finishes in a fraction of the time
pylintPyPIYou want deeper analysis than pyflakes gives, including inference across modules and design warnings, and you can accept slower runs and more false positives
pycodestylePyPIYou only want the PEP 8 style checks and none of the plugin machinery or error detection
pyflakesPyPIYou only care about real errors such as unused imports and undefined names and want zero style opinions